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 }