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 "apb.AccountID{ 80 AppId: a.id, 81 Realm: realm, 82 Namespace: namespace, 83 Name: name, 84 ResourceType: resourceType, 85 } 86 }