github.com/manicqin/nomad@v0.9.5/command/agent/consul/client.go (about) 1 package consul 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "net/url" 8 "reflect" 9 "strconv" 10 "strings" 11 "sync" 12 "sync/atomic" 13 "time" 14 15 metrics "github.com/armon/go-metrics" 16 log "github.com/hashicorp/go-hclog" 17 18 "github.com/hashicorp/consul/api" 19 "github.com/hashicorp/nomad/helper" 20 "github.com/hashicorp/nomad/nomad/structs" 21 "github.com/hashicorp/nomad/plugins/drivers" 22 ) 23 24 const ( 25 // nomadServicePrefix is the prefix that scopes all Nomad registered 26 // services (both agent and task entries). 27 nomadServicePrefix = "_nomad" 28 29 // nomadTaskPrefix is the prefix that scopes Nomad registered services 30 // for tasks. 31 nomadTaskPrefix = nomadServicePrefix + "-task-" 32 33 // nomadCheckPrefix is the prefix that scopes Nomad registered checks for 34 // services. 35 nomadCheckPrefix = nomadServicePrefix + "-check-" 36 37 // defaultRetryInterval is how quickly to retry syncing services and 38 // checks to Consul when an error occurs. Will backoff up to a max. 39 defaultRetryInterval = time.Second 40 41 // defaultMaxRetryInterval is the default max retry interval. 42 defaultMaxRetryInterval = 30 * time.Second 43 44 // defaultPeriodicalInterval is the interval at which the service 45 // client reconciles state between the desired services and checks and 46 // what's actually registered in Consul. This is done at an interval, 47 // rather than being purely edge triggered, to handle the case that the 48 // Consul agent's state may change underneath us 49 defaultPeriodicInterval = 30 * time.Second 50 51 // ttlCheckBuffer is the time interval that Nomad can take to report Consul 52 // the check result 53 ttlCheckBuffer = 31 * time.Second 54 55 // defaultShutdownWait is how long Shutdown() should block waiting for 56 // enqueued operations to sync to Consul by default. 57 defaultShutdownWait = time.Minute 58 59 // DefaultQueryWaitDuration is the max duration the Consul Agent will 60 // spend waiting for a response from a Consul Query. 61 DefaultQueryWaitDuration = 2 * time.Second 62 63 // ServiceTagHTTP is the tag assigned to HTTP services 64 ServiceTagHTTP = "http" 65 66 // ServiceTagRPC is the tag assigned to RPC services 67 ServiceTagRPC = "rpc" 68 69 // ServiceTagSerf is the tag assigned to Serf services 70 ServiceTagSerf = "serf" 71 72 // deregisterProbationPeriod is the initialization period where 73 // services registered in Consul but not in Nomad don't get deregistered, 74 // to allow for nomad restoring tasks 75 deregisterProbationPeriod = time.Minute 76 ) 77 78 // CatalogAPI is the consul/api.Catalog API used by Nomad. 79 type CatalogAPI interface { 80 Datacenters() ([]string, error) 81 Service(service, tag string, q *api.QueryOptions) ([]*api.CatalogService, *api.QueryMeta, error) 82 } 83 84 // AgentAPI is the consul/api.Agent API used by Nomad. 85 type AgentAPI interface { 86 Services() (map[string]*api.AgentService, error) 87 Checks() (map[string]*api.AgentCheck, error) 88 CheckRegister(check *api.AgentCheckRegistration) error 89 CheckDeregister(checkID string) error 90 Self() (map[string]map[string]interface{}, error) 91 ServiceRegister(service *api.AgentServiceRegistration) error 92 ServiceDeregister(serviceID string) error 93 UpdateTTL(id, output, status string) error 94 } 95 96 func agentServiceUpdateRequired(reg *api.AgentServiceRegistration, svc *api.AgentService) bool { 97 return !(reg.Kind == svc.Kind && 98 reg.ID == svc.ID && 99 reg.Port == svc.Port && 100 reg.Address == svc.Address && 101 reg.Name == svc.Service && 102 reflect.DeepEqual(reg.Tags, svc.Tags)) 103 } 104 105 // operations are submitted to the main loop via commit() for synchronizing 106 // with Consul. 107 type operations struct { 108 regServices []*api.AgentServiceRegistration 109 regChecks []*api.AgentCheckRegistration 110 deregServices []string 111 deregChecks []string 112 } 113 114 // AllocRegistration holds the status of services registered for a particular 115 // allocations by task. 116 type AllocRegistration struct { 117 // Tasks maps the name of a task to its registered services and checks 118 Tasks map[string]*ServiceRegistrations 119 } 120 121 func (a *AllocRegistration) copy() *AllocRegistration { 122 c := &AllocRegistration{ 123 Tasks: make(map[string]*ServiceRegistrations, len(a.Tasks)), 124 } 125 126 for k, v := range a.Tasks { 127 c.Tasks[k] = v.copy() 128 } 129 130 return c 131 } 132 133 // NumServices returns the number of registered services 134 func (a *AllocRegistration) NumServices() int { 135 if a == nil { 136 return 0 137 } 138 139 total := 0 140 for _, treg := range a.Tasks { 141 for _, sreg := range treg.Services { 142 if sreg.Service != nil { 143 total++ 144 } 145 } 146 } 147 148 return total 149 } 150 151 // NumChecks returns the number of registered checks 152 func (a *AllocRegistration) NumChecks() int { 153 if a == nil { 154 return 0 155 } 156 157 total := 0 158 for _, treg := range a.Tasks { 159 for _, sreg := range treg.Services { 160 total += len(sreg.Checks) 161 } 162 } 163 164 return total 165 } 166 167 // ServiceRegistrations holds the status of services registered for a particular 168 // task or task group. 169 type ServiceRegistrations struct { 170 Services map[string]*ServiceRegistration 171 } 172 173 func (t *ServiceRegistrations) copy() *ServiceRegistrations { 174 c := &ServiceRegistrations{ 175 Services: make(map[string]*ServiceRegistration, len(t.Services)), 176 } 177 178 for k, v := range t.Services { 179 c.Services[k] = v.copy() 180 } 181 182 return c 183 } 184 185 // ServiceRegistration holds the status of a registered Consul Service and its 186 // Checks. 187 type ServiceRegistration struct { 188 // serviceID and checkIDs are internal fields that track just the IDs of the 189 // services/checks registered in Consul. It is used to materialize the other 190 // fields when queried. 191 serviceID string 192 checkIDs map[string]struct{} 193 194 // Service is the AgentService registered in Consul. 195 Service *api.AgentService 196 197 // Checks is the status of the registered checks. 198 Checks []*api.AgentCheck 199 } 200 201 func (s *ServiceRegistration) copy() *ServiceRegistration { 202 // Copy does not copy the external fields but only the internal fields. This 203 // is so that the caller of AllocRegistrations can not access the internal 204 // fields and that method uses these fields to populate the external fields. 205 return &ServiceRegistration{ 206 serviceID: s.serviceID, 207 checkIDs: helper.CopyMapStringStruct(s.checkIDs), 208 } 209 } 210 211 // ServiceClient handles task and agent service registration with Consul. 212 type ServiceClient struct { 213 client AgentAPI 214 logger log.Logger 215 retryInterval time.Duration 216 maxRetryInterval time.Duration 217 periodicInterval time.Duration 218 219 // exitCh is closed when the main Run loop exits 220 exitCh chan struct{} 221 222 // shutdownCh is closed when the client should shutdown 223 shutdownCh chan struct{} 224 225 // shutdownWait is how long Shutdown() blocks waiting for the final 226 // sync() to finish. Defaults to defaultShutdownWait 227 shutdownWait time.Duration 228 229 opCh chan *operations 230 231 services map[string]*api.AgentServiceRegistration 232 checks map[string]*api.AgentCheckRegistration 233 234 explicitlyDeregisteredServices map[string]bool 235 explicitlyDeregisteredChecks map[string]bool 236 237 // allocRegistrations stores the services and checks that are registered 238 // with Consul by allocation ID. 239 allocRegistrations map[string]*AllocRegistration 240 allocRegistrationsLock sync.RWMutex 241 242 // agent services and checks record entries for the agent itself which 243 // should be removed on shutdown 244 agentServices map[string]struct{} 245 agentChecks map[string]struct{} 246 agentLock sync.Mutex 247 248 // seen is 1 if Consul has ever been seen; otherwise 0. Accessed with 249 // atomics. 250 seen int32 251 252 // deregisterProbationExpiry is the time before which consul sync shouldn't deregister 253 // unknown services. 254 // Used to mitigate risk of deleting restored services upon client restart. 255 deregisterProbationExpiry time.Time 256 257 // checkWatcher restarts checks that are unhealthy. 258 checkWatcher *checkWatcher 259 260 // isClientAgent specifies whether this Consul client is being used 261 // by a Nomad client. 262 isClientAgent bool 263 } 264 265 // NewServiceClient creates a new Consul ServiceClient from an existing Consul API 266 // Client, logger and takes whether the client is being used by a Nomad Client agent. 267 // When being used by a Nomad client, this Consul client reconciles all services and 268 // checks created by Nomad on behalf of running tasks. 269 func NewServiceClient(consulClient AgentAPI, logger log.Logger, isNomadClient bool) *ServiceClient { 270 logger = logger.ResetNamed("consul.sync") 271 return &ServiceClient{ 272 client: consulClient, 273 logger: logger, 274 retryInterval: defaultRetryInterval, 275 maxRetryInterval: defaultMaxRetryInterval, 276 periodicInterval: defaultPeriodicInterval, 277 exitCh: make(chan struct{}), 278 shutdownCh: make(chan struct{}), 279 shutdownWait: defaultShutdownWait, 280 opCh: make(chan *operations, 8), 281 services: make(map[string]*api.AgentServiceRegistration), 282 checks: make(map[string]*api.AgentCheckRegistration), 283 explicitlyDeregisteredServices: make(map[string]bool), 284 explicitlyDeregisteredChecks: make(map[string]bool), 285 allocRegistrations: make(map[string]*AllocRegistration), 286 agentServices: make(map[string]struct{}), 287 agentChecks: make(map[string]struct{}), 288 checkWatcher: newCheckWatcher(logger, consulClient), 289 isClientAgent: isNomadClient, 290 deregisterProbationExpiry: time.Now().Add(deregisterProbationPeriod), 291 } 292 } 293 294 // seen is used by markSeen and hasSeen 295 const seen = 1 296 297 // markSeen marks Consul as having been seen (meaning at least one operation 298 // has succeeded). 299 func (c *ServiceClient) markSeen() { 300 atomic.StoreInt32(&c.seen, seen) 301 } 302 303 // hasSeen returns true if any Consul operation has ever succeeded. Useful to 304 // squelch errors if Consul isn't running. 305 func (c *ServiceClient) hasSeen() bool { 306 return atomic.LoadInt32(&c.seen) == seen 307 } 308 309 // Run the Consul main loop which retries operations against Consul. It should 310 // be called exactly once. 311 func (c *ServiceClient) Run() { 312 defer close(c.exitCh) 313 314 ctx, cancel := context.WithCancel(context.Background()) 315 defer cancel() 316 317 // init will be closed when Consul has been contacted 318 init := make(chan struct{}) 319 go checkConsulTLSSkipVerify(ctx, c.logger, c.client, init) 320 321 // Process operations while waiting for initial contact with Consul but 322 // do not sync until contact has been made. 323 INIT: 324 for { 325 select { 326 case <-init: 327 c.markSeen() 328 break INIT 329 case <-c.shutdownCh: 330 return 331 case ops := <-c.opCh: 332 c.merge(ops) 333 } 334 } 335 c.logger.Trace("able to contact Consul") 336 337 // Block until contact with Consul has been established 338 // Start checkWatcher 339 go c.checkWatcher.Run(ctx) 340 341 // Always immediately sync to reconcile Nomad and Consul's state 342 retryTimer := time.NewTimer(0) 343 344 failures := 0 345 for { 346 select { 347 case <-retryTimer.C: 348 case <-c.shutdownCh: 349 // Cancel check watcher but sync one last time 350 cancel() 351 case ops := <-c.opCh: 352 c.merge(ops) 353 } 354 355 if err := c.sync(); err != nil { 356 if failures == 0 { 357 // Log on the first failure 358 c.logger.Warn("failed to update services in Consul", "error", err) 359 } else if failures%10 == 0 { 360 // Log every 10th consecutive failure 361 c.logger.Error("still unable to update services in Consul", "failures", failures, "error", err) 362 } 363 364 failures++ 365 if !retryTimer.Stop() { 366 // Timer already expired, since the timer may 367 // or may not have been read in the select{} 368 // above, conditionally receive on it 369 select { 370 case <-retryTimer.C: 371 default: 372 } 373 } 374 backoff := c.retryInterval * time.Duration(failures) 375 if backoff > c.maxRetryInterval { 376 backoff = c.maxRetryInterval 377 } 378 retryTimer.Reset(backoff) 379 } else { 380 if failures > 0 { 381 c.logger.Info("successfully updated services in Consul") 382 failures = 0 383 } 384 385 // on successful sync, clear deregistered consul entities 386 c.clearExplicitlyDeregistered() 387 388 // Reset timer to periodic interval to periodically 389 // reconile with Consul 390 if !retryTimer.Stop() { 391 select { 392 case <-retryTimer.C: 393 default: 394 } 395 } 396 retryTimer.Reset(c.periodicInterval) 397 } 398 399 select { 400 case <-c.shutdownCh: 401 // Exit only after sync'ing all outstanding operations 402 if len(c.opCh) > 0 { 403 for len(c.opCh) > 0 { 404 c.merge(<-c.opCh) 405 } 406 continue 407 } 408 return 409 default: 410 } 411 412 } 413 } 414 415 // commit operations unless already shutting down. 416 func (c *ServiceClient) commit(ops *operations) { 417 select { 418 case c.opCh <- ops: 419 case <-c.shutdownCh: 420 } 421 } 422 423 func (c *ServiceClient) clearExplicitlyDeregistered() { 424 c.explicitlyDeregisteredServices = map[string]bool{} 425 c.explicitlyDeregisteredChecks = map[string]bool{} 426 } 427 428 // merge registrations into state map prior to sync'ing with Consul 429 func (c *ServiceClient) merge(ops *operations) { 430 for _, s := range ops.regServices { 431 c.services[s.ID] = s 432 } 433 for _, check := range ops.regChecks { 434 c.checks[check.ID] = check 435 } 436 for _, sid := range ops.deregServices { 437 delete(c.services, sid) 438 c.explicitlyDeregisteredServices[sid] = true 439 } 440 for _, cid := range ops.deregChecks { 441 delete(c.checks, cid) 442 c.explicitlyDeregisteredChecks[cid] = true 443 } 444 metrics.SetGauge([]string{"client", "consul", "services"}, float32(len(c.services))) 445 metrics.SetGauge([]string{"client", "consul", "checks"}, float32(len(c.checks))) 446 } 447 448 // sync enqueued operations. 449 func (c *ServiceClient) sync() error { 450 sreg, creg, sdereg, cdereg := 0, 0, 0, 0 451 452 consulServices, err := c.client.Services() 453 if err != nil { 454 metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1) 455 return fmt.Errorf("error querying Consul services: %v", err) 456 } 457 458 consulChecks, err := c.client.Checks() 459 if err != nil { 460 metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1) 461 return fmt.Errorf("error querying Consul checks: %v", err) 462 } 463 464 inProbation := time.Now().Before(c.deregisterProbationExpiry) 465 466 // Remove Nomad services in Consul but unknown locally 467 for id := range consulServices { 468 if _, ok := c.services[id]; ok { 469 // Known service, skip 470 continue 471 } 472 473 // Ignore if this is not a Nomad managed service. Also ignore 474 // Nomad managed services if this is not a client agent. 475 // This is to prevent server agents from removing services 476 // registered by client agents 477 if !isNomadService(id) || !c.isClientAgent { 478 // Not managed by Nomad, skip 479 continue 480 } 481 482 // Ignore unknown services during probation 483 if inProbation && !c.explicitlyDeregisteredServices[id] { 484 continue 485 } 486 487 // Ignore if this is a service for a Nomad managed sidecar proxy. 488 if isNomadSidecar(id, c.services) { 489 continue 490 } 491 492 // Unknown Nomad managed service; kill 493 if err := c.client.ServiceDeregister(id); err != nil { 494 if isOldNomadService(id) { 495 // Don't hard-fail on old entries. See #3620 496 continue 497 } 498 499 metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1) 500 return err 501 } 502 sdereg++ 503 metrics.IncrCounter([]string{"client", "consul", "service_deregistrations"}, 1) 504 } 505 506 // Add Nomad services missing from Consul, or where the service has been updated. 507 for id, locals := range c.services { 508 existingSvc, ok := consulServices[id] 509 510 if ok { 511 // There is an existing registration of this service in Consul, so here 512 // we validate to see if the service has been invalidated to see if it 513 // should be updated. 514 if !agentServiceUpdateRequired(locals, existingSvc) { 515 // No Need to update services that have not changed 516 continue 517 } 518 } 519 520 if err = c.client.ServiceRegister(locals); err != nil { 521 metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1) 522 return err 523 } 524 sreg++ 525 metrics.IncrCounter([]string{"client", "consul", "service_registrations"}, 1) 526 } 527 528 // Remove Nomad checks in Consul but unknown locally 529 for id, check := range consulChecks { 530 if _, ok := c.checks[id]; ok { 531 // Known check, leave it 532 continue 533 } 534 535 // Ignore if this is not a Nomad managed check. Also ignore 536 // Nomad managed checks if this is not a client agent. 537 // This is to prevent server agents from removing checks 538 // registered by client agents 539 if !isNomadService(check.ServiceID) || !c.isClientAgent || !isNomadCheck(check.CheckID) { 540 // Service not managed by Nomad, skip 541 continue 542 } 543 544 // Ignore unknown services during probation 545 if inProbation && !c.explicitlyDeregisteredChecks[id] { 546 continue 547 } 548 549 // Ignore if this is a check for a Nomad managed sidecar proxy. 550 if isNomadSidecar(check.ServiceID, c.services) { 551 continue 552 } 553 554 // Unknown Nomad managed check; remove 555 if err := c.client.CheckDeregister(id); err != nil { 556 if isOldNomadService(check.ServiceID) { 557 // Don't hard-fail on old entries. 558 continue 559 } 560 561 metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1) 562 return err 563 } 564 cdereg++ 565 metrics.IncrCounter([]string{"client", "consul", "check_deregistrations"}, 1) 566 } 567 568 // Add Nomad checks missing from Consul 569 for id, check := range c.checks { 570 if _, ok := consulChecks[id]; ok { 571 // Already in Consul; skipping 572 continue 573 } 574 575 if err := c.client.CheckRegister(check); err != nil { 576 metrics.IncrCounter([]string{"client", "consul", "sync_failure"}, 1) 577 return err 578 } 579 creg++ 580 metrics.IncrCounter([]string{"client", "consul", "check_registrations"}, 1) 581 } 582 583 // Only log if something was actually synced 584 if sreg > 0 || sdereg > 0 || creg > 0 || cdereg > 0 { 585 c.logger.Debug("sync complete", "registered_services", sreg, "deregistered_services", sdereg, 586 "registered_checks", creg, "deregistered_checks", cdereg) 587 } 588 return nil 589 } 590 591 // RegisterAgent registers Nomad agents (client or server). The 592 // Service.PortLabel should be a literal port to be parsed with SplitHostPort. 593 // Script checks are not supported and will return an error. Registration is 594 // asynchronous. 595 // 596 // Agents will be deregistered when Shutdown is called. 597 func (c *ServiceClient) RegisterAgent(role string, services []*structs.Service) error { 598 ops := operations{} 599 600 for _, service := range services { 601 id := makeAgentServiceID(role, service) 602 603 // Unlike tasks, agents don't use port labels. Agent ports are 604 // stored directly in the PortLabel. 605 host, rawport, err := net.SplitHostPort(service.PortLabel) 606 if err != nil { 607 return fmt.Errorf("error parsing port label %q from service %q: %v", service.PortLabel, service.Name, err) 608 } 609 port, err := strconv.Atoi(rawport) 610 if err != nil { 611 return fmt.Errorf("error parsing port %q from service %q: %v", rawport, service.Name, err) 612 } 613 serviceReg := &api.AgentServiceRegistration{ 614 ID: id, 615 Name: service.Name, 616 Tags: service.Tags, 617 Address: host, 618 Port: port, 619 // This enables the consul UI to show that Nomad registered this service 620 Meta: map[string]string{ 621 "external-source": "nomad", 622 }, 623 } 624 ops.regServices = append(ops.regServices, serviceReg) 625 626 for _, check := range service.Checks { 627 checkID := MakeCheckID(id, check) 628 if check.Type == structs.ServiceCheckScript { 629 return fmt.Errorf("service %q contains invalid check: agent checks do not support scripts", service.Name) 630 } 631 checkHost, checkPort := serviceReg.Address, serviceReg.Port 632 if check.PortLabel != "" { 633 // Unlike tasks, agents don't use port labels. Agent ports are 634 // stored directly in the PortLabel. 635 host, rawport, err := net.SplitHostPort(check.PortLabel) 636 if err != nil { 637 return fmt.Errorf("error parsing port label %q from check %q: %v", service.PortLabel, check.Name, err) 638 } 639 port, err := strconv.Atoi(rawport) 640 if err != nil { 641 return fmt.Errorf("error parsing port %q from check %q: %v", rawport, check.Name, err) 642 } 643 checkHost, checkPort = host, port 644 } 645 checkReg, err := createCheckReg(id, checkID, check, checkHost, checkPort) 646 if err != nil { 647 return fmt.Errorf("failed to add check %q: %v", check.Name, err) 648 } 649 ops.regChecks = append(ops.regChecks, checkReg) 650 } 651 } 652 653 // Don't bother committing agent checks if we're already shutting down 654 c.agentLock.Lock() 655 defer c.agentLock.Unlock() 656 select { 657 case <-c.shutdownCh: 658 return nil 659 default: 660 } 661 662 // Now add them to the registration queue 663 c.commit(&ops) 664 665 // Record IDs for deregistering on shutdown 666 for _, id := range ops.regServices { 667 c.agentServices[id.ID] = struct{}{} 668 } 669 for _, id := range ops.regChecks { 670 c.agentChecks[id.ID] = struct{}{} 671 } 672 return nil 673 } 674 675 // serviceRegs creates service registrations, check registrations, and script 676 // checks from a service. It returns a service registration object with the 677 // service and check IDs populated. 678 func (c *ServiceClient) serviceRegs(ops *operations, service *structs.Service, workload *WorkloadServices) ( 679 *ServiceRegistration, error) { 680 681 // Get the services ID 682 id := MakeAllocServiceID(workload.AllocID, workload.Name(), service) 683 sreg := &ServiceRegistration{ 684 serviceID: id, 685 checkIDs: make(map[string]struct{}, len(service.Checks)), 686 } 687 688 // Service address modes default to auto 689 addrMode := service.AddressMode 690 if addrMode == "" { 691 addrMode = structs.AddressModeAuto 692 } 693 694 // Determine the address to advertise based on the mode 695 ip, port, err := getAddress(addrMode, service.PortLabel, workload.Networks, workload.DriverNetwork) 696 if err != nil { 697 return nil, fmt.Errorf("unable to get address for service %q: %v", service.Name, err) 698 } 699 700 // Determine whether to use tags or canary_tags 701 var tags []string 702 if workload.Canary && len(service.CanaryTags) > 0 { 703 tags = make([]string, len(service.CanaryTags)) 704 copy(tags, service.CanaryTags) 705 } else { 706 tags = make([]string, len(service.Tags)) 707 copy(tags, service.Tags) 708 } 709 710 // newConnect returns (nil, nil) if there's no Connect-enabled service. 711 connect, err := newConnect(service.Name, service.Connect, workload.Networks) 712 if err != nil { 713 return nil, fmt.Errorf("invalid Consul Connect configuration for service %q: %v", service.Name, err) 714 } 715 716 meta := make(map[string]string, len(service.Meta)) 717 for k, v := range service.Meta { 718 meta[k] = v 719 } 720 721 // This enables the consul UI to show that Nomad registered this service 722 meta["external-source"] = "nomad" 723 724 // Build the Consul Service registration request 725 serviceReg := &api.AgentServiceRegistration{ 726 ID: id, 727 Name: service.Name, 728 Tags: tags, 729 Address: ip, 730 Port: port, 731 Meta: meta, 732 Connect: connect, // will be nil if no Connect stanza 733 } 734 ops.regServices = append(ops.regServices, serviceReg) 735 736 // Build the check registrations 737 checkIDs, err := c.checkRegs(ops, id, service, workload) 738 if err != nil { 739 return nil, err 740 } 741 for _, cid := range checkIDs { 742 sreg.checkIDs[cid] = struct{}{} 743 } 744 return sreg, nil 745 } 746 747 // checkRegs registers the checks for the given service and returns the 748 // registered check ids. 749 func (c *ServiceClient) checkRegs(ops *operations, serviceID string, service *structs.Service, 750 workload *WorkloadServices) ([]string, error) { 751 752 // Fast path 753 numChecks := len(service.Checks) 754 if numChecks == 0 { 755 return nil, nil 756 } 757 758 checkIDs := make([]string, 0, numChecks) 759 for _, check := range service.Checks { 760 checkID := MakeCheckID(serviceID, check) 761 checkIDs = append(checkIDs, checkID) 762 if check.Type == structs.ServiceCheckScript { 763 // Skip getAddress for script checks 764 checkReg, err := createCheckReg(serviceID, checkID, check, "", 0) 765 if err != nil { 766 return nil, fmt.Errorf("failed to add script check %q: %v", check.Name, err) 767 } 768 ops.regChecks = append(ops.regChecks, checkReg) 769 continue 770 } 771 772 // Default to the service's port but allow check to override 773 portLabel := check.PortLabel 774 if portLabel == "" { 775 // Default to the service's port label 776 portLabel = service.PortLabel 777 } 778 779 // Checks address mode defaults to host for pre-#3380 backward compat 780 addrMode := check.AddressMode 781 if addrMode == "" { 782 addrMode = structs.AddressModeHost 783 } 784 785 ip, port, err := getAddress(addrMode, portLabel, workload.Networks, workload.DriverNetwork) 786 if err != nil { 787 return nil, fmt.Errorf("error getting address for check %q: %v", check.Name, err) 788 } 789 790 checkReg, err := createCheckReg(serviceID, checkID, check, ip, port) 791 if err != nil { 792 return nil, fmt.Errorf("failed to add check %q: %v", check.Name, err) 793 } 794 ops.regChecks = append(ops.regChecks, checkReg) 795 } 796 return checkIDs, nil 797 } 798 799 // RegisterWorkload with Consul. Adds all service entries and checks to Consul. 800 // 801 // If the service IP is set it used as the address in the service registration. 802 // Checks will always use the IP from the Task struct (host's IP). 803 // 804 // Actual communication with Consul is done asynchronously (see Run). 805 func (c *ServiceClient) RegisterWorkload(workload *WorkloadServices) error { 806 // Fast path 807 numServices := len(workload.Services) 808 if numServices == 0 { 809 return nil 810 } 811 812 t := new(ServiceRegistrations) 813 t.Services = make(map[string]*ServiceRegistration, numServices) 814 815 ops := &operations{} 816 for _, service := range workload.Services { 817 sreg, err := c.serviceRegs(ops, service, workload) 818 if err != nil { 819 return err 820 } 821 t.Services[sreg.serviceID] = sreg 822 } 823 824 // Add the workload to the allocation's registration 825 c.addRegistrations(workload.AllocID, workload.Name(), t) 826 827 c.commit(ops) 828 829 // Start watching checks. Done after service registrations are built 830 // since an error building them could leak watches. 831 for _, service := range workload.Services { 832 serviceID := MakeAllocServiceID(workload.AllocID, workload.Name(), service) 833 for _, check := range service.Checks { 834 if check.TriggersRestarts() { 835 checkID := MakeCheckID(serviceID, check) 836 c.checkWatcher.Watch(workload.AllocID, workload.Name(), checkID, check, workload.Restarter) 837 } 838 } 839 } 840 return nil 841 } 842 843 // UpdateWorkload in Consul. Does not alter the service if only checks have 844 // changed. 845 // 846 // DriverNetwork must not change between invocations for the same allocation. 847 func (c *ServiceClient) UpdateWorkload(old, newWorkload *WorkloadServices) error { 848 ops := &operations{} 849 850 regs := new(ServiceRegistrations) 851 regs.Services = make(map[string]*ServiceRegistration, len(newWorkload.Services)) 852 853 existingIDs := make(map[string]*structs.Service, len(old.Services)) 854 for _, s := range old.Services { 855 existingIDs[MakeAllocServiceID(old.AllocID, old.Name(), s)] = s 856 } 857 newIDs := make(map[string]*structs.Service, len(newWorkload.Services)) 858 for _, s := range newWorkload.Services { 859 newIDs[MakeAllocServiceID(newWorkload.AllocID, newWorkload.Name(), s)] = s 860 } 861 862 // Loop over existing Service IDs to see if they have been removed 863 for existingID, existingSvc := range existingIDs { 864 newSvc, ok := newIDs[existingID] 865 866 if !ok { 867 // Existing service entry removed 868 ops.deregServices = append(ops.deregServices, existingID) 869 for _, check := range existingSvc.Checks { 870 cid := MakeCheckID(existingID, check) 871 ops.deregChecks = append(ops.deregChecks, cid) 872 873 // Unwatch watched checks 874 if check.TriggersRestarts() { 875 c.checkWatcher.Unwatch(cid) 876 } 877 } 878 continue 879 } 880 881 oldHash := existingSvc.Hash(old.AllocID, old.Name(), old.Canary) 882 newHash := newSvc.Hash(newWorkload.AllocID, newWorkload.Name(), newWorkload.Canary) 883 if oldHash == newHash { 884 // Service exists and hasn't changed, don't re-add it later 885 delete(newIDs, existingID) 886 } 887 888 // Service still exists so add it to the task's registration 889 sreg := &ServiceRegistration{ 890 serviceID: existingID, 891 checkIDs: make(map[string]struct{}, len(newSvc.Checks)), 892 } 893 regs.Services[existingID] = sreg 894 895 // See if any checks were updated 896 existingChecks := make(map[string]*structs.ServiceCheck, len(existingSvc.Checks)) 897 for _, check := range existingSvc.Checks { 898 existingChecks[MakeCheckID(existingID, check)] = check 899 } 900 901 // Register new checks 902 for _, check := range newSvc.Checks { 903 checkID := MakeCheckID(existingID, check) 904 if _, exists := existingChecks[checkID]; exists { 905 // Check is still required. Remove it from the map so it doesn't get 906 // deleted later. 907 delete(existingChecks, checkID) 908 sreg.checkIDs[checkID] = struct{}{} 909 } 910 911 // New check on an unchanged service; add them now 912 newCheckIDs, err := c.checkRegs(ops, existingID, newSvc, newWorkload) 913 if err != nil { 914 return err 915 } 916 917 for _, checkID := range newCheckIDs { 918 sreg.checkIDs[checkID] = struct{}{} 919 } 920 921 // Update all watched checks as CheckRestart fields aren't part of ID 922 if check.TriggersRestarts() { 923 c.checkWatcher.Watch(newWorkload.AllocID, newWorkload.Name(), checkID, check, newWorkload.Restarter) 924 } 925 } 926 927 // Remove existing checks not in updated service 928 for cid, check := range existingChecks { 929 ops.deregChecks = append(ops.deregChecks, cid) 930 931 // Unwatch checks 932 if check.TriggersRestarts() { 933 c.checkWatcher.Unwatch(cid) 934 } 935 } 936 } 937 938 // Any remaining services should just be enqueued directly 939 for _, newSvc := range newIDs { 940 sreg, err := c.serviceRegs(ops, newSvc, newWorkload) 941 if err != nil { 942 return err 943 } 944 945 regs.Services[sreg.serviceID] = sreg 946 } 947 948 // Add the task to the allocation's registration 949 c.addRegistrations(newWorkload.AllocID, newWorkload.Name(), regs) 950 951 c.commit(ops) 952 953 // Start watching checks. Done after service registrations are built 954 // since an error building them could leak watches. 955 for _, service := range newIDs { 956 serviceID := MakeAllocServiceID(newWorkload.AllocID, newWorkload.Name(), service) 957 for _, check := range service.Checks { 958 if check.TriggersRestarts() { 959 checkID := MakeCheckID(serviceID, check) 960 c.checkWatcher.Watch(newWorkload.AllocID, newWorkload.Name(), checkID, check, newWorkload.Restarter) 961 } 962 } 963 } 964 return nil 965 } 966 967 // RemoveWorkload from Consul. Removes all service entries and checks. 968 // 969 // Actual communication with Consul is done asynchronously (see Run). 970 func (c *ServiceClient) RemoveWorkload(workload *WorkloadServices) { 971 ops := operations{} 972 973 for _, service := range workload.Services { 974 id := MakeAllocServiceID(workload.AllocID, workload.Name(), service) 975 ops.deregServices = append(ops.deregServices, id) 976 977 for _, check := range service.Checks { 978 cid := MakeCheckID(id, check) 979 ops.deregChecks = append(ops.deregChecks, cid) 980 981 if check.TriggersRestarts() { 982 c.checkWatcher.Unwatch(cid) 983 } 984 } 985 } 986 987 // Remove the workload from the alloc's registrations 988 c.removeRegistration(workload.AllocID, workload.Name()) 989 990 // Now add them to the deregistration fields; main Run loop will update 991 c.commit(&ops) 992 } 993 994 // AllocRegistrations returns the registrations for the given allocation. If the 995 // allocation has no reservations, the response is a nil object. 996 func (c *ServiceClient) AllocRegistrations(allocID string) (*AllocRegistration, error) { 997 // Get the internal struct using the lock 998 c.allocRegistrationsLock.RLock() 999 regInternal, ok := c.allocRegistrations[allocID] 1000 if !ok { 1001 c.allocRegistrationsLock.RUnlock() 1002 return nil, nil 1003 } 1004 1005 // Copy so we don't expose internal structs 1006 reg := regInternal.copy() 1007 c.allocRegistrationsLock.RUnlock() 1008 1009 // Query the services and checks to populate the allocation registrations. 1010 services, err := c.client.Services() 1011 if err != nil { 1012 return nil, err 1013 } 1014 1015 checks, err := c.client.Checks() 1016 if err != nil { 1017 return nil, err 1018 } 1019 1020 // Populate the object 1021 for _, treg := range reg.Tasks { 1022 for serviceID, sreg := range treg.Services { 1023 sreg.Service = services[serviceID] 1024 for checkID := range sreg.checkIDs { 1025 if check, ok := checks[checkID]; ok { 1026 sreg.Checks = append(sreg.Checks, check) 1027 } 1028 } 1029 } 1030 } 1031 1032 return reg, nil 1033 } 1034 1035 // UpdateTTL is used to update the TTL of a check. Typically this will only be 1036 // called to heartbeat script checks. 1037 func (c *ServiceClient) UpdateTTL(id, output, status string) error { 1038 return c.client.UpdateTTL(id, output, status) 1039 } 1040 1041 // Shutdown the Consul client. Update running task registrations and deregister 1042 // agent from Consul. On first call blocks up to shutdownWait before giving up 1043 // on syncing operations. 1044 func (c *ServiceClient) Shutdown() error { 1045 // Serialize Shutdown calls with RegisterAgent to prevent leaking agent 1046 // entries. 1047 c.agentLock.Lock() 1048 defer c.agentLock.Unlock() 1049 select { 1050 case <-c.shutdownCh: 1051 return nil 1052 default: 1053 close(c.shutdownCh) 1054 } 1055 1056 // Give run loop time to sync, but don't block indefinitely 1057 deadline := time.After(c.shutdownWait) 1058 1059 // Wait for Run to finish any outstanding operations and exit 1060 select { 1061 case <-c.exitCh: 1062 case <-deadline: 1063 // Don't wait forever though 1064 } 1065 1066 // If Consul was never seen nothing could be written so exit early 1067 if !c.hasSeen() { 1068 return nil 1069 } 1070 1071 // Always attempt to deregister Nomad agent Consul entries, even if 1072 // deadline was reached 1073 for id := range c.agentServices { 1074 if err := c.client.ServiceDeregister(id); err != nil { 1075 c.logger.Error("failed deregistering agent service", "service_id", id, "error", err) 1076 } 1077 } 1078 for id := range c.agentChecks { 1079 if err := c.client.CheckDeregister(id); err != nil { 1080 c.logger.Error("failed deregistering agent check", "check_id", id, "error", err) 1081 } 1082 } 1083 1084 return nil 1085 } 1086 1087 // addRegistration adds the service registrations for the given allocation. 1088 func (c *ServiceClient) addRegistrations(allocID, taskName string, reg *ServiceRegistrations) { 1089 c.allocRegistrationsLock.Lock() 1090 defer c.allocRegistrationsLock.Unlock() 1091 1092 alloc, ok := c.allocRegistrations[allocID] 1093 if !ok { 1094 alloc = &AllocRegistration{ 1095 Tasks: make(map[string]*ServiceRegistrations), 1096 } 1097 c.allocRegistrations[allocID] = alloc 1098 } 1099 alloc.Tasks[taskName] = reg 1100 } 1101 1102 // removeRegistrations removes the registration for the given allocation. 1103 func (c *ServiceClient) removeRegistration(allocID, taskName string) { 1104 c.allocRegistrationsLock.Lock() 1105 defer c.allocRegistrationsLock.Unlock() 1106 1107 alloc, ok := c.allocRegistrations[allocID] 1108 if !ok { 1109 return 1110 } 1111 1112 // Delete the task and if it is the last one also delete the alloc's 1113 // registration 1114 delete(alloc.Tasks, taskName) 1115 if len(alloc.Tasks) == 0 { 1116 delete(c.allocRegistrations, allocID) 1117 } 1118 } 1119 1120 // makeAgentServiceID creates a unique ID for identifying an agent service in 1121 // Consul. 1122 // 1123 // Agent service IDs are of the form: 1124 // 1125 // {nomadServicePrefix}-{ROLE}-b32(sha1({Service.Name}-{Service.Tags...}) 1126 // Example Server ID: _nomad-server-fbbk265qn4tmt25nd4ep42tjvmyj3hr4 1127 // Example Client ID: _nomad-client-ggnjpgl7yn7rgmvxzilmpvrzzvrszc7l 1128 // 1129 func makeAgentServiceID(role string, service *structs.Service) string { 1130 return fmt.Sprintf("%s-%s-%s", nomadServicePrefix, role, service.Hash(role, "", false)) 1131 } 1132 1133 // MakeAllocServiceID creates a unique ID for identifying an alloc service in 1134 // Consul. 1135 // 1136 // Example Service ID: _nomad-task-b4e61df9-b095-d64e-f241-23860da1375f-redis-http-http 1137 func MakeAllocServiceID(allocID, taskName string, service *structs.Service) string { 1138 return fmt.Sprintf("%s%s-%s-%s-%s", nomadTaskPrefix, allocID, taskName, service.Name, service.PortLabel) 1139 } 1140 1141 // MakeCheckID creates a unique ID for a check. 1142 // 1143 // Example Check ID: _nomad-check-434ae42f9a57c5705344974ac38de2aee0ee089d 1144 func MakeCheckID(serviceID string, check *structs.ServiceCheck) string { 1145 return fmt.Sprintf("%s%s", nomadCheckPrefix, check.Hash(serviceID)) 1146 } 1147 1148 // createCheckReg creates a Check that can be registered with Consul. 1149 // 1150 // Script checks simply have a TTL set and the caller is responsible for 1151 // running the script and heartbeating. 1152 func createCheckReg(serviceID, checkID string, check *structs.ServiceCheck, host string, port int) (*api.AgentCheckRegistration, error) { 1153 chkReg := api.AgentCheckRegistration{ 1154 ID: checkID, 1155 Name: check.Name, 1156 ServiceID: serviceID, 1157 } 1158 chkReg.Status = check.InitialStatus 1159 chkReg.Timeout = check.Timeout.String() 1160 chkReg.Interval = check.Interval.String() 1161 1162 // Require an address for http or tcp checks 1163 if port == 0 && check.RequiresPort() { 1164 return nil, fmt.Errorf("%s checks require an address", check.Type) 1165 } 1166 1167 switch check.Type { 1168 case structs.ServiceCheckHTTP: 1169 proto := check.Protocol 1170 if proto == "" { 1171 proto = "http" 1172 } 1173 if check.TLSSkipVerify { 1174 chkReg.TLSSkipVerify = true 1175 } 1176 base := url.URL{ 1177 Scheme: proto, 1178 Host: net.JoinHostPort(host, strconv.Itoa(port)), 1179 } 1180 relative, err := url.Parse(check.Path) 1181 if err != nil { 1182 return nil, err 1183 } 1184 url := base.ResolveReference(relative) 1185 chkReg.HTTP = url.String() 1186 chkReg.Method = check.Method 1187 chkReg.Header = check.Header 1188 1189 case structs.ServiceCheckTCP: 1190 chkReg.TCP = net.JoinHostPort(host, strconv.Itoa(port)) 1191 1192 case structs.ServiceCheckScript: 1193 chkReg.TTL = (check.Interval + ttlCheckBuffer).String() 1194 // As of Consul 1.0.0 setting TTL and Interval is a 400 1195 chkReg.Interval = "" 1196 1197 case structs.ServiceCheckGRPC: 1198 chkReg.GRPC = fmt.Sprintf("%s/%s", net.JoinHostPort(host, strconv.Itoa(port)), check.GRPCService) 1199 chkReg.GRPCUseTLS = check.GRPCUseTLS 1200 if check.TLSSkipVerify { 1201 chkReg.TLSSkipVerify = true 1202 } 1203 1204 default: 1205 return nil, fmt.Errorf("check type %+q not valid", check.Type) 1206 } 1207 return &chkReg, nil 1208 } 1209 1210 // isNomadCheck returns true if the ID matches the pattern of a Nomad managed 1211 // check. 1212 func isNomadCheck(id string) bool { 1213 return strings.HasPrefix(id, nomadCheckPrefix) 1214 } 1215 1216 // isNomadService returns true if the ID matches the pattern of a Nomad managed 1217 // service (new or old formats). Agent services return false as independent 1218 // client and server agents may be running on the same machine. #2827 1219 func isNomadService(id string) bool { 1220 return strings.HasPrefix(id, nomadTaskPrefix) || isOldNomadService(id) 1221 } 1222 1223 // isOldNomadService returns true if the ID matches an old pattern managed by 1224 // Nomad. 1225 // 1226 // Pre-0.7.1 task service IDs are of the form: 1227 // 1228 // {nomadServicePrefix}-executor-{ALLOC_ID}-{Service.Name}-{Service.Tags...} 1229 // Example Service ID: _nomad-executor-1234-echo-http-tag1-tag2-tag3 1230 // 1231 func isOldNomadService(id string) bool { 1232 const prefix = nomadServicePrefix + "-executor" 1233 return strings.HasPrefix(id, prefix) 1234 } 1235 1236 // isNomadSidecar returns true if the ID matches a sidecar proxy for a Nomad 1237 // managed service. 1238 // 1239 // For example if you have a Connect enabled service with the ID: 1240 // 1241 // _nomad-task-5229c7f8-376b-3ccc-edd9-981e238f7033-cache-redis-cache-db 1242 // 1243 // Consul will create a service for the sidecar proxy with the ID: 1244 // 1245 // _nomad-task-5229c7f8-376b-3ccc-edd9-981e238f7033-cache-redis-cache-db-sidecar-proxy 1246 // 1247 func isNomadSidecar(id string, services map[string]*api.AgentServiceRegistration) bool { 1248 const suffix = "-sidecar-proxy" 1249 if !strings.HasSuffix(id, suffix) { 1250 return false 1251 } 1252 1253 // Make sure the Nomad managed service for this proxy still exists. 1254 _, ok := services[id[:len(id)-len(suffix)]] 1255 return ok 1256 } 1257 1258 // getAddress returns the IP and port to use for a service or check. If no port 1259 // label is specified (an empty value), zero values are returned because no 1260 // address could be resolved. 1261 func getAddress(addrMode, portLabel string, networks structs.Networks, driverNet *drivers.DriverNetwork) (string, int, error) { 1262 switch addrMode { 1263 case structs.AddressModeAuto: 1264 if driverNet.Advertise() { 1265 addrMode = structs.AddressModeDriver 1266 } else { 1267 addrMode = structs.AddressModeHost 1268 } 1269 return getAddress(addrMode, portLabel, networks, driverNet) 1270 case structs.AddressModeHost: 1271 if portLabel == "" { 1272 if len(networks) != 1 { 1273 // If no networks are specified return zero 1274 // values. Consul will advertise the host IP 1275 // with no port. This is the pre-0.7.1 behavior 1276 // some people rely on. 1277 return "", 0, nil 1278 } 1279 1280 return networks[0].IP, 0, nil 1281 } 1282 1283 // Default path: use host ip:port 1284 ip, port := networks.Port(portLabel) 1285 if ip == "" && port <= 0 { 1286 return "", 0, fmt.Errorf("invalid port %q: port label not found", portLabel) 1287 } 1288 return ip, port, nil 1289 1290 case structs.AddressModeDriver: 1291 // Require a driver network if driver address mode is used 1292 if driverNet == nil { 1293 return "", 0, fmt.Errorf(`cannot use address_mode="driver": no driver network exists`) 1294 } 1295 1296 // If no port label is specified just return the IP 1297 if portLabel == "" { 1298 return driverNet.IP, 0, nil 1299 } 1300 1301 // If the port is a label, use the driver's port (not the host's) 1302 if port, ok := driverNet.PortMap[portLabel]; ok { 1303 return driverNet.IP, port, nil 1304 } 1305 1306 // If port isn't a label, try to parse it as a literal port number 1307 port, err := strconv.Atoi(portLabel) 1308 if err != nil { 1309 // Don't include Atoi error message as user likely 1310 // never intended it to be a numeric and it creates a 1311 // confusing error message 1312 return "", 0, fmt.Errorf("invalid port label %q: port labels in driver address_mode must be numeric or in the driver's port map", portLabel) 1313 } 1314 if port <= 0 { 1315 return "", 0, fmt.Errorf("invalid port: %q: port must be >0", portLabel) 1316 } 1317 1318 return driverNet.IP, port, nil 1319 1320 default: 1321 // Shouldn't happen due to validation, but enforce invariants 1322 return "", 0, fmt.Errorf("invalid address mode %q", addrMode) 1323 } 1324 } 1325 1326 // newConnect creates a new Consul AgentServiceConnect struct based on a Nomad 1327 // Connect struct. If the nomad Connect struct is nil, nil will be returned to 1328 // disable Connect for this service. 1329 func newConnect(serviceName string, nc *structs.ConsulConnect, networks structs.Networks) (*api.AgentServiceConnect, error) { 1330 if nc == nil { 1331 // No Connect stanza, returning nil is fine 1332 return nil, nil 1333 } 1334 1335 cc := &api.AgentServiceConnect{ 1336 Native: nc.Native, 1337 } 1338 1339 if nc.SidecarService == nil { 1340 return cc, nil 1341 } 1342 1343 net, port, err := getConnectPort(serviceName, networks) 1344 if err != nil { 1345 return nil, err 1346 } 1347 1348 // Bind to netns IP(s):port 1349 proxyConfig := map[string]interface{}{} 1350 localServiceAddress := "" 1351 localServicePort := 0 1352 if nc.SidecarService.Proxy != nil { 1353 localServiceAddress = nc.SidecarService.Proxy.LocalServiceAddress 1354 localServicePort = nc.SidecarService.Proxy.LocalServicePort 1355 if nc.SidecarService.Proxy.Config != nil { 1356 proxyConfig = nc.SidecarService.Proxy.Config 1357 } 1358 } 1359 proxyConfig["bind_address"] = "0.0.0.0" 1360 proxyConfig["bind_port"] = port.To 1361 1362 // Advertise host IP:port 1363 cc.SidecarService = &api.AgentServiceRegistration{ 1364 Tags: helper.CopySliceString(nc.SidecarService.Tags), 1365 Address: net.IP, 1366 Port: port.Value, 1367 1368 // Automatically configure the proxy to bind to all addresses 1369 // within the netns. 1370 Proxy: &api.AgentServiceConnectProxyConfig{ 1371 LocalServiceAddress: localServiceAddress, 1372 LocalServicePort: localServicePort, 1373 Config: proxyConfig, 1374 }, 1375 } 1376 1377 // If no further proxy settings were explicitly configured, exit early 1378 if nc.SidecarService.Proxy == nil { 1379 return cc, nil 1380 } 1381 1382 numUpstreams := len(nc.SidecarService.Proxy.Upstreams) 1383 if numUpstreams == 0 { 1384 return cc, nil 1385 } 1386 1387 upstreams := make([]api.Upstream, numUpstreams) 1388 for i, nu := range nc.SidecarService.Proxy.Upstreams { 1389 upstreams[i].DestinationName = nu.DestinationName 1390 upstreams[i].LocalBindPort = nu.LocalBindPort 1391 } 1392 cc.SidecarService.Proxy.Upstreams = upstreams 1393 1394 return cc, nil 1395 } 1396 1397 // getConnectPort returns the network and port for the Connect proxy sidecar 1398 // defined for this service. An error is returned if the network and port 1399 // cannot be determined. 1400 func getConnectPort(serviceName string, networks structs.Networks) (*structs.NetworkResource, structs.Port, error) { 1401 if n := len(networks); n != 1 { 1402 return nil, structs.Port{}, fmt.Errorf("Connect only supported with exactly 1 network (found %d)", n) 1403 } 1404 1405 port, ok := networks[0].PortForService(serviceName) 1406 if !ok { 1407 return nil, structs.Port{}, fmt.Errorf("No Connect port defined for service %q", serviceName) 1408 } 1409 1410 return networks[0], port, nil 1411 }