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