go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/quotabeta/quotaconfig/configservice/configservice.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 configservice provides an implementation of quotaconfig.Interface 16 // which fetches *pb.Policy configs stored with the LUCI Config service. Create 17 // an instance suitable for use with the quota library using New. 18 package configservice 19 20 import ( 21 "context" 22 23 "google.golang.org/protobuf/proto" 24 25 "go.chromium.org/luci/common/errors" 26 "go.chromium.org/luci/config" 27 "go.chromium.org/luci/config/cfgclient" 28 "go.chromium.org/luci/config/validation" 29 30 "go.chromium.org/luci/server/caching" 31 pb "go.chromium.org/luci/server/quotabeta/proto" 32 "go.chromium.org/luci/server/quotabeta/quotaconfig" 33 ) 34 35 // Ensure configService implements Interface at compile-time. 36 var _ quotaconfig.Interface = &configService{} 37 38 // configService fetches known *pb.Policy protos from the config service. 39 // Implements Interface. Safe for concurrent use as long as the cache is 40 // also safe for concurrent use. 41 type configService struct { 42 cache caching.BlobCache 43 cfgSet config.Set 44 path string 45 } 46 47 // New returns a quotaconfig.Interface which fetches known *pb.Policy protos at 48 // the given path for the given config set known to the LUCI Config service. 49 // Suitable for use with the quota library by calling 50 // quota.WithConfig(ctx, configservice.New(ctx, cfgSet, path)). 51 // Panics if caching.GlobalCache() returns nil, which shouldn't happen if the 52 // redisconn module (required by the quota library) has been initialized. 53 func New(ctx context.Context, cfgSet config.Set, path string) quotaconfig.Interface { 54 cache := caching.GlobalCache(ctx, "quota.configservice") 55 if cache == nil { 56 // Shouldn't happen, since the quota library depends on redisconn which 57 // installs a redis-based cache into the context on module initialization. 58 panic(errors.New("no global cache available")) 59 } 60 return &configService{ 61 cache: cache, 62 cfgSet: cfgSet, 63 path: path, 64 } 65 } 66 67 // Get returns a cached copy of the named *pb.Policy if it exists, or 68 // quotaconfig.ErrNotFound if it doesn't. Errs if the context doesn't contain a 69 // cfgclient.Interface (usually installed by config/service/cfgmodule). 70 func (c *configService) Get(ctx context.Context, name string) (*pb.Policy, error) { 71 b, err := c.cache.Get(ctx, name) 72 switch { 73 case err == caching.ErrCacheMiss: 74 return nil, quotaconfig.ErrNotFound 75 case err != nil: 76 return nil, errors.Annotate(err, "retrieving cached policy %q", name).Err() 77 } 78 p := &pb.Policy{} 79 if err := proto.Unmarshal(b, p); err != nil { 80 return nil, errors.Annotate(err, "unmarshalling cached policy %q", name).Err() 81 } 82 return p, nil 83 } 84 85 // Refresh fetches all *pb.Policies from the LUCI Config service. 86 func (c *configService) Refresh(ctx context.Context) error { 87 s := &pb.Config{} 88 if err := cfgclient.Get(ctx, c.cfgSet, c.path, cfgclient.ProtoText(s), nil); err != nil { 89 return errors.Annotate(err, "fetching policy config %q for config set %q", c.path, c.cfgSet).Err() 90 } 91 v := &validation.Context{ 92 Context: ctx, 93 } 94 for i, p := range s.GetPolicy() { 95 v.Enter("policy %d", i) 96 quotaconfig.ValidatePolicy(v, p) 97 v.Exit() 98 } 99 if err := v.Finalize(); err != nil { 100 return errors.Annotate(err, "policy config %q for config set %q did not pass validation", c.path, c.cfgSet).Err() 101 } 102 for _, p := range s.GetPolicy() { 103 b, err := proto.Marshal(p) 104 if err != nil { 105 return errors.Annotate(err, "marshalling policy %q", p.Name).Err() 106 } 107 if err := c.cache.Set(ctx, p.Name, b, 0); err != nil { 108 return errors.Annotate(err, "caching policy %q", p.Name).Err() 109 } 110 } 111 return nil 112 }