github.com/google/cloudprober@v0.11.3/rds/gcp/forwarding_rules.go (about) 1 // Copyright 2020 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 // This file implements support for discovering forwarding rules in a GCP 16 // project. It currently supports only regional forwarding rules. We can 17 // consider adding support for global forwarding rules in future if necessary. 18 19 package gcp 20 21 import ( 22 "context" 23 "fmt" 24 "math/rand" 25 "sync" 26 "time" 27 28 "github.com/golang/protobuf/proto" 29 "github.com/google/cloudprober/logger" 30 configpb "github.com/google/cloudprober/rds/gcp/proto" 31 pb "github.com/google/cloudprober/rds/proto" 32 "github.com/google/cloudprober/rds/server/filter" 33 "golang.org/x/oauth2/google" 34 compute "google.golang.org/api/compute/v1" 35 ) 36 37 // frData struct encapsulates information for a fowarding rule. 38 type frData struct { 39 ip string 40 region string 41 } 42 43 /* 44 ForwardingRulesFilters defines filters supported by the forwarding_rules resource 45 type. 46 Example: 47 filter { 48 key: "name" 49 value: "cloudprober.*" 50 } 51 */ 52 var ForwardingRulesFilters = struct { 53 RegexFilterKeys []string 54 LabelsFilter bool 55 }{ 56 []string{"name", "region"}, 57 false, 58 } 59 60 // forwardingRulesLister is a GCE instances lister. It implements a cache, 61 // that's populated at a regular interval by making the GCE API calls. 62 // Listing actually only returns the current contents of that cache. 63 type forwardingRulesLister struct { 64 project string 65 c *configpb.ForwardingRules 66 thisInstance string 67 l *logger.Logger 68 69 mu sync.RWMutex 70 namesPerScope map[string][]string // "us-central1": ["fr1", "fr2"] 71 cachePerScope map[string]map[string]*frData // "us-central1": {"fr1": data} 72 computeSvc *compute.Service 73 } 74 75 // listResources returns the list of resource records, where each record 76 // consists of an instance name and the IP address associated with it. IP address 77 // to return is selected based on the provided ipConfig. 78 func (frl *forwardingRulesLister) listResources(req *pb.ListResourcesRequest) ([]*pb.Resource, error) { 79 var resources []*pb.Resource 80 81 allFilters, err := filter.ParseFilters(req.GetFilter(), ForwardingRulesFilters.RegexFilterKeys, "") 82 if err != nil { 83 return nil, err 84 } 85 86 nameFilter, regionFilter := allFilters.RegexFilters["name"], allFilters.RegexFilters["region"] 87 88 frl.mu.RLock() 89 defer frl.mu.RUnlock() 90 91 for region, names := range frl.namesPerScope { 92 cache := frl.cachePerScope[region] 93 94 for _, name := range names { 95 fr := cache[name] 96 97 if fr == nil { 98 frl.l.Errorf("forwarding_rules: cached info missing for %s", name) 99 continue 100 } 101 102 if nameFilter != nil && !nameFilter.Match(name, frl.l) { 103 continue 104 } 105 106 if regionFilter != nil && !regionFilter.Match(cache[name].region, frl.l) { 107 continue 108 } 109 110 resources = append(resources, &pb.Resource{ 111 Name: proto.String(name), 112 Ip: proto.String(cache[name].ip), 113 }) 114 } 115 } 116 117 frl.l.Infof("forwarding_rules.listResources: returning %d forwarding rules", len(resources)) 118 return resources, nil 119 } 120 121 func (frl *forwardingRulesLister) expandForRegion(region string) ([]string, map[string]*frData, error) { 122 var ( 123 names []string 124 cache = make(map[string]*frData) 125 ) 126 127 frList, err := frl.computeSvc.ForwardingRules.List(frl.project, region).Do() 128 if err != nil { 129 return nil, nil, err 130 } 131 for _, item := range frList.Items { 132 cache[item.Name] = &frData{ 133 ip: item.IPAddress, 134 region: region, 135 } 136 names = append(names, item.Name) 137 } 138 139 return names, cache, nil 140 } 141 142 // expand runs equivalent API calls as "gcloud compute instances list", 143 // and is what is used to populate the cache. 144 func (frl *forwardingRulesLister) expand(reEvalInterval time.Duration) { 145 frl.l.Debugf("forwarding_rules.expand: running for the project: %s", frl.project) 146 147 regionList, err := frl.computeSvc.Regions.List(frl.project).Filter(frl.c.GetRegionFilter()).Do() 148 if err != nil { 149 frl.l.Errorf("forwarding_rules.expand: error while getting list of all regions: %v", err) 150 return 151 } 152 153 // Shuffle the regions list to change the order in each cycle. 154 rl := regionList.Items 155 rand.Seed(time.Now().UnixNano()) 156 rand.Shuffle(len(rl), func(i, j int) { rl[i], rl[j] = rl[j], rl[i] }) 157 158 frl.l.Infof("forwarding_rules.expand: expanding GCE targets for %d regions", len(rl)) 159 160 var numItems int 161 162 sleepBetweenRegions := reEvalInterval / (2 * time.Duration(len(rl)+1)) 163 for _, region := range rl { 164 names, cache, err := frl.expandForRegion(region.Name) 165 if err != nil { 166 frl.l.Errorf("forwarding_rules.expand: error while listing forwarding rules in region (%s): %v", region.Name, err) 167 continue 168 } 169 170 frl.mu.Lock() 171 frl.cachePerScope[region.Name] = cache 172 frl.namesPerScope[region.Name] = names 173 frl.mu.Unlock() 174 175 numItems += len(names) 176 time.Sleep(sleepBetweenRegions) 177 } 178 179 frl.l.Infof("forwarding_rules.expand: got %d forwarding rules", numItems) 180 } 181 182 // defaultComputeService returns a compute.Service object, initialized using 183 // default credentials. 184 func defaultComputeService(apiVersion string) (*compute.Service, error) { 185 client, err := google.DefaultClient(context.Background(), compute.ComputeScope) 186 if err != nil { 187 return nil, err 188 } 189 cs, err := compute.New(client) 190 if err != nil { 191 return nil, err 192 } 193 194 cs.BasePath = "https://www.googleapis.com/compute/" + apiVersion + "/projects/" 195 return cs, nil 196 } 197 198 func newForwardingRulesLister(project, apiVersion string, c *configpb.ForwardingRules, l *logger.Logger) (*forwardingRulesLister, error) { 199 cs, err := defaultComputeService(apiVersion) 200 if err != nil { 201 return nil, fmt.Errorf("forwarding_rules.expand: error creating compute service: %v", err) 202 } 203 204 frl := &forwardingRulesLister{ 205 project: project, 206 c: c, 207 cachePerScope: make(map[string]map[string]*frData), 208 namesPerScope: make(map[string][]string), 209 computeSvc: cs, 210 l: l, 211 } 212 213 reEvalInterval := time.Duration(c.GetReEvalSec()) * time.Second 214 go func() { 215 frl.expand(0) 216 // Introduce a random delay between 0-reEvalInterval before 217 // starting the refresh loop. If there are multiple cloudprober 218 // forwardingRules, this will make sure that each instance calls GCE 219 // API at a different point of time. 220 rand.Seed(time.Now().UnixNano()) 221 randomDelaySec := rand.Intn(int(reEvalInterval.Seconds())) 222 time.Sleep(time.Duration(randomDelaySec) * time.Second) 223 for range time.Tick(reEvalInterval) { 224 frl.expand(reEvalInterval) 225 } 226 }() 227 return frl, nil 228 }