github.com/google/cloudprober@v0.11.3/targets/gce/forwarding_rules.go (about)

     1  // Copyright 2017 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 gce
    16  
    17  import (
    18  	"errors"
    19  	"fmt"
    20  	"net"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  
    25  	"cloud.google.com/go/compute/metadata"
    26  	"github.com/google/cloudprober/logger"
    27  	"github.com/google/cloudprober/targets/endpoint"
    28  	configpb "github.com/google/cloudprober/targets/gce/proto"
    29  	compute "google.golang.org/api/compute/v1"
    30  )
    31  
    32  // globalForwardingRules is a singleton instance of the forwardingRules struct.
    33  // It is presented as a singleton because, like instances, forwardingRules provides
    34  // a cache layer that is best shared by all probes.
    35  var (
    36  	globalForwardingRules *forwardingRules
    37  	onceForwardingRules   sync.Once
    38  )
    39  
    40  // forwardingRules is a lister which lists GCE forwarding rules (see
    41  // https://cloud.google.com/compute/docs/load-balancing/network/forwarding-rules
    42  // for information on forwarding rules). In addition to being able to list the
    43  // rules, this particular lister implements a cache. On a timer (configured by
    44  // GlobalGCETargetsOptions.re_eval_sec cloudprober/targets/targets.proto) in the
    45  // background the cache will be populated (by the equivalent of running "gcloud
    46  // compute forwarding-rules list"). Listing actually only returns the current
    47  // contents of that cache.
    48  //
    49  // Note that because this uses the GCLOUD API, GCE staging is unable to use this
    50  // target type. See b/26320525 for more on this.
    51  //
    52  // TODO(izzycecil): The cache layer provided by this, instances, lameduck, and resolver
    53  //               are all pretty similar. RTC will need a similar cache. I should
    54  //               abstract out this whole cache layer. It will be more testable that
    55  //               way, and probably more readable, as well.
    56  type forwardingRules struct {
    57  	project     string
    58  	c           *configpb.ForwardingRules
    59  	names       []string
    60  	localRegion string
    61  	cache       map[string]*compute.ForwardingRule
    62  	apiVersion  string
    63  	l           *logger.Logger
    64  }
    65  
    66  // List produces a list of all the forwarding rules. The list is similar to
    67  // "gcloud compute forwarding-rules list", but with a cache layer reducing the
    68  // number of actual API calls made.
    69  func (frp *forwardingRules) ListEndpoints() []endpoint.Endpoint {
    70  	return endpoint.EndpointsFromNames(frp.names)
    71  }
    72  
    73  // Resolve returns the IP address associated with the forwarding
    74  // rule. Eventually we can expand this to return protocol and port as well.
    75  func (frp *forwardingRules) Resolve(name string, ipVer int) (net.IP, error) {
    76  	f := frp.cache[name]
    77  	if f == nil {
    78  		return nil, fmt.Errorf("gce.forwardingRulesProvider.resolve(%s): forwarding rule not in in-memory GCE forwardingRules database", name)
    79  	}
    80  	return net.ParseIP(f.IPAddress), nil
    81  }
    82  
    83  // This function will attempt to refresh the cache of GCE targets.
    84  // This attempt may fail, in which case the function will log and leave the cache untouched,
    85  // since it is assumed that the cache will be refreshed by a subsequent call.
    86  // It runs API calls equivalent to "gcloud compute forwarding-rules list"
    87  func (frp *forwardingRules) expand() {
    88  	frp.l.Infof("gce.forwardingRules.expand: expanding GCE targets")
    89  
    90  	cs, err := defaultComputeService(frp.apiVersion)
    91  	if err != nil {
    92  		frp.l.Errorf("gce.forwardingRules.expand: error while creating the compute service: %v", err)
    93  		return
    94  	}
    95  
    96  	regions, err := frp.getTargetRegions(cs)
    97  	if err != nil {
    98  		frp.l.Errorf("gce.forwardingRules.expand: error while getting the list of target regions: %v", err)
    99  		return
   100  	}
   101  
   102  	var forwardingRulesList []*compute.ForwardingRule
   103  
   104  	for _, region := range regions {
   105  		l, err := cs.ForwardingRules.List(frp.project, region).Do()
   106  		if err != nil {
   107  			frp.l.Errorf("gce.forwardingRules.expand(region=%s): error while getting the list of forwarding rules: %v", region, err)
   108  			return
   109  		}
   110  		forwardingRulesList = append(forwardingRulesList, l.Items...)
   111  	}
   112  
   113  	var result []string
   114  	for _, ins := range forwardingRulesList {
   115  		frp.cache[ins.Name] = ins
   116  		result = append(result, ins.Name)
   117  	}
   118  
   119  	frp.l.Debugf("Expanded target list: %q", result)
   120  	frp.names = result
   121  }
   122  
   123  // getTargetRegions returns the list of regions we are interested in based on
   124  // the configuration.
   125  func (frp *forwardingRules) getTargetRegions(cs *compute.Service) ([]string, error) {
   126  	// Select local region if region is not specified.
   127  	if len(frp.c.GetRegion()) == 0 {
   128  		return []string{frp.localRegion}, nil
   129  	}
   130  
   131  	// If more than one region is specified or only specified region is not "all"
   132  	if len(frp.c.GetRegion()) > 1 || frp.c.GetRegion()[0] != "all" {
   133  		return frp.c.GetRegion(), nil
   134  	}
   135  
   136  	l, err := cs.Regions.List(frp.project).Do()
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	regions := make([]string, len(l.Items))
   142  	for i := range l.Items {
   143  		regions[i] = l.Items[i].Name
   144  	}
   145  
   146  	return regions, nil
   147  }
   148  
   149  // Instance's region is not stored in the metadata, we need to get it from the zone.
   150  func getLocalRegion() (string, error) {
   151  	if !metadata.OnGCE() {
   152  		return "", errors.New("getLocalRegion: not running on GCE")
   153  	}
   154  
   155  	zone, err := metadata.Zone()
   156  	if err != nil {
   157  		return "", err
   158  	}
   159  
   160  	zoneParts := strings.Split(zone, "-")
   161  	return strings.Join(zoneParts[0:len(zoneParts)-1], "-"), nil
   162  }
   163  
   164  // newForwardingrules will (if needed) initialize and return the
   165  // globalForwardingRules singleton.
   166  func newForwardingRules(project string, opts *configpb.GlobalOptions, frpb *configpb.ForwardingRules, l *logger.Logger) (*forwardingRules, error) {
   167  	reEvalInterval := time.Duration(opts.GetReEvalSec()) * time.Second
   168  
   169  	var localRegion string
   170  	var err error
   171  
   172  	// Initialize forwardingRules provider only once
   173  	onceForwardingRules.Do(func() {
   174  
   175  		if len(frpb.GetRegion()) == 0 {
   176  			localRegion, err = getLocalRegion()
   177  			if err != nil {
   178  				err = fmt.Errorf("gce.newForwardingRules: error while getting local region: %v", err)
   179  				return
   180  			}
   181  			l.Infof("gce.newForwardingRules: local region: %s", localRegion)
   182  		}
   183  
   184  		globalForwardingRules = &forwardingRules{
   185  			project:     project,
   186  			c:           frpb,
   187  			localRegion: localRegion,
   188  			cache:       make(map[string]*compute.ForwardingRule),
   189  			apiVersion:  opts.GetApiVersion(),
   190  			l:           l,
   191  		}
   192  
   193  		go func() {
   194  			globalForwardingRules.expand()
   195  			for range time.Tick(reEvalInterval) {
   196  				globalForwardingRules.expand()
   197  			}
   198  		}()
   199  	})
   200  
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	return globalForwardingRules, err
   206  }