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 }