github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/command/agent/agent.go (about) 1 package agent 2 3 import ( 4 "fmt" 5 "io" 6 "log" 7 "net" 8 "os" 9 "path/filepath" 10 "runtime" 11 "strconv" 12 "strings" 13 "sync" 14 "sync/atomic" 15 "time" 16 17 "github.com/hashicorp/nomad/client" 18 clientconfig "github.com/hashicorp/nomad/client/config" 19 "github.com/hashicorp/nomad/command/agent/consul" 20 "github.com/hashicorp/nomad/nomad" 21 "github.com/hashicorp/nomad/nomad/structs" 22 ) 23 24 const ( 25 clientHttpCheckInterval = 10 * time.Second 26 clientHttpCheckTimeout = 3 * time.Second 27 serverHttpCheckInterval = 10 * time.Second 28 serverHttpCheckTimeout = 6 * time.Second 29 serverRpcCheckInterval = 10 * time.Second 30 serverRpcCheckTimeout = 3 * time.Second 31 serverSerfCheckInterval = 10 * time.Second 32 serverSerfCheckTimeout = 3 * time.Second 33 ) 34 35 // Agent is a long running daemon that is used to run both 36 // clients and servers. Servers are responsible for managing 37 // state and making scheduling decisions. Clients can be 38 // scheduled to, and are responsible for interfacing with 39 // servers to run allocations. 40 type Agent struct { 41 config *Config 42 logger *log.Logger 43 logOutput io.Writer 44 45 // consulSyncer registers the Nomad agent with the Consul Agent 46 consulSyncer *consul.Syncer 47 48 client *client.Client 49 clientHTTPAddr string 50 51 server *nomad.Server 52 serverHTTPAddr string 53 serverRPCAddr string 54 serverSerfAddr string 55 56 shutdown bool 57 shutdownCh chan struct{} 58 shutdownLock sync.Mutex 59 } 60 61 // NewAgent is used to create a new agent with the given configuration 62 func NewAgent(config *Config, logOutput io.Writer) (*Agent, error) { 63 a := &Agent{ 64 config: config, 65 logger: log.New(logOutput, "", log.LstdFlags|log.Lmicroseconds), 66 logOutput: logOutput, 67 shutdownCh: make(chan struct{}), 68 } 69 70 if err := a.setupConsulSyncer(); err != nil { 71 return nil, fmt.Errorf("Failed to initialize Consul syncer task: %v", err) 72 } 73 if err := a.setupServer(); err != nil { 74 return nil, err 75 } 76 if err := a.setupClient(); err != nil { 77 return nil, err 78 } 79 if a.client == nil && a.server == nil { 80 return nil, fmt.Errorf("must have at least client or server mode enabled") 81 } 82 83 // The Nomad Agent runs the consul.Syncer regardless of whether or not the 84 // Agent is running in Client or Server mode (or both), and regardless of 85 // the consul.auto_advertise parameter. The Client and Server both reuse the 86 // same consul.Syncer instance. This Syncer task periodically executes 87 // callbacks that update Consul. The reason the Syncer is always running is 88 // because one of the callbacks is attempts to self-bootstrap Nomad using 89 // information found in Consul. 90 go a.consulSyncer.Run() 91 92 return a, nil 93 } 94 95 // serverConfig is used to generate a new server configuration struct 96 // for initializing a nomad server. 97 func (a *Agent) serverConfig() (*nomad.Config, error) { 98 conf := a.config.NomadConfig 99 if conf == nil { 100 conf = nomad.DefaultConfig() 101 } 102 conf.LogOutput = a.logOutput 103 conf.DevMode = a.config.DevMode 104 conf.Build = fmt.Sprintf("%s%s", a.config.Version, a.config.VersionPrerelease) 105 if a.config.Region != "" { 106 conf.Region = a.config.Region 107 } 108 if a.config.Datacenter != "" { 109 conf.Datacenter = a.config.Datacenter 110 } 111 if a.config.NodeName != "" { 112 conf.NodeName = a.config.NodeName 113 } 114 if a.config.Server.BootstrapExpect > 0 { 115 if a.config.Server.BootstrapExpect == 1 { 116 conf.Bootstrap = true 117 } else { 118 atomic.StoreInt32(&conf.BootstrapExpect, int32(a.config.Server.BootstrapExpect)) 119 } 120 } 121 if a.config.DataDir != "" { 122 conf.DataDir = filepath.Join(a.config.DataDir, "server") 123 } 124 if a.config.Server.DataDir != "" { 125 conf.DataDir = a.config.Server.DataDir 126 } 127 if a.config.Server.ProtocolVersion != 0 { 128 conf.ProtocolVersion = uint8(a.config.Server.ProtocolVersion) 129 } 130 if a.config.Server.NumSchedulers != 0 { 131 conf.NumSchedulers = a.config.Server.NumSchedulers 132 } 133 if len(a.config.Server.EnabledSchedulers) != 0 { 134 conf.EnabledSchedulers = a.config.Server.EnabledSchedulers 135 } 136 137 // Set up the bind addresses 138 rpcAddr, err := a.getRPCAddr(true) 139 if err != nil { 140 return nil, err 141 } 142 serfAddr, err := a.getSerfAddr(true) 143 if err != nil { 144 return nil, err 145 } 146 conf.RPCAddr.Port = rpcAddr.Port 147 conf.RPCAddr.IP = rpcAddr.IP 148 conf.SerfConfig.MemberlistConfig.BindPort = serfAddr.Port 149 conf.SerfConfig.MemberlistConfig.BindAddr = serfAddr.IP.String() 150 151 // Set up the advertise addresses 152 httpAddr, err := a.getHTTPAddr(false) 153 if err != nil { 154 return nil, err 155 } 156 rpcAddr, err = a.getRPCAddr(false) 157 if err != nil { 158 return nil, err 159 } 160 serfAddr, err = a.getSerfAddr(false) 161 if err != nil { 162 return nil, err 163 } 164 a.serverHTTPAddr = net.JoinHostPort(httpAddr.IP.String(), strconv.Itoa(httpAddr.Port)) 165 a.serverRPCAddr = net.JoinHostPort(rpcAddr.IP.String(), strconv.Itoa(rpcAddr.Port)) 166 a.serverSerfAddr = net.JoinHostPort(serfAddr.IP.String(), strconv.Itoa(serfAddr.Port)) 167 conf.RPCAdvertise = rpcAddr 168 conf.SerfConfig.MemberlistConfig.AdvertiseAddr = serfAddr.IP.String() 169 conf.SerfConfig.MemberlistConfig.AdvertisePort = serfAddr.Port 170 171 // Set up gc threshold and heartbeat grace period 172 if gcThreshold := a.config.Server.NodeGCThreshold; gcThreshold != "" { 173 dur, err := time.ParseDuration(gcThreshold) 174 if err != nil { 175 return nil, err 176 } 177 conf.NodeGCThreshold = dur 178 } 179 180 if heartbeatGrace := a.config.Server.HeartbeatGrace; heartbeatGrace != "" { 181 dur, err := time.ParseDuration(heartbeatGrace) 182 if err != nil { 183 return nil, err 184 } 185 conf.HeartbeatGrace = dur 186 } 187 188 if a.config.Consul.AutoAdvertise && a.config.Consul.ServerServiceName == "" { 189 return nil, fmt.Errorf("server_service_name must be set when auto_advertise is enabled") 190 } 191 192 // Add the Consul and Vault configs 193 conf.ConsulConfig = a.config.Consul 194 conf.VaultConfig = a.config.Vault 195 196 // Set the TLS config 197 conf.TLSConfig = a.config.TLSConfig 198 199 return conf, nil 200 } 201 202 // clientConfig is used to generate a new client configuration struct 203 // for initializing a Nomad client. 204 func (a *Agent) clientConfig() (*clientconfig.Config, error) { 205 // Setup the configuration 206 conf := a.config.ClientConfig 207 if conf == nil { 208 conf = clientconfig.DefaultConfig() 209 } 210 if a.server != nil { 211 conf.RPCHandler = a.server 212 } 213 conf.LogOutput = a.logOutput 214 conf.DevMode = a.config.DevMode 215 if a.config.Region != "" { 216 conf.Region = a.config.Region 217 } 218 if a.config.DataDir != "" { 219 conf.StateDir = filepath.Join(a.config.DataDir, "client") 220 conf.AllocDir = filepath.Join(a.config.DataDir, "alloc") 221 } 222 if a.config.Client.StateDir != "" { 223 conf.StateDir = a.config.Client.StateDir 224 } 225 if a.config.Client.AllocDir != "" { 226 conf.AllocDir = a.config.Client.AllocDir 227 } 228 conf.Servers = a.config.Client.Servers 229 if a.config.Client.NetworkInterface != "" { 230 conf.NetworkInterface = a.config.Client.NetworkInterface 231 } 232 conf.ChrootEnv = a.config.Client.ChrootEnv 233 conf.Options = a.config.Client.Options 234 // Logging deprecation messages about consul related configuration in client 235 // options 236 var invalidConsulKeys []string 237 for key := range conf.Options { 238 if strings.HasPrefix(key, "consul") { 239 invalidConsulKeys = append(invalidConsulKeys, fmt.Sprintf("options.%s", key)) 240 } 241 } 242 if len(invalidConsulKeys) > 0 { 243 a.logger.Printf("[WARN] agent: Invalid keys: %v", strings.Join(invalidConsulKeys, ",")) 244 a.logger.Printf(`Nomad client ignores consul related configuration in client options. 245 Please refer to the guide https://www.nomadproject.io/docs/agent/configuration/consul.html 246 to configure Nomad to work with Consul.`) 247 } 248 249 if a.config.Client.NetworkSpeed != 0 { 250 conf.NetworkSpeed = a.config.Client.NetworkSpeed 251 } 252 if a.config.Client.MaxKillTimeout != "" { 253 dur, err := time.ParseDuration(a.config.Client.MaxKillTimeout) 254 if err != nil { 255 return nil, fmt.Errorf("Error parsing retry interval: %s", err) 256 } 257 conf.MaxKillTimeout = dur 258 } 259 conf.ClientMaxPort = uint(a.config.Client.ClientMaxPort) 260 conf.ClientMinPort = uint(a.config.Client.ClientMinPort) 261 262 // Setup the node 263 conf.Node = new(structs.Node) 264 conf.Node.Datacenter = a.config.Datacenter 265 conf.Node.Name = a.config.NodeName 266 conf.Node.Meta = a.config.Client.Meta 267 conf.Node.NodeClass = a.config.Client.NodeClass 268 269 // Set up the HTTP advertise address 270 httpAddr, err := a.selectAddr(a.getHTTPAddr, false) 271 if err != nil { 272 return nil, err 273 } 274 conf.Node.HTTPAddr = httpAddr 275 a.clientHTTPAddr = httpAddr 276 277 // Reserve resources on the node. 278 r := conf.Node.Reserved 279 if r == nil { 280 r = new(structs.Resources) 281 conf.Node.Reserved = r 282 } 283 r.CPU = a.config.Client.Reserved.CPU 284 r.MemoryMB = a.config.Client.Reserved.MemoryMB 285 r.DiskMB = a.config.Client.Reserved.DiskMB 286 r.IOPS = a.config.Client.Reserved.IOPS 287 conf.GloballyReservedPorts = a.config.Client.Reserved.ParsedReservedPorts 288 289 conf.Version = fmt.Sprintf("%s%s", a.config.Version, a.config.VersionPrerelease) 290 conf.Revision = a.config.Revision 291 292 if a.config.Consul.AutoAdvertise && a.config.Consul.ClientServiceName == "" { 293 return nil, fmt.Errorf("client_service_name must be set when auto_advertise is enabled") 294 } 295 296 conf.ConsulConfig = a.config.Consul 297 conf.VaultConfig = a.config.Vault 298 conf.StatsCollectionInterval = a.config.Telemetry.collectionInterval 299 conf.PublishNodeMetrics = a.config.Telemetry.PublishNodeMetrics 300 conf.PublishAllocationMetrics = a.config.Telemetry.PublishAllocationMetrics 301 302 // Set the TLS related configs 303 conf.TLSConfig = a.config.TLSConfig 304 conf.Node.TLSEnabled = conf.TLSConfig.EnableHTTP 305 306 return conf, nil 307 } 308 309 // setupServer is used to setup the server if enabled 310 func (a *Agent) setupServer() error { 311 if !a.config.Server.Enabled { 312 return nil 313 } 314 315 // Setup the configuration 316 conf, err := a.serverConfig() 317 if err != nil { 318 return fmt.Errorf("server config setup failed: %s", err) 319 } 320 321 // Sets up the keyring for gossip encryption 322 if err := a.setupKeyrings(conf); err != nil { 323 return fmt.Errorf("failed to configure keyring: %v", err) 324 } 325 326 // Create the server 327 server, err := nomad.NewServer(conf, a.consulSyncer, a.logger) 328 if err != nil { 329 return fmt.Errorf("server setup failed: %v", err) 330 } 331 a.server = server 332 333 // Resolve consul check addresses. Always use advertise address for services 334 httpCheckAddr, err := a.selectAddr(a.getHTTPAddr, !a.config.Consul.ChecksUseAdvertise) 335 if err != nil { 336 return err 337 } 338 rpcCheckAddr, err := a.selectAddr(a.getRPCAddr, !a.config.Consul.ChecksUseAdvertise) 339 if err != nil { 340 return err 341 } 342 serfCheckAddr, err := a.selectAddr(a.getSerfAddr, !a.config.Consul.ChecksUseAdvertise) 343 if err != nil { 344 return err 345 } 346 347 // Create the Nomad Server services for Consul 348 // TODO re-introduce HTTP/S checks when Consul 0.7.1 comes out 349 if a.config.Consul.AutoAdvertise { 350 httpServ := &structs.Service{ 351 Name: a.config.Consul.ServerServiceName, 352 PortLabel: a.serverHTTPAddr, 353 Tags: []string{consul.ServiceTagHTTP}, 354 Checks: []*structs.ServiceCheck{ 355 &structs.ServiceCheck{ 356 Name: "Nomad Server HTTP Check", 357 Type: "http", 358 Path: "/v1/status/peers", 359 Protocol: "http", 360 Interval: serverHttpCheckInterval, 361 Timeout: serverHttpCheckTimeout, 362 PortLabel: httpCheckAddr, 363 }, 364 }, 365 } 366 rpcServ := &structs.Service{ 367 Name: a.config.Consul.ServerServiceName, 368 PortLabel: a.serverRPCAddr, 369 Tags: []string{consul.ServiceTagRPC}, 370 Checks: []*structs.ServiceCheck{ 371 &structs.ServiceCheck{ 372 Name: "Nomad Server RPC Check", 373 Type: "tcp", 374 Interval: serverRpcCheckInterval, 375 Timeout: serverRpcCheckTimeout, 376 PortLabel: rpcCheckAddr, 377 }, 378 }, 379 } 380 serfServ := &structs.Service{ 381 PortLabel: a.serverSerfAddr, 382 Name: a.config.Consul.ServerServiceName, 383 Tags: []string{consul.ServiceTagSerf}, 384 Checks: []*structs.ServiceCheck{ 385 &structs.ServiceCheck{ 386 Name: "Nomad Server Serf Check", 387 Type: "tcp", 388 Interval: serverSerfCheckInterval, 389 Timeout: serverSerfCheckTimeout, 390 PortLabel: serfCheckAddr, 391 }, 392 }, 393 } 394 395 // Add the http port check if TLS isn't enabled 396 // TODO Add TLS check when Consul 0.7.1 comes out. 397 consulServices := map[consul.ServiceKey]*structs.Service{ 398 consul.GenerateServiceKey(rpcServ): rpcServ, 399 consul.GenerateServiceKey(serfServ): serfServ, 400 } 401 if !conf.TLSConfig.EnableHTTP { 402 consulServices[consul.GenerateServiceKey(httpServ)] = httpServ 403 } 404 a.consulSyncer.SetServices(consul.ServerDomain, consulServices) 405 } 406 407 return nil 408 } 409 410 // setupKeyrings is used to initialize and load keyrings during agent startup 411 func (a *Agent) setupKeyrings(config *nomad.Config) error { 412 file := filepath.Join(a.config.DataDir, serfKeyring) 413 414 if a.config.Server.EncryptKey == "" { 415 goto LOAD 416 } 417 if _, err := os.Stat(file); err != nil { 418 if err := initKeyring(file, a.config.Server.EncryptKey); err != nil { 419 return err 420 } 421 } 422 423 LOAD: 424 if _, err := os.Stat(file); err == nil { 425 config.SerfConfig.KeyringFile = file 426 } 427 if err := loadKeyringFile(config.SerfConfig); err != nil { 428 return err 429 } 430 // Success! 431 return nil 432 } 433 434 // setupClient is used to setup the client if enabled 435 func (a *Agent) setupClient() error { 436 if !a.config.Client.Enabled { 437 return nil 438 } 439 440 // Setup the configuration 441 conf, err := a.clientConfig() 442 if err != nil { 443 return fmt.Errorf("client setup failed: %v", err) 444 } 445 446 // Reserve some ports for the plugins if we are on Windows 447 if runtime.GOOS == "windows" { 448 if err := a.reservePortsForClient(conf); err != nil { 449 return err 450 } 451 } 452 453 // Create the client 454 client, err := client.NewClient(conf, a.consulSyncer, a.logger) 455 if err != nil { 456 return fmt.Errorf("client setup failed: %v", err) 457 } 458 a.client = client 459 460 // Resolve the http check address 461 httpCheckAddr, err := a.selectAddr(a.getHTTPAddr, !a.config.Consul.ChecksUseAdvertise) 462 if err != nil { 463 return err 464 } 465 466 // Create the Nomad Client services for Consul 467 // TODO think how we can re-introduce HTTP/S checks when Consul 0.7.1 comes 468 // out 469 if a.config.Consul.AutoAdvertise { 470 httpServ := &structs.Service{ 471 Name: a.config.Consul.ClientServiceName, 472 PortLabel: a.clientHTTPAddr, 473 Tags: []string{consul.ServiceTagHTTP}, 474 Checks: []*structs.ServiceCheck{ 475 &structs.ServiceCheck{ 476 Name: "Nomad Client HTTP Check", 477 Type: "http", 478 Path: "/v1/agent/servers", 479 Protocol: "http", 480 Interval: clientHttpCheckInterval, 481 Timeout: clientHttpCheckTimeout, 482 PortLabel: httpCheckAddr, 483 }, 484 }, 485 } 486 if !conf.TLSConfig.EnableHTTP { 487 a.consulSyncer.SetServices(consul.ClientDomain, map[consul.ServiceKey]*structs.Service{ 488 consul.GenerateServiceKey(httpServ): httpServ, 489 }) 490 } 491 } 492 493 return nil 494 } 495 496 // Defines the selector interface 497 type addrSelector func(bool) (*net.TCPAddr, error) 498 499 // selectAddr returns the right address given a selector, and return it as a PortLabel 500 // preferBind is a weak preference, and will skip 0.0.0.0 501 func (a *Agent) selectAddr(selector addrSelector, preferBind bool) (string, error) { 502 addr, err := selector(preferBind) 503 if err != nil { 504 return "", err 505 } 506 507 if preferBind && addr.IP.String() == "0.0.0.0" { 508 addr, err = selector(false) 509 if err != nil { 510 return "", err 511 } 512 } 513 514 address := net.JoinHostPort(addr.IP.String(), strconv.Itoa(addr.Port)) 515 return address, nil 516 } 517 518 // getHTTPAddr returns the HTTP address to use based on the clients 519 // configuration. If bind is true, an address appropriate for binding is 520 // returned, otherwise an address for advertising is returned. Skip 0.0.0.0 521 // unless returning a bind address, since that's the only time it's useful. 522 func (a *Agent) getHTTPAddr(bind bool) (*net.TCPAddr, error) { 523 advertAddr := a.config.AdvertiseAddrs.HTTP 524 bindAddr := a.config.Addresses.HTTP 525 globalBindAddr := a.config.BindAddr 526 port := a.config.Ports.HTTP 527 return pickAddress(bind, globalBindAddr, advertAddr, bindAddr, port, "HTTP") 528 } 529 530 // getRPCAddr returns the HTTP address to use based on the clients 531 // configuration. If bind is true, an address appropriate for binding is 532 // returned, otherwise an address for advertising is returned. Skip 0.0.0.0 533 // unless returning a bind address, since that's the only time it's useful. 534 func (a *Agent) getRPCAddr(bind bool) (*net.TCPAddr, error) { 535 advertAddr := a.config.AdvertiseAddrs.RPC 536 bindAddr := a.config.Addresses.RPC 537 globalBindAddr := a.config.BindAddr 538 port := a.config.Ports.RPC 539 return pickAddress(bind, globalBindAddr, advertAddr, bindAddr, port, "RPC") 540 } 541 542 // getSerfAddr returns the Serf address to use based on the clients 543 // configuration. If bind is true, an address appropriate for binding is 544 // returned, otherwise an address for advertising is returned. Skip 0.0.0.0 545 // unless returning a bind address, since that's the only time it's useful. 546 func (a *Agent) getSerfAddr(bind bool) (*net.TCPAddr, error) { 547 advertAddr := a.config.AdvertiseAddrs.Serf 548 bindAddr := a.config.Addresses.Serf 549 globalBindAddr := a.config.BindAddr 550 port := a.config.Ports.Serf 551 return pickAddress(bind, globalBindAddr, advertAddr, bindAddr, port, "Serf") 552 } 553 554 // pickAddress is a shared helper to pick the address to either bind to or 555 // advertise. 556 func pickAddress(bind bool, globalBindAddr, advertiseAddr, bindAddr string, port int, service string) (*net.TCPAddr, error) { 557 var serverAddr string 558 if advertiseAddr != "" && !bind { 559 serverAddr = advertiseAddr 560 561 // Check if the advertise has a port 562 if host, pport, err := net.SplitHostPort(advertiseAddr); err == nil { 563 if parsed, err := strconv.Atoi(pport); err == nil { 564 serverAddr = host 565 port = parsed 566 } 567 } 568 } else if bindAddr != "" && !(bindAddr == "0.0.0.0" && !bind) { 569 serverAddr = bindAddr 570 } else if globalBindAddr != "" && !(globalBindAddr == "0.0.0.0" && !bind) { 571 serverAddr = globalBindAddr 572 } else { 573 serverAddr = "127.0.0.1" 574 } 575 576 ip := net.ParseIP(serverAddr) 577 if ip == nil { 578 joined := net.JoinHostPort(serverAddr, strconv.Itoa(port)) 579 addr, err := net.ResolveTCPAddr("tcp", joined) 580 if err == nil { 581 return addr, nil 582 } 583 584 return nil, fmt.Errorf("Failed to parse %s %q as IP and failed to resolve address: %v", service, serverAddr, err) 585 } 586 587 return &net.TCPAddr{ 588 IP: ip, 589 Port: port, 590 }, nil 591 } 592 593 // reservePortsForClient reserves a range of ports for the client to use when 594 // it creates various plugins for log collection, executors, drivers, etc 595 func (a *Agent) reservePortsForClient(conf *clientconfig.Config) error { 596 // finding the device name for loopback 597 deviceName, addr, mask, err := a.findLoopbackDevice() 598 if err != nil { 599 return fmt.Errorf("error finding the device name for loopback: %v", err) 600 } 601 602 // seeing if the user has already reserved some resources on this device 603 var nr *structs.NetworkResource 604 if conf.Node.Reserved == nil { 605 conf.Node.Reserved = &structs.Resources{} 606 } 607 for _, n := range conf.Node.Reserved.Networks { 608 if n.Device == deviceName { 609 nr = n 610 } 611 } 612 // If the user hasn't already created the device, we create it 613 if nr == nil { 614 nr = &structs.NetworkResource{ 615 Device: deviceName, 616 IP: addr, 617 CIDR: mask, 618 ReservedPorts: make([]structs.Port, 0), 619 } 620 } 621 // appending the port ranges we want to use for the client to the list of 622 // reserved ports for this device 623 for i := conf.ClientMinPort; i <= conf.ClientMaxPort; i++ { 624 nr.ReservedPorts = append(nr.ReservedPorts, structs.Port{Label: fmt.Sprintf("plugin-%d", i), Value: int(i)}) 625 } 626 conf.Node.Reserved.Networks = append(conf.Node.Reserved.Networks, nr) 627 return nil 628 } 629 630 // findLoopbackDevice iterates through all the interfaces on a machine and 631 // returns the ip addr, mask of the loopback device 632 func (a *Agent) findLoopbackDevice() (string, string, string, error) { 633 var ifcs []net.Interface 634 var err error 635 ifcs, err = net.Interfaces() 636 if err != nil { 637 return "", "", "", err 638 } 639 for _, ifc := range ifcs { 640 addrs, err := ifc.Addrs() 641 if err != nil { 642 return "", "", "", err 643 } 644 for _, addr := range addrs { 645 var ip net.IP 646 switch v := addr.(type) { 647 case *net.IPNet: 648 ip = v.IP 649 case *net.IPAddr: 650 ip = v.IP 651 } 652 if ip.IsLoopback() { 653 if ip.To4() == nil { 654 continue 655 } 656 return ifc.Name, ip.String(), addr.String(), nil 657 } 658 } 659 } 660 661 return "", "", "", fmt.Errorf("no loopback devices with IPV4 addr found") 662 } 663 664 // Leave is used gracefully exit. Clients will inform servers 665 // of their departure so that allocations can be rescheduled. 666 func (a *Agent) Leave() error { 667 if a.client != nil { 668 if err := a.client.Leave(); err != nil { 669 a.logger.Printf("[ERR] agent: client leave failed: %v", err) 670 } 671 } 672 if a.server != nil { 673 if err := a.server.Leave(); err != nil { 674 a.logger.Printf("[ERR] agent: server leave failed: %v", err) 675 } 676 } 677 return nil 678 } 679 680 // Shutdown is used to terminate the agent. 681 func (a *Agent) Shutdown() error { 682 a.shutdownLock.Lock() 683 defer a.shutdownLock.Unlock() 684 685 if a.shutdown { 686 return nil 687 } 688 689 a.logger.Println("[INFO] agent: requesting shutdown") 690 if a.client != nil { 691 if err := a.client.Shutdown(); err != nil { 692 a.logger.Printf("[ERR] agent: client shutdown failed: %v", err) 693 } 694 } 695 if a.server != nil { 696 if err := a.server.Shutdown(); err != nil { 697 a.logger.Printf("[ERR] agent: server shutdown failed: %v", err) 698 } 699 } 700 701 if err := a.consulSyncer.Shutdown(); err != nil { 702 a.logger.Printf("[ERR] agent: shutting down consul service failed: %v", err) 703 } 704 705 a.logger.Println("[INFO] agent: shutdown complete") 706 a.shutdown = true 707 close(a.shutdownCh) 708 return nil 709 } 710 711 // RPC is used to make an RPC call to the Nomad servers 712 func (a *Agent) RPC(method string, args interface{}, reply interface{}) error { 713 if a.server != nil { 714 return a.server.RPC(method, args, reply) 715 } 716 return a.client.RPC(method, args, reply) 717 } 718 719 // Client returns the configured client or nil 720 func (a *Agent) Client() *client.Client { 721 return a.client 722 } 723 724 // Server returns the configured server or nil 725 func (a *Agent) Server() *nomad.Server { 726 return a.server 727 } 728 729 // Stats is used to return statistics for debugging and insight 730 // for various sub-systems 731 func (a *Agent) Stats() map[string]map[string]string { 732 stats := make(map[string]map[string]string) 733 if a.server != nil { 734 subStat := a.server.Stats() 735 for k, v := range subStat { 736 stats[k] = v 737 } 738 } 739 if a.client != nil { 740 subStat := a.client.Stats() 741 for k, v := range subStat { 742 stats[k] = v 743 } 744 } 745 return stats 746 } 747 748 // setupConsulSyncer creates the Consul tasks used by this Nomad Agent 749 // (either Client or Server mode). 750 func (a *Agent) setupConsulSyncer() error { 751 var err error 752 a.consulSyncer, err = consul.NewSyncer(a.config.Consul, a.shutdownCh, a.logger) 753 if err != nil { 754 return err 755 } 756 757 a.consulSyncer.SetAddrFinder(func(portLabel string) (string, int) { 758 host, port, err := net.SplitHostPort(portLabel) 759 if err != nil { 760 p, err := strconv.Atoi(port) 761 if err != nil { 762 return "", 0 763 } 764 return "", p 765 } 766 767 // If the addr for the service is ":port", then we fall back 768 // to Nomad's default address resolution protocol. 769 // 770 // TODO(sean@): This should poll Consul to figure out what 771 // its advertise address is and use that in order to handle 772 // the case where there is something funky like NAT on this 773 // host. For now we just use the BindAddr if set, otherwise 774 // we fall back to a loopback addr. 775 if host == "" { 776 if a.config.BindAddr != "" { 777 host = a.config.BindAddr 778 } else { 779 host = "127.0.0.1" 780 } 781 } 782 p, err := strconv.Atoi(port) 783 if err != nil { 784 return host, 0 785 } 786 return host, p 787 }) 788 789 return nil 790 }