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  }