github.com/google/cloudprober@v0.11.3/rds/gcp/rtc_variables.go (about) 1 // Copyright 2018 The Cloudprober 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 gcp 16 17 import ( 18 "context" 19 "fmt" 20 "math/rand" 21 "strings" 22 "sync" 23 "time" 24 25 "github.com/golang/protobuf/proto" 26 "github.com/google/cloudprober/logger" 27 configpb "github.com/google/cloudprober/rds/gcp/proto" 28 pb "github.com/google/cloudprober/rds/proto" 29 "github.com/google/cloudprober/rds/server/filter" 30 "golang.org/x/oauth2/google" 31 runtimeconfig "google.golang.org/api/runtimeconfig/v1beta1" 32 ) 33 34 // rtcVar is an internal representation of the runtimeconfig (RTC) variables. 35 // We currently store only the variable name and its update time. In future, we 36 // may decide to include more information in RTC variables. 37 type rtcVar struct { 38 name string 39 updateTime time.Time 40 } 41 42 // rtcVariablesLister is a RuntimeConfig Variables lister. It implements a 43 // cache, that's populated at a regular interval by making the GCP API calls. 44 // Listing actually only returns the current contents of that cache. 45 type rtcVariablesLister struct { 46 project string 47 c *configpb.RTCVariables 48 apiVersion string 49 l *logger.Logger 50 51 mu sync.RWMutex // Mutex for names and cache 52 cache map[string][]*rtcVar 53 svc *runtimeconfig.ProjectsConfigsVariablesService 54 } 55 56 // listResources returns the list of resource records, where each record 57 // consists of a RTC variable name. 58 func (rvl *rtcVariablesLister) listResources(req *pb.ListResourcesRequest) ([]*pb.Resource, error) { 59 var resources []*pb.Resource 60 61 allFilters, err := filter.ParseFilters(req.GetFilter(), []string{"config_name"}, "updated_within") 62 if err != nil { 63 return nil, err 64 } 65 66 configNameFilter, freshnessFilter := allFilters.RegexFilters["config_name"], allFilters.FreshnessFilter 67 68 rvl.mu.RLock() 69 defer rvl.mu.RUnlock() 70 for configName, rtcVars := range rvl.cache { 71 if configNameFilter != nil && !configNameFilter.Match(configName, rvl.l) { 72 continue 73 } 74 75 for _, rtcVar := range rtcVars { 76 if freshnessFilter != nil && !freshnessFilter.Match(rtcVar.updateTime, rvl.l) { 77 continue 78 } 79 80 resources = append(resources, &pb.Resource{ 81 Name: proto.String(rtcVar.name), 82 }) 83 } 84 } 85 86 if len(resources) > 0 { 87 rvl.l.Infof("rtc_variables.listResources: returning %d variables", len(resources)) 88 } 89 return resources, nil 90 } 91 92 // defaultRTCService returns a compute.Service object, initialized using the 93 // default credentials. 94 func defaultRTCService() (*runtimeconfig.ProjectsConfigsVariablesService, error) { 95 client, err := google.DefaultClient(context.Background(), runtimeconfig.CloudruntimeconfigScope) 96 if err != nil { 97 return nil, err 98 } 99 svc, err := runtimeconfig.New(client) 100 if err != nil { 101 return nil, err 102 } 103 return runtimeconfig.NewProjectsConfigsVariablesService(svc), nil 104 } 105 106 // processVar processes the RTC variable resource and returns an rtcVar object. 107 func processVar(v *runtimeconfig.Variable) (*rtcVar, error) { 108 // Variable names include the full path, including the config name. 109 varParts := strings.Split(v.Name, "/") 110 if len(varParts) == 1 { 111 return nil, fmt.Errorf("invalid variable name: %s", v.Name) 112 } 113 varName := varParts[len(varParts)-1] 114 115 // Variable update time is in RFC3339 format 116 // https://cloud.google.com/deployment-manager/runtime-configurator/reference/rest/v1beta1/projects.configs.variables 117 updateTime, err := time.Parse(time.RFC3339Nano, v.UpdateTime) 118 if err != nil { 119 return nil, fmt.Errorf("could not parse variable(%s) update time (%s): %v", v.Name, v.UpdateTime, err) 120 } 121 122 return &rtcVar{varName, updateTime}, nil 123 } 124 125 // expand makes API calls to list variables in configured RTC configs and 126 // populates the cache. 127 func (rvl *rtcVariablesLister) expand(rtcConfig *configpb.RTCVariables_RTCConfig, reEvalInterval time.Duration) { 128 path := "projects/" + rvl.project + "/configs/" + rtcConfig.GetName() 129 configVarsList, err := rvl.svc.List(path).Do() 130 if err != nil { 131 rvl.l.Errorf("rtc_variables.expand: error while getting list of all vars for config path %s: %v", path, err) 132 return 133 } 134 135 result := make([]*rtcVar, len(configVarsList.Variables)) 136 for i, v := range configVarsList.Variables { 137 rvl.l.Debugf("rtc_variables.expand: processing runtime-config var: %s, update time: %s", v.Name, v.UpdateTime) 138 rv, err := processVar(v) 139 if err != nil { 140 rvl.l.Errorf("Error processing the RTC variable (%s): %v", v.Name, err) 141 continue 142 } 143 result[i] = rv 144 } 145 146 rvl.mu.Lock() 147 rvl.cache[rtcConfig.GetName()] = result 148 rvl.mu.Unlock() 149 } 150 151 func newRTCVariablesLister(project, apiVersion string, c *configpb.RTCVariables, l *logger.Logger) (*rtcVariablesLister, error) { 152 svc, err := defaultRTCService() 153 if err != nil { 154 return nil, fmt.Errorf("rtc_variables.expand: error creating RTC service: %v", err) 155 } 156 157 rvl := &rtcVariablesLister{ 158 project: project, 159 c: c, 160 apiVersion: apiVersion, 161 cache: make(map[string][]*rtcVar), 162 svc: svc, 163 l: l, 164 } 165 166 for _, rtcConfig := range rvl.c.GetRtcConfig() { 167 rvl.l.Infof("rtc_variables.expand: expanding RTC vars for project (%s) and config (%s)", rvl.project, rtcConfig.GetName()) 168 169 reEvalInterval := time.Duration(rtcConfig.GetReEvalSec()) * time.Second 170 go func(rtcConfig *configpb.RTCVariables_RTCConfig) { 171 rvl.expand(rtcConfig, 0) 172 // Introduce a random delay between 0-reEvalInterval before 173 // starting the refresh loop. If there are multiple cloudprober 174 // rtcVariables, this will make sure that each instance calls GCE 175 // API at a different point of time. 176 rand.Seed(time.Now().UnixNano()) 177 randomDelaySec := rand.Intn(int(reEvalInterval.Seconds())) 178 time.Sleep(time.Duration(randomDelaySec) * time.Second) 179 for range time.Tick(reEvalInterval) { 180 rvl.expand(rtcConfig, reEvalInterval) 181 } 182 }(rtcConfig) 183 } 184 return rvl, nil 185 }