go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/quota/register.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package quota
    16  
    17  import (
    18  	"sync"
    19  
    20  	"go.chromium.org/luci/common/data/stringset"
    21  	"go.chromium.org/luci/common/errors"
    22  
    23  	"go.chromium.org/luci/server/quota/quotapb"
    24  )
    25  
    26  type Application struct {
    27  	id        string
    28  	resources stringset.Set
    29  }
    30  
    31  var quotaApplicationsMu sync.RWMutex
    32  var quotaApplications = map[string]*Application{}
    33  var quotaApplicationsClosed bool
    34  var quotaAppTypesOnce sync.Once
    35  
    36  type ApplicationOptions struct {
    37  	// ResourceTypes enumerates all the ResourceTypes this application can use.
    38  	//
    39  	// Policies and Accounts both specify a single resource type, and these must
    40  	// match. I.e. you cannot use a policy for `qps` to manage a `storage_bytes`
    41  	// account.
    42  	ResourceTypes []string
    43  }
    44  
    45  func Register(appID string, ao *ApplicationOptions) *Application {
    46  	if len(appID) == 0 {
    47  		panic("empty appID")
    48  	}
    49  	resources := stringset.NewFromSlice(ao.ResourceTypes...)
    50  	if len(resources) == 0 {
    51  		panic(errors.New("quota app registration requires at least one resource type"))
    52  	}
    53  	if resources.Has("") {
    54  		panic(errors.New("resource types must not include an empty string"))
    55  	}
    56  
    57  	quotaApplicationsMu.Lock()
    58  	defer quotaApplicationsMu.Unlock()
    59  
    60  	if quotaApplicationsClosed {
    61  		panic(errors.New("quota app registration already closed"))
    62  	}
    63  	if _, ok := quotaApplications[appID]; ok {
    64  		panic(errors.Reason("appID %q already registered", appID).Err())
    65  	}
    66  	ret := &Application{appID, resources}
    67  	quotaApplications[appID] = ret
    68  	return ret
    69  }
    70  
    71  // AccountID is a convenience method to make an AccountID tied to this
    72  // application.
    73  //
    74  // Will panic if resourceType is not registered for this Application.
    75  func (a *Application) AccountID(realm, namespace, name, resourceType string) *quotapb.AccountID {
    76  	if !a.resources.Has(resourceType) {
    77  		panic(errors.Reason("application %q does not have resourceType %q", a.id, resourceType).Err())
    78  	}
    79  	return &quotapb.AccountID{
    80  		AppId:        a.id,
    81  		Realm:        realm,
    82  		Namespace:    namespace,
    83  		Name:         name,
    84  		ResourceType: resourceType,
    85  	}
    86  }