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  }