github.com/google/cloudprober@v0.11.3/targets/targets.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 targets provides the means to list and resolve targets for probers in
    16  // the cloudprober framework. To learn more about the kinds of targets
    17  // available, first look at the "targets.proto" to see how targets are configured.
    18  //
    19  // The interface targets.Targets is what actually provides the ability to list
    20  // targets (or, more generally, resources), and is what is used by probers. The
    21  // targets.New constructor provides a specific Target implementation that wraps
    22  // filtering functionality over a core target lister.
    23  package targets
    24  
    25  import (
    26  	"errors"
    27  	"fmt"
    28  	"net"
    29  	"regexp"
    30  	"strings"
    31  	"sync"
    32  	"time"
    33  
    34  	"github.com/golang/protobuf/proto"
    35  	"github.com/google/cloudprober/config/runconfig"
    36  	"github.com/google/cloudprober/logger"
    37  	rdsclient "github.com/google/cloudprober/rds/client"
    38  	rdsclientpb "github.com/google/cloudprober/rds/client/proto"
    39  	rdspb "github.com/google/cloudprober/rds/proto"
    40  	"github.com/google/cloudprober/targets/endpoint"
    41  	"github.com/google/cloudprober/targets/file"
    42  	"github.com/google/cloudprober/targets/gce"
    43  	targetspb "github.com/google/cloudprober/targets/proto"
    44  	dnsRes "github.com/google/cloudprober/targets/resolver"
    45  )
    46  
    47  // globalResolver is a singleton DNS resolver that is used as the default
    48  // resolver by targets. It is a singleton because dnsRes.Resolver provides a
    49  // cache layer that is best shared by all probes.
    50  var (
    51  	globalResolver *dnsRes.Resolver
    52  )
    53  
    54  // Targets must have refreshed this much time after the lameduck for them to
    55  // become valid again. This is to take care of the following race between
    56  // targets refresh and lameduck creation:
    57  //   Targets are refreshed few seconds after lameduck, and are deleted few more
    58  //   seconds after that. If there is no min lameduck duration, we'll end up
    59  //   ignoring lameduck in this case. With min lameduck duration, targets will
    60  //   need to be refreshed few minutes after being lameducked.
    61  const minLameduckDuration = 5 * time.Minute
    62  
    63  // extensionMap is a map of targets-types extensions. While creating new
    64  // targets, if it's not a known targets type, we lookup this map to check if
    65  // it matches with a registered extension.
    66  var (
    67  	extensionMap   = make(map[int]func(interface{}, *logger.Logger) (Targets, error))
    68  	extensionMapMu sync.Mutex
    69  )
    70  
    71  var (
    72  	sharedTargets   = make(map[string]Targets)
    73  	sharedTargetsMu sync.RWMutex
    74  )
    75  
    76  // Targets are able to list and resolve targets with their List and Resolve
    77  // methods.  A single instance of Targets represents a specific listing method
    78  // --- if multiple sets of resources need to be listed/resolved, a separate
    79  // instance of Targets will be needed.
    80  type Targets interface {
    81  	endpoint.Lister
    82  	resolver
    83  }
    84  
    85  type resolver interface {
    86  	// Resolve, given a target and IP Version will return the IP address for that
    87  	// target.
    88  	Resolve(name string, ipVer int) (net.IP, error)
    89  }
    90  
    91  // staticLister is a simple list of hosts that does not change. This corresponds
    92  // to the "host_names" type in cloudprober/targets/targets.proto.  For
    93  // example, one could have a probe whose targets are `host_names:
    94  // "www.google.com, 8.8.8.8"`.
    95  type staticLister struct {
    96  	list []endpoint.Endpoint
    97  }
    98  
    99  // List returns a copy of its static host list.
   100  func (sl *staticLister) ListEndpoints() []endpoint.Endpoint {
   101  	return sl.list
   102  }
   103  
   104  // A dummy target object, for external probes that don't have any
   105  // "proper" targets.
   106  type dummy struct {
   107  }
   108  
   109  // List for dummy targets returns one empty string.  This is to ensure
   110  // that any iteration over targets will at least be executed once.
   111  func (d *dummy) ListEndpoints() []endpoint.Endpoint {
   112  	return []endpoint.Endpoint{endpoint.Endpoint{Name: ""}}
   113  }
   114  
   115  // Resolve will just return an unspecified IP address.  This can be
   116  // easily checked by using `func (IP) IsUnspecified`.
   117  func (d *dummy) Resolve(name string, ipVer int) (net.IP, error) {
   118  	return net.IPv6unspecified, nil
   119  }
   120  
   121  // targets is the main implementation of the Targets interface, composed of a core
   122  // lister and resolver. Essentially it provides a wrapper around the core lister,
   123  // providing various filtering options. Currently filtering by regex and lameduck
   124  // is supported.
   125  type targets struct {
   126  	lister   endpoint.Lister
   127  	resolver resolver
   128  	re       *regexp.Regexp
   129  	ldLister endpoint.Lister
   130  	l        *logger.Logger
   131  }
   132  
   133  // Resolve either resolves a target using the core resolver, or returns an error
   134  // if no core resolver was provided. Currently all target types provide a
   135  // resolver.
   136  func (t *targets) Resolve(name string, ipVer int) (net.IP, error) {
   137  	if t.resolver == nil {
   138  		return nil, errors.New("no Resolver provided by this target type")
   139  	}
   140  	return t.resolver.Resolve(name, ipVer)
   141  }
   142  
   143  func (t *targets) lameduckMap() map[string]endpoint.Endpoint {
   144  	lameDuckMap := make(map[string]endpoint.Endpoint)
   145  	if t.ldLister != nil {
   146  		lameDucksList := t.ldLister.ListEndpoints()
   147  		for _, i := range lameDucksList {
   148  			lameDuckMap[i.Name] = i
   149  		}
   150  	}
   151  	return lameDuckMap
   152  }
   153  
   154  func (t *targets) includeInResult(ep endpoint.Endpoint, ldMap map[string]endpoint.Endpoint) bool {
   155  	// Filter by regexp
   156  	if t.re != nil && !t.re.MatchString(ep.Name) {
   157  		return false
   158  	}
   159  
   160  	if len(ldMap) == 0 {
   161  		return true
   162  	}
   163  
   164  	// If there is a lameduck entry and it was updated after the target entry,
   165  	// don't include it in result.
   166  	ldEP, ok := ldMap[ep.Name]
   167  	if ok {
   168  		// If lameduck endpoint or target endpoint don't have last-updated set,
   169  		// skip checking which one is newer.
   170  		if ldEP.LastUpdated.IsZero() || ep.LastUpdated.IsZero() {
   171  			return false
   172  		}
   173  		return ep.LastUpdated.After(ldEP.LastUpdated.Add(minLameduckDuration))
   174  	}
   175  
   176  	return true
   177  }
   178  
   179  // ListEndpoints returns the list of target endpoints, where each endpoint
   180  // consists of a name and associated metadata like port and target labels.
   181  //
   182  // It gets the list of targets from the configured targets type, filters them
   183  // by the configured regex, excludes lame ducks and returns the resultant list.
   184  //
   185  // This method should be concurrency safe as it doesn't modify any shared
   186  // variables and doesn't rely on multiple accesses to same variable being
   187  // consistent.
   188  //
   189  // Note that some targets, for example static hosts, may not have any
   190  // associated metadata at all, those endpoint fields are left empty in that
   191  // case.
   192  func (t *targets) ListEndpoints() []endpoint.Endpoint {
   193  	if t.lister == nil {
   194  		t.l.Error("List(): Lister t.lister is nil")
   195  		return []endpoint.Endpoint{}
   196  	}
   197  
   198  	var list []endpoint.Endpoint
   199  
   200  	list = t.lister.ListEndpoints()
   201  
   202  	ldMap := t.lameduckMap()
   203  	if t.re != nil || len(ldMap) != 0 {
   204  		var result []endpoint.Endpoint
   205  		for _, ep := range list {
   206  			if t.includeInResult(ep, ldMap) {
   207  				result = append(result, ep)
   208  			}
   209  		}
   210  		list = result
   211  	}
   212  
   213  	return list
   214  }
   215  
   216  // baseTargets constructs a targets instance with no lister or resolver. It
   217  // provides essentially everything that the targets type wraps over its lister.
   218  func baseTargets(targetsDef *targetspb.TargetsDef, ldLister endpoint.Lister, l *logger.Logger) (*targets, error) {
   219  	if l == nil {
   220  		l = &logger.Logger{}
   221  	}
   222  
   223  	tgts := &targets{
   224  		l:        l,
   225  		resolver: globalResolver,
   226  		ldLister: ldLister,
   227  	}
   228  
   229  	if targetsDef == nil {
   230  		return tgts, nil
   231  	}
   232  
   233  	if targetsDef.GetRegex() != "" {
   234  		var err error
   235  		if tgts.re, err = regexp.Compile(targetsDef.GetRegex()); err != nil {
   236  			return nil, fmt.Errorf("invalid targets regex: %s. Err: %v", targetsDef.GetRegex(), err)
   237  		}
   238  	}
   239  
   240  	return tgts, nil
   241  }
   242  
   243  // StaticTargets returns a basic "targets" object (implementing the Targets
   244  // interface) from a comma-separated list of hosts. This function panics if
   245  // "hosts" string is not valid. It is mainly used by tests to quickly get a
   246  // targets.Targets object from a list of hosts.
   247  func StaticTargets(hosts string) Targets {
   248  	t, err := staticTargets(hosts)
   249  	if err != nil {
   250  		panic(err)
   251  	}
   252  	return t
   253  }
   254  
   255  // RDSClientConf converts RDS targets into RDS client configuration.
   256  func RDSClientConf(pb *targetspb.RDSTargets, globalOpts *targetspb.GlobalTargetsOptions, l *logger.Logger) (rdsclient.ListResourcesFunc, *rdsclientpb.ClientConf, error) {
   257  	var listResourcesFunc rdsclient.ListResourcesFunc
   258  
   259  	// Intialize server address with global options.
   260  	serverOpts := globalOpts.GetRdsServerOptions()
   261  	if serverOpts == nil && globalOpts.GetRdsServerAddress() != "" {
   262  		l.Warning("rds_server_address is now deprecated. please use rds_server_options instead")
   263  		serverOpts = &rdsclientpb.ClientConf_ServerOptions{
   264  			ServerAddress: proto.String(globalOpts.GetRdsServerAddress()),
   265  		}
   266  	}
   267  
   268  	// If targets specific rds_server_options is given, use that.
   269  	if pb.GetRdsServerOptions() != nil {
   270  		serverOpts = pb.GetRdsServerOptions()
   271  	}
   272  
   273  	// If rds_server_address is not given in both, local options and in global
   274  	// options, look for the locally running RDS server.
   275  	if serverOpts == nil {
   276  		localRDSServer := runconfig.LocalRDSServer()
   277  		if localRDSServer == nil {
   278  			return nil, nil, fmt.Errorf("rds_server_address not given and found no local RDS server")
   279  		}
   280  		listResourcesFunc = localRDSServer.ListResources
   281  	}
   282  
   283  	toks := strings.SplitN(pb.GetResourcePath(), "://", 2)
   284  	if len(toks) != 2 || toks[0] == "" {
   285  		return nil, nil, fmt.Errorf("provider not specified in the resource_path: %s", pb.GetResourcePath())
   286  	}
   287  	provider := toks[0]
   288  
   289  	return listResourcesFunc, &rdsclientpb.ClientConf{
   290  		ServerOptions: serverOpts,
   291  		Request: &rdspb.ListResourcesRequest{
   292  			Provider:     &provider,
   293  			ResourcePath: &toks[1],
   294  			Filter:       pb.GetFilter(),
   295  			IpConfig:     pb.GetIpConfig(),
   296  		},
   297  	}, nil
   298  }
   299  
   300  // New returns an instance of Targets as defined by a Targets protobuf (and a
   301  // GlobalTargetsOptions protobuf). The Targets instance returned will filter a
   302  // core target lister (i.e. static host-list, GCE instances, GCE forwarding
   303  // rules, RTC targets) by an optional regex or with the lameduck mechanism.
   304  //
   305  // All information related to creating the Target instance will be logged to
   306  // globalLogger. The logger "l" will be given to the new Targets instance
   307  // for future logging. If "l" is not provided, a default instance will be given.
   308  //
   309  // See cloudprober/targets/targets.proto for more information on the possible
   310  // configurations of Targets.
   311  func New(targetsDef *targetspb.TargetsDef, ldLister endpoint.Lister, globalOpts *targetspb.GlobalTargetsOptions, globalLogger, l *logger.Logger) (Targets, error) {
   312  	t, err := baseTargets(targetsDef, ldLister, l)
   313  	if err != nil {
   314  		globalLogger.Error("Unable to produce the base target lister")
   315  		return nil, fmt.Errorf("targets.New(): Error making baseTargets: %v", err)
   316  	}
   317  
   318  	switch targetsDef.Type.(type) {
   319  	case *targetspb.TargetsDef_HostNames:
   320  		st, err := staticTargets(targetsDef.GetHostNames())
   321  		if err != nil {
   322  			return nil, fmt.Errorf("targets.New(): error creating targets from host_names: %v", err)
   323  		}
   324  		t.lister, t.resolver = st, st
   325  
   326  	case *targetspb.TargetsDef_SharedTargets:
   327  		sharedTargetsMu.RLock()
   328  		defer sharedTargetsMu.RUnlock()
   329  		st := sharedTargets[targetsDef.GetSharedTargets()]
   330  		if st == nil {
   331  			return nil, fmt.Errorf("targets.New(): Shared targets %s are not defined", targetsDef.GetSharedTargets())
   332  		}
   333  		t.lister, t.resolver = st, st
   334  
   335  	case *targetspb.TargetsDef_GceTargets:
   336  		s, err := gce.New(targetsDef.GetGceTargets(), globalOpts.GetGlobalGceTargetsOptions(), globalResolver, globalLogger)
   337  		if err != nil {
   338  			return nil, fmt.Errorf("targets.New(): error creating GCE targets: %v", err)
   339  		}
   340  		t.lister, t.resolver = s, s
   341  
   342  	case *targetspb.TargetsDef_RdsTargets:
   343  		listResourcesFunc, clientConf, err := RDSClientConf(targetsDef.GetRdsTargets(), globalOpts, l)
   344  		if err != nil {
   345  			return nil, fmt.Errorf("target.New(): error creating RDS client: %v", err)
   346  		}
   347  
   348  		client, err := rdsclient.New(clientConf, listResourcesFunc, l)
   349  		if err != nil {
   350  			return nil, fmt.Errorf("target.New(): error creating RDS client: %v", err)
   351  		}
   352  
   353  		t.lister, t.resolver = client, client
   354  
   355  	case *targetspb.TargetsDef_FileTargets:
   356  		ft, err := file.New(targetsDef.GetFileTargets(), globalResolver, l)
   357  		if err != nil {
   358  			return nil, fmt.Errorf("target.New(): %v", err)
   359  		}
   360  		t.lister, t.resolver = ft, ft
   361  
   362  	case *targetspb.TargetsDef_DummyTargets:
   363  		dummy := &dummy{}
   364  		t.lister, t.resolver = dummy, dummy
   365  
   366  	default:
   367  		extT, err := getExtensionTargets(targetsDef, t.l)
   368  		if err != nil {
   369  			return nil, fmt.Errorf("targets.New(): %v", err)
   370  		}
   371  		t.lister, t.resolver = extT, extT
   372  	}
   373  
   374  	return t, nil
   375  }
   376  
   377  func getExtensionTargets(pb *targetspb.TargetsDef, l *logger.Logger) (Targets, error) {
   378  	extensions, err := proto.ExtensionDescs(pb)
   379  	if err != nil {
   380  		return nil, fmt.Errorf("error getting extensions from the target config (%s): %v", pb.String(), err)
   381  	}
   382  	if len(extensions) != 1 {
   383  		return nil, fmt.Errorf("there should be exactly one extension in the targets config (%s), got %d extensions", pb.String(), len(extensions))
   384  	}
   385  	desc := extensions[0]
   386  	value, err := proto.GetExtension(pb, desc)
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  	l.Infof("Extension field: %d, value: %v", desc.Field, value)
   391  	extensionMapMu.Lock()
   392  	defer extensionMapMu.Unlock()
   393  	newTargetsFunc, ok := extensionMap[int(desc.Field)]
   394  	if !ok {
   395  		return nil, fmt.Errorf("no targets type registered for the extension: %d", desc.Field)
   396  	}
   397  	return newTargetsFunc(value, l)
   398  }
   399  
   400  // RegisterTargetsType registers a new targets type. New targets types are
   401  // integrated with the config subsystem using the protobuf extensions.
   402  //
   403  // TODO(manugarg): Add a full example of using extensions.
   404  func RegisterTargetsType(extensionFieldNo int, newTargetsFunc func(interface{}, *logger.Logger) (Targets, error)) {
   405  	extensionMapMu.Lock()
   406  	defer extensionMapMu.Unlock()
   407  	extensionMap[extensionFieldNo] = newTargetsFunc
   408  }
   409  
   410  // SetSharedTargets adds given targets to an internal map. These targets can
   411  // then be referred by multiple probes through "shared_targets" option.
   412  func SetSharedTargets(name string, tgts Targets) {
   413  	sharedTargetsMu.Lock()
   414  	defer sharedTargetsMu.Unlock()
   415  	sharedTargets[name] = tgts
   416  }
   417  
   418  // init initializes the package by creating a new global resolver.
   419  func init() {
   420  	globalResolver = dnsRes.New()
   421  }