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

     1  // Copyright 2017-2019 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 implements Google Compute Engine (GCE) targets for Cloudprober.
    16  //
    17  // It currently supports following GCE targets:
    18  //	Instances
    19  //	Forwarding Rules (only regional currently)
    20  //
    21  // Targets are configured through a config file, based on the protobuf defined
    22  // in the config.proto file in the same directory. Example config:
    23  //
    24  // All instances matching a certain regex:
    25  //	targets {
    26  //	  gce_targets {
    27  //	    instances {}
    28  //	  }
    29  //	  regex: "ig-proxy-.*"
    30  //	}
    31  //
    32  // Public IP of all instances matching a certain regex:
    33  //	targets {
    34  //	  gce_targets {
    35  //	    instances {
    36  //	      public_ip: true
    37  //	    }
    38  //	  }
    39  //	  regex: "ig-proxy-.*"
    40  //	}
    41  //
    42  // All forwarding rules in the local region:
    43  //	targets {
    44  //	  gce_targets {
    45  //	    forwarding_rules {}
    46  //	  }
    47  //	}
    48  package gce
    49  
    50  import (
    51  	"context"
    52  	"errors"
    53  	"fmt"
    54  	"net"
    55  	"sync"
    56  
    57  	"cloud.google.com/go/compute/metadata"
    58  	"github.com/golang/protobuf/proto"
    59  	"github.com/google/cloudprober/logger"
    60  	"github.com/google/cloudprober/targets/endpoint"
    61  	configpb "github.com/google/cloudprober/targets/gce/proto"
    62  	dnsRes "github.com/google/cloudprober/targets/resolver"
    63  
    64  	"github.com/google/cloudprober/rds/client"
    65  	clientconfigpb "github.com/google/cloudprober/rds/client/proto"
    66  	"github.com/google/cloudprober/rds/gcp"
    67  	rdspb "github.com/google/cloudprober/rds/proto"
    68  	"github.com/google/cloudprober/rds/server"
    69  	serverconfigpb "github.com/google/cloudprober/rds/server/proto"
    70  )
    71  
    72  var global struct {
    73  	mu      sync.RWMutex
    74  	servers map[string]*server.Server
    75  }
    76  
    77  // Targets interface is redefined here (originally defined in targets.go) to
    78  // allow returning private gceResources.
    79  type Targets interface {
    80  	// ListEndpoints produces list of targets.
    81  	ListEndpoints() []endpoint.Endpoint
    82  	// Resolve, given a target name and IP Version will return the IP address for
    83  	// that target.
    84  	Resolve(name string, ipVer int) (net.IP, error)
    85  }
    86  
    87  // gceResources encapsulates a set of GCE resources of a particular type. It's
    88  // mostly a wrapper around RDS clients created during the initialization step.
    89  type gceResources struct {
    90  	c          *configpb.TargetsConf
    91  	globalOpts *configpb.GlobalOptions
    92  	l          *logger.Logger
    93  
    94  	// RDS parameters
    95  	resourceType string
    96  	ipConfig     *rdspb.IPConfig
    97  	filters      []*rdspb.Filter
    98  	clients      map[string]*client.Client
    99  
   100  	// DNS config
   101  	r      *dnsRes.Resolver
   102  	useDNS bool
   103  }
   104  
   105  func initRDSServer(resourceType, apiVersion string, projects []string, reEvalInterval int, l *logger.Logger) (*server.Server, error) {
   106  	global.mu.Lock()
   107  	defer global.mu.Unlock()
   108  
   109  	if global.servers == nil {
   110  		global.servers = make(map[string]*server.Server)
   111  	}
   112  
   113  	if global.servers[resourceType] != nil {
   114  		return global.servers[resourceType], nil
   115  	}
   116  
   117  	pc := gcp.DefaultProviderConfig(projects, map[string]string{gcp.ResourceTypes.GCEInstances: ""}, reEvalInterval, apiVersion)
   118  	srv, err := server.New(context.Background(), &serverconfigpb.ServerConf{Provider: []*serverconfigpb.Provider{pc}}, nil, l)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	global.servers[resourceType] = srv
   124  	return srv, nil
   125  }
   126  
   127  func newRDSClient(req *rdspb.ListResourcesRequest, listResourcesFunc client.ListResourcesFunc, l *logger.Logger) (*client.Client, error) {
   128  	c := &clientconfigpb.ClientConf{
   129  		Request:   req,
   130  		ReEvalSec: proto.Int32(10), // Refresh client every 10s.
   131  	}
   132  
   133  	client, err := client.New(c, listResourcesFunc, l)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	return client, nil
   139  }
   140  
   141  func (gr *gceResources) rdsRequest(project string) *rdspb.ListResourcesRequest {
   142  	return &rdspb.ListResourcesRequest{
   143  		Provider:     proto.String("gcp"),
   144  		ResourcePath: proto.String(fmt.Sprintf("%s/%s", gr.resourceType, project)),
   145  		Filter:       gr.filters,
   146  		IpConfig:     gr.ipConfig,
   147  	}
   148  }
   149  
   150  func (gr *gceResources) initClients(projects []string) error {
   151  	srv, err := initRDSServer(gr.resourceType, gr.globalOpts.GetApiVersion(), projects, int(gr.globalOpts.GetReEvalSec()), gr.l)
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	for _, project := range projects {
   157  		client, err := newRDSClient(gr.rdsRequest(project), srv.ListResources, gr.l)
   158  		if err != nil {
   159  			return err
   160  		}
   161  		gr.clients[project] = client
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  // ListEndpoints returns the list of GCE endpoints.
   168  func (gr *gceResources) ListEndpoints() []endpoint.Endpoint {
   169  	var ep []endpoint.Endpoint
   170  	for _, client := range gr.clients {
   171  		ep = append(ep, client.ListEndpoints()...)
   172  	}
   173  	return ep
   174  }
   175  
   176  // Resolve resolves the name into an IP address. Unless explicitly configured
   177  // to use DNS, we use the RDS client to determine the resource IPs.
   178  func (gr *gceResources) Resolve(name string, ipVer int) (net.IP, error) {
   179  	if gr.useDNS {
   180  		return gr.r.Resolve(name, ipVer)
   181  	}
   182  
   183  	var ip net.IP
   184  	var err error
   185  
   186  	for _, client := range gr.clients {
   187  		ip, err = client.Resolve(name, ipVer)
   188  		if err == nil {
   189  			return ip, err
   190  		}
   191  	}
   192  
   193  	return ip, err
   194  }
   195  
   196  // New is a helper function to unpack a Targets proto into a Targets interface.
   197  func New(conf *configpb.TargetsConf, globalOpts *configpb.GlobalOptions, res *dnsRes.Resolver, l *logger.Logger) (Targets, error) {
   198  	projects := conf.GetProject()
   199  	if projects == nil {
   200  		if !metadata.OnGCE() {
   201  			return nil, errors.New("targets.gce.New(): project is required for GCE targets when not running on GCE")
   202  		}
   203  		currentProj, err := metadata.ProjectID()
   204  		projects = append([]string{}, currentProj)
   205  		if err != nil {
   206  			return nil, fmt.Errorf("targets.gce.New(): Error getting project ID: %v", err)
   207  		}
   208  	}
   209  
   210  	gr := &gceResources{
   211  		c:          conf,
   212  		ipConfig:   instancesIPConfig(conf.GetInstances()),
   213  		clients:    make(map[string]*client.Client),
   214  		globalOpts: globalOpts,
   215  		l:          l,
   216  	}
   217  
   218  	switch conf.Type.(type) {
   219  	case *configpb.TargetsConf_Instances:
   220  		gr.resourceType = "gce_instances"
   221  		// Verify that config is correct.
   222  		if err := verifyInstancesConfig(conf.GetInstances(), res); err != nil {
   223  			return nil, err
   224  		}
   225  
   226  		if len(conf.GetInstances().GetLabel()) > 0 {
   227  			filters, err := parseLabels(conf.GetInstances().GetLabel())
   228  			if err != nil {
   229  				return nil, err
   230  			}
   231  			gr.filters = filters
   232  		}
   233  		return gr, gr.initClients(projects)
   234  
   235  	case *configpb.TargetsConf_ForwardingRules:
   236  		// TODO(manugarg): implement forwarding rules using gceResources like instances.go.
   237  		frp, err := newForwardingRules(projects[0], globalOpts, conf.GetForwardingRules(), l)
   238  		if err != nil {
   239  			return nil, err
   240  		}
   241  		return frp, nil
   242  	}
   243  
   244  	return nil, errors.New("unknown GCE targets type")
   245  }