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  }