github.com/GoogleCloudPlatform/testgrid@v0.0.174/config/cache.go (about) 1 /* 2 Copyright 2021 The TestGrid Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package config 18 19 import ( 20 "context" 21 "fmt" 22 "os" 23 "strings" 24 "sync" 25 "time" 26 27 "cloud.google.com/go/storage" 28 29 configpb "github.com/GoogleCloudPlatform/testgrid/pb/config" 30 "github.com/GoogleCloudPlatform/testgrid/util/gcs" 31 ) 32 33 // TODO(slchase): remove 34 // This "eventual consistency" isn't as useful for the utilities that are using Read() or ReadGCS(). Complex cases are handled by the snapshot library. 35 var ( 36 cache map[string]Config 37 cacheLock sync.RWMutex 38 ) 39 40 const cacheRefreshInterval = 5 * time.Minute 41 42 // Config holds a config proto and when it was fetched. 43 type Config struct { 44 proto *configpb.Configuration 45 attrs *storage.ReaderObjectAttrs 46 lastFetch time.Time 47 } 48 49 func init() { 50 cache = map[string]Config{} 51 } 52 53 // InitCache clears the cache of configs, forcing the next ReadGCS call to fetch fresh from GCS. 54 // 55 // Used primarily for testing 56 func InitCache() { 57 cacheLock.Lock() 58 defer cacheLock.Unlock() 59 cache = map[string]Config{} 60 } 61 62 // ReadGCS opens the config at path and unmarshals it into a Configuration proto. 63 // 64 // If it has been read recently, a cached version will be served. 65 func ReadGCS(ctx context.Context, opener gcs.Opener, path gcs.Path) (*configpb.Configuration, *storage.ReaderObjectAttrs, error) { 66 cacheLock.Lock() 67 defer cacheLock.Unlock() 68 cfg, ok := cache[path.String()] 69 if ok && time.Since(cfg.lastFetch) < cacheRefreshInterval { 70 return cfg.proto, cfg.attrs, nil 71 } 72 73 r, attrs, err := opener.Open(ctx, path) 74 if err != nil { 75 return nil, nil, fmt.Errorf("failed to open config: %w", err) 76 } 77 p, err := Unmarshal(r) 78 defer r.Close() 79 if err != nil { 80 return nil, nil, fmt.Errorf("failed to unmarshal config: %w", err) 81 } 82 cache[path.String()] = Config{ 83 proto: p, 84 attrs: attrs, 85 lastFetch: time.Now(), 86 } 87 return p, attrs, nil 88 } 89 90 // ReadPath reads the config from the specified local file path. 91 func ReadPath(path string) (*configpb.Configuration, error) { 92 f, err := os.Open(path) 93 if err != nil { 94 return nil, fmt.Errorf("open: %v", err) 95 } 96 return Unmarshal(f) 97 } 98 99 // Read will read the Configuration proto message from a local or gs:// path. 100 // 101 // The ctx and client are only relevant when path refers to GCS. 102 func Read(ctx context.Context, path string, client *storage.Client) (*configpb.Configuration, error) { 103 if strings.HasPrefix(path, "gs://") { 104 gcsPath, err := gcs.NewPath(path) 105 if err != nil { 106 return nil, fmt.Errorf("bad path: %v", err) 107 } 108 c, _, err := ReadGCS(ctx, gcs.NewClient(client), *gcsPath) 109 return c, err 110 } 111 return ReadPath(path) 112 }