github.com/hspak/nomad@v0.7.2-0.20180309000617-bc4ae22a39a5/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  	"strings"
    12  	"sync"
    13  	"sync/atomic"
    14  	"time"
    15  
    16  	metrics "github.com/armon/go-metrics"
    17  	"github.com/hashicorp/consul/api"
    18  	version "github.com/hashicorp/go-version"
    19  	"github.com/hashicorp/nomad/client"
    20  	clientconfig "github.com/hashicorp/nomad/client/config"
    21  	"github.com/hashicorp/nomad/command/agent/consul"
    22  	"github.com/hashicorp/nomad/nomad"
    23  	"github.com/hashicorp/nomad/nomad/structs"
    24  	"github.com/hashicorp/nomad/nomad/structs/config"
    25  	"github.com/hashicorp/raft"
    26  )
    27  
    28  const (
    29  	agentHttpCheckInterval  = 10 * time.Second
    30  	agentHttpCheckTimeout   = 5 * time.Second
    31  	serverRpcCheckInterval  = 10 * time.Second
    32  	serverRpcCheckTimeout   = 3 * time.Second
    33  	serverSerfCheckInterval = 10 * time.Second
    34  	serverSerfCheckTimeout  = 3 * time.Second
    35  
    36  	// roles used in identifying Consul entries for Nomad agents
    37  	consulRoleServer = "server"
    38  	consulRoleClient = "client"
    39  )
    40  
    41  // Agent is a long running daemon that is used to run both
    42  // clients and servers. Servers are responsible for managing
    43  // state and making scheduling decisions. Clients can be
    44  // scheduled to, and are responsible for interfacing with
    45  // servers to run allocations.
    46  type Agent struct {
    47  	config     *Config
    48  	configLock sync.Mutex
    49  
    50  	logger    *log.Logger
    51  	logOutput io.Writer
    52  
    53  	// consulService is Nomad's custom Consul client for managing services
    54  	// and checks.
    55  	consulService *consul.ServiceClient
    56  
    57  	// consulCatalog is the subset of Consul's Catalog API Nomad uses.
    58  	consulCatalog consul.CatalogAPI
    59  
    60  	// consulSupportsTLSSkipVerify flags whether or not Nomad can register
    61  	// checks with TLSSkipVerify
    62  	consulSupportsTLSSkipVerify bool
    63  
    64  	client *client.Client
    65  
    66  	server *nomad.Server
    67  
    68  	shutdown     bool
    69  	shutdownCh   chan struct{}
    70  	shutdownLock sync.Mutex
    71  
    72  	InmemSink *metrics.InmemSink
    73  }
    74  
    75  // NewAgent is used to create a new agent with the given configuration
    76  func NewAgent(config *Config, logOutput io.Writer, inmem *metrics.InmemSink) (*Agent, error) {
    77  	a := &Agent{
    78  		config:     config,
    79  		logger:     log.New(logOutput, "", log.LstdFlags|log.Lmicroseconds),
    80  		logOutput:  logOutput,
    81  		shutdownCh: make(chan struct{}),
    82  		InmemSink:  inmem,
    83  	}
    84  
    85  	if err := a.setupConsul(config.Consul); err != nil {
    86  		return nil, fmt.Errorf("Failed to initialize Consul client: %v", err)
    87  	}
    88  	if err := a.setupServer(); err != nil {
    89  		return nil, err
    90  	}
    91  	if err := a.setupClient(); err != nil {
    92  		return nil, err
    93  	}
    94  	if a.client == nil && a.server == nil {
    95  		return nil, fmt.Errorf("must have at least client or server mode enabled")
    96  	}
    97  
    98  	return a, nil
    99  }
   100  
   101  // convertServerConfig takes an agent config and log output and returns a Nomad
   102  // Config.
   103  func convertServerConfig(agentConfig *Config, logOutput io.Writer) (*nomad.Config, error) {
   104  	conf := agentConfig.NomadConfig
   105  	if conf == nil {
   106  		conf = nomad.DefaultConfig()
   107  	}
   108  	conf.LogOutput = logOutput
   109  	conf.DevMode = agentConfig.DevMode
   110  	conf.Build = agentConfig.Version.VersionNumber()
   111  	if agentConfig.Region != "" {
   112  		conf.Region = agentConfig.Region
   113  	}
   114  
   115  	// Set the Authoritative Region if set, otherwise default to
   116  	// the same as the local region.
   117  	if agentConfig.Server.AuthoritativeRegion != "" {
   118  		conf.AuthoritativeRegion = agentConfig.Server.AuthoritativeRegion
   119  	} else if agentConfig.Region != "" {
   120  		conf.AuthoritativeRegion = agentConfig.Region
   121  	}
   122  
   123  	if agentConfig.Datacenter != "" {
   124  		conf.Datacenter = agentConfig.Datacenter
   125  	}
   126  	if agentConfig.NodeName != "" {
   127  		conf.NodeName = agentConfig.NodeName
   128  	}
   129  	if agentConfig.Server.BootstrapExpect > 0 {
   130  		if agentConfig.Server.BootstrapExpect == 1 {
   131  			conf.Bootstrap = true
   132  		} else {
   133  			atomic.StoreInt32(&conf.BootstrapExpect, int32(agentConfig.Server.BootstrapExpect))
   134  		}
   135  	}
   136  	if agentConfig.DataDir != "" {
   137  		conf.DataDir = filepath.Join(agentConfig.DataDir, "server")
   138  	}
   139  	if agentConfig.Server.DataDir != "" {
   140  		conf.DataDir = agentConfig.Server.DataDir
   141  	}
   142  	if agentConfig.Server.ProtocolVersion != 0 {
   143  		conf.ProtocolVersion = uint8(agentConfig.Server.ProtocolVersion)
   144  	}
   145  	if agentConfig.Server.RaftProtocol != 0 {
   146  		conf.RaftConfig.ProtocolVersion = raft.ProtocolVersion(agentConfig.Server.RaftProtocol)
   147  	}
   148  	if agentConfig.Server.NumSchedulers != 0 {
   149  		conf.NumSchedulers = agentConfig.Server.NumSchedulers
   150  	}
   151  	if len(agentConfig.Server.EnabledSchedulers) != 0 {
   152  		conf.EnabledSchedulers = agentConfig.Server.EnabledSchedulers
   153  	}
   154  	if agentConfig.ACL.Enabled {
   155  		conf.ACLEnabled = true
   156  	}
   157  	if agentConfig.ACL.ReplicationToken != "" {
   158  		conf.ReplicationToken = agentConfig.ACL.ReplicationToken
   159  	}
   160  	if agentConfig.Sentinel != nil {
   161  		conf.SentinelConfig = agentConfig.Sentinel
   162  	}
   163  	if agentConfig.Server.NonVotingServer {
   164  		conf.NonVoter = true
   165  	}
   166  	if agentConfig.Server.RedundancyZone != "" {
   167  		conf.RedundancyZone = agentConfig.Server.RedundancyZone
   168  	}
   169  	if agentConfig.Server.UpgradeVersion != "" {
   170  		conf.UpgradeVersion = agentConfig.Server.UpgradeVersion
   171  	}
   172  	if agentConfig.Autopilot != nil {
   173  		if agentConfig.Autopilot.CleanupDeadServers != nil {
   174  			conf.AutopilotConfig.CleanupDeadServers = *agentConfig.Autopilot.CleanupDeadServers
   175  		}
   176  		if agentConfig.Autopilot.ServerStabilizationTime != 0 {
   177  			conf.AutopilotConfig.ServerStabilizationTime = agentConfig.Autopilot.ServerStabilizationTime
   178  		}
   179  		if agentConfig.Autopilot.LastContactThreshold != 0 {
   180  			conf.AutopilotConfig.LastContactThreshold = agentConfig.Autopilot.LastContactThreshold
   181  		}
   182  		if agentConfig.Autopilot.MaxTrailingLogs != 0 {
   183  			conf.AutopilotConfig.MaxTrailingLogs = uint64(agentConfig.Autopilot.MaxTrailingLogs)
   184  		}
   185  		if agentConfig.Autopilot.EnableRedundancyZones != nil {
   186  			conf.AutopilotConfig.EnableRedundancyZones = *agentConfig.Autopilot.EnableRedundancyZones
   187  		}
   188  		if agentConfig.Autopilot.DisableUpgradeMigration != nil {
   189  			conf.AutopilotConfig.DisableUpgradeMigration = *agentConfig.Autopilot.DisableUpgradeMigration
   190  		}
   191  		if agentConfig.Autopilot.EnableCustomUpgrades != nil {
   192  			conf.AutopilotConfig.EnableCustomUpgrades = *agentConfig.Autopilot.EnableCustomUpgrades
   193  		}
   194  	}
   195  
   196  	// Set up the bind addresses
   197  	rpcAddr, err := net.ResolveTCPAddr("tcp", agentConfig.normalizedAddrs.RPC)
   198  	if err != nil {
   199  		return nil, fmt.Errorf("Failed to parse RPC address %q: %v", agentConfig.normalizedAddrs.RPC, err)
   200  	}
   201  	serfAddr, err := net.ResolveTCPAddr("tcp", agentConfig.normalizedAddrs.Serf)
   202  	if err != nil {
   203  		return nil, fmt.Errorf("Failed to parse Serf address %q: %v", agentConfig.normalizedAddrs.Serf, err)
   204  	}
   205  	conf.RPCAddr.Port = rpcAddr.Port
   206  	conf.RPCAddr.IP = rpcAddr.IP
   207  	conf.SerfConfig.MemberlistConfig.BindPort = serfAddr.Port
   208  	conf.SerfConfig.MemberlistConfig.BindAddr = serfAddr.IP.String()
   209  
   210  	// Set up the advertise addresses
   211  	rpcAddr, err = net.ResolveTCPAddr("tcp", agentConfig.AdvertiseAddrs.RPC)
   212  	if err != nil {
   213  		return nil, fmt.Errorf("Failed to parse RPC advertise address %q: %v", agentConfig.AdvertiseAddrs.RPC, err)
   214  	}
   215  	serfAddr, err = net.ResolveTCPAddr("tcp", agentConfig.AdvertiseAddrs.Serf)
   216  	if err != nil {
   217  		return nil, fmt.Errorf("Failed to parse Serf advertise address %q: %v", agentConfig.AdvertiseAddrs.Serf, err)
   218  	}
   219  	conf.RPCAdvertise = rpcAddr
   220  	conf.SerfConfig.MemberlistConfig.AdvertiseAddr = serfAddr.IP.String()
   221  	conf.SerfConfig.MemberlistConfig.AdvertisePort = serfAddr.Port
   222  
   223  	// Set up gc threshold and heartbeat grace period
   224  	if gcThreshold := agentConfig.Server.NodeGCThreshold; gcThreshold != "" {
   225  		dur, err := time.ParseDuration(gcThreshold)
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  		conf.NodeGCThreshold = dur
   230  	}
   231  	if gcThreshold := agentConfig.Server.JobGCThreshold; gcThreshold != "" {
   232  		dur, err := time.ParseDuration(gcThreshold)
   233  		if err != nil {
   234  			return nil, err
   235  		}
   236  		conf.JobGCThreshold = dur
   237  	}
   238  	if gcThreshold := agentConfig.Server.EvalGCThreshold; gcThreshold != "" {
   239  		dur, err := time.ParseDuration(gcThreshold)
   240  		if err != nil {
   241  			return nil, err
   242  		}
   243  		conf.EvalGCThreshold = dur
   244  	}
   245  	if gcThreshold := agentConfig.Server.DeploymentGCThreshold; gcThreshold != "" {
   246  		dur, err := time.ParseDuration(gcThreshold)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  		conf.DeploymentGCThreshold = dur
   251  	}
   252  
   253  	if heartbeatGrace := agentConfig.Server.HeartbeatGrace; heartbeatGrace != 0 {
   254  		conf.HeartbeatGrace = heartbeatGrace
   255  	}
   256  	if min := agentConfig.Server.MinHeartbeatTTL; min != 0 {
   257  		conf.MinHeartbeatTTL = min
   258  	}
   259  	if maxHPS := agentConfig.Server.MaxHeartbeatsPerSecond; maxHPS != 0 {
   260  		conf.MaxHeartbeatsPerSecond = maxHPS
   261  	}
   262  
   263  	if *agentConfig.Consul.AutoAdvertise && agentConfig.Consul.ServerServiceName == "" {
   264  		return nil, fmt.Errorf("server_service_name must be set when auto_advertise is enabled")
   265  	}
   266  
   267  	// Add the Consul and Vault configs
   268  	conf.ConsulConfig = agentConfig.Consul
   269  	conf.VaultConfig = agentConfig.Vault
   270  
   271  	// Set the TLS config
   272  	conf.TLSConfig = agentConfig.TLSConfig
   273  
   274  	// Setup telemetry related config
   275  	conf.StatsCollectionInterval = agentConfig.Telemetry.collectionInterval
   276  	conf.DisableTaggedMetrics = agentConfig.Telemetry.DisableTaggedMetrics
   277  	conf.BackwardsCompatibleMetrics = agentConfig.Telemetry.BackwardsCompatibleMetrics
   278  
   279  	return conf, nil
   280  }
   281  
   282  // serverConfig is used to generate a new server configuration struct
   283  // for initializing a nomad server.
   284  func (a *Agent) serverConfig() (*nomad.Config, error) {
   285  	return convertServerConfig(a.config, a.logOutput)
   286  }
   287  
   288  // clientConfig is used to generate a new client configuration struct
   289  // for initializing a Nomad client.
   290  func (a *Agent) clientConfig() (*clientconfig.Config, error) {
   291  	// Setup the configuration
   292  	conf := a.config.ClientConfig
   293  	if conf == nil {
   294  		conf = clientconfig.DefaultConfig()
   295  	}
   296  	if a.server != nil {
   297  		conf.RPCHandler = a.server
   298  	}
   299  	conf.LogOutput = a.logOutput
   300  	conf.LogLevel = a.config.LogLevel
   301  	conf.DevMode = a.config.DevMode
   302  	if a.config.Region != "" {
   303  		conf.Region = a.config.Region
   304  	}
   305  	if a.config.DataDir != "" {
   306  		conf.StateDir = filepath.Join(a.config.DataDir, "client")
   307  		conf.AllocDir = filepath.Join(a.config.DataDir, "alloc")
   308  	}
   309  	if a.config.Client.StateDir != "" {
   310  		conf.StateDir = a.config.Client.StateDir
   311  	}
   312  	if a.config.Client.AllocDir != "" {
   313  		conf.AllocDir = a.config.Client.AllocDir
   314  	}
   315  	conf.Servers = a.config.Client.Servers
   316  	if a.config.Client.NetworkInterface != "" {
   317  		conf.NetworkInterface = a.config.Client.NetworkInterface
   318  	}
   319  	conf.ChrootEnv = a.config.Client.ChrootEnv
   320  	conf.Options = a.config.Client.Options
   321  	// Logging deprecation messages about consul related configuration in client
   322  	// options
   323  	var invalidConsulKeys []string
   324  	for key := range conf.Options {
   325  		if strings.HasPrefix(key, "consul") {
   326  			invalidConsulKeys = append(invalidConsulKeys, fmt.Sprintf("options.%s", key))
   327  		}
   328  	}
   329  	if len(invalidConsulKeys) > 0 {
   330  		a.logger.Printf("[WARN] agent: Invalid keys: %v", strings.Join(invalidConsulKeys, ","))
   331  		a.logger.Printf(`Nomad client ignores consul related configuration in client options.
   332  		Please refer to the guide https://www.nomadproject.io/docs/agent/configuration/consul.html
   333  		to configure Nomad to work with Consul.`)
   334  	}
   335  
   336  	if a.config.Client.NetworkSpeed != 0 {
   337  		conf.NetworkSpeed = a.config.Client.NetworkSpeed
   338  	}
   339  	if a.config.Client.CpuCompute != 0 {
   340  		conf.CpuCompute = a.config.Client.CpuCompute
   341  	}
   342  	if a.config.Client.MaxKillTimeout != "" {
   343  		dur, err := time.ParseDuration(a.config.Client.MaxKillTimeout)
   344  		if err != nil {
   345  			return nil, fmt.Errorf("Error parsing max kill timeout: %s", err)
   346  		}
   347  		conf.MaxKillTimeout = dur
   348  	}
   349  	conf.ClientMaxPort = uint(a.config.Client.ClientMaxPort)
   350  	conf.ClientMinPort = uint(a.config.Client.ClientMinPort)
   351  
   352  	// Setup the node
   353  	conf.Node = new(structs.Node)
   354  	conf.Node.Datacenter = a.config.Datacenter
   355  	conf.Node.Name = a.config.NodeName
   356  	conf.Node.Meta = a.config.Client.Meta
   357  	conf.Node.NodeClass = a.config.Client.NodeClass
   358  
   359  	// Set up the HTTP advertise address
   360  	conf.Node.HTTPAddr = a.config.AdvertiseAddrs.HTTP
   361  
   362  	// Reserve resources on the node.
   363  	r := conf.Node.Reserved
   364  	if r == nil {
   365  		r = new(structs.Resources)
   366  		conf.Node.Reserved = r
   367  	}
   368  	r.CPU = a.config.Client.Reserved.CPU
   369  	r.MemoryMB = a.config.Client.Reserved.MemoryMB
   370  	r.DiskMB = a.config.Client.Reserved.DiskMB
   371  	r.IOPS = a.config.Client.Reserved.IOPS
   372  	conf.GloballyReservedPorts = a.config.Client.Reserved.ParsedReservedPorts
   373  
   374  	conf.Version = a.config.Version
   375  
   376  	if *a.config.Consul.AutoAdvertise && a.config.Consul.ClientServiceName == "" {
   377  		return nil, fmt.Errorf("client_service_name must be set when auto_advertise is enabled")
   378  	}
   379  
   380  	conf.ConsulConfig = a.config.Consul
   381  	conf.VaultConfig = a.config.Vault
   382  
   383  	// Set up Telemetry configuration
   384  	conf.StatsCollectionInterval = a.config.Telemetry.collectionInterval
   385  	conf.PublishNodeMetrics = a.config.Telemetry.PublishNodeMetrics
   386  	conf.PublishAllocationMetrics = a.config.Telemetry.PublishAllocationMetrics
   387  	conf.DisableTaggedMetrics = a.config.Telemetry.DisableTaggedMetrics
   388  	conf.BackwardsCompatibleMetrics = a.config.Telemetry.BackwardsCompatibleMetrics
   389  
   390  	// Set the TLS related configs
   391  	conf.TLSConfig = a.config.TLSConfig
   392  	conf.Node.TLSEnabled = conf.TLSConfig.EnableHTTP
   393  
   394  	// Set the GC related configs
   395  	conf.GCInterval = a.config.Client.GCInterval
   396  	conf.GCParallelDestroys = a.config.Client.GCParallelDestroys
   397  	conf.GCDiskUsageThreshold = a.config.Client.GCDiskUsageThreshold
   398  	conf.GCInodeUsageThreshold = a.config.Client.GCInodeUsageThreshold
   399  	conf.GCMaxAllocs = a.config.Client.GCMaxAllocs
   400  	if a.config.Client.NoHostUUID != nil {
   401  		conf.NoHostUUID = *a.config.Client.NoHostUUID
   402  	} else {
   403  		// Default no_host_uuid to true
   404  		conf.NoHostUUID = true
   405  	}
   406  
   407  	// Setup the ACLs
   408  	conf.ACLEnabled = a.config.ACL.Enabled
   409  	conf.ACLTokenTTL = a.config.ACL.TokenTTL
   410  	conf.ACLPolicyTTL = a.config.ACL.PolicyTTL
   411  
   412  	return conf, nil
   413  }
   414  
   415  // setupServer is used to setup the server if enabled
   416  func (a *Agent) setupServer() error {
   417  	if !a.config.Server.Enabled {
   418  		return nil
   419  	}
   420  
   421  	// Setup the configuration
   422  	conf, err := a.serverConfig()
   423  	if err != nil {
   424  		return fmt.Errorf("server config setup failed: %s", err)
   425  	}
   426  
   427  	// Sets up the keyring for gossip encryption
   428  	if err := a.setupKeyrings(conf); err != nil {
   429  		return fmt.Errorf("failed to configure keyring: %v", err)
   430  	}
   431  
   432  	// Create the server
   433  	server, err := nomad.NewServer(conf, a.consulCatalog, a.logger)
   434  	if err != nil {
   435  		return fmt.Errorf("server setup failed: %v", err)
   436  	}
   437  	a.server = server
   438  
   439  	// Consul check addresses default to bind but can be toggled to use advertise
   440  	rpcCheckAddr := a.config.normalizedAddrs.RPC
   441  	serfCheckAddr := a.config.normalizedAddrs.Serf
   442  	if *a.config.Consul.ChecksUseAdvertise {
   443  		rpcCheckAddr = a.config.AdvertiseAddrs.RPC
   444  		serfCheckAddr = a.config.AdvertiseAddrs.Serf
   445  	}
   446  
   447  	// Create the Nomad Server services for Consul
   448  	if *a.config.Consul.AutoAdvertise {
   449  		httpServ := &structs.Service{
   450  			Name:      a.config.Consul.ServerServiceName,
   451  			PortLabel: a.config.AdvertiseAddrs.HTTP,
   452  			Tags:      []string{consul.ServiceTagHTTP},
   453  		}
   454  		const isServer = true
   455  		if check := a.agentHTTPCheck(isServer); check != nil {
   456  			httpServ.Checks = []*structs.ServiceCheck{check}
   457  		}
   458  		rpcServ := &structs.Service{
   459  			Name:      a.config.Consul.ServerServiceName,
   460  			PortLabel: a.config.AdvertiseAddrs.RPC,
   461  			Tags:      []string{consul.ServiceTagRPC},
   462  			Checks: []*structs.ServiceCheck{
   463  				{
   464  					Name:      "Nomad Server RPC Check",
   465  					Type:      "tcp",
   466  					Interval:  serverRpcCheckInterval,
   467  					Timeout:   serverRpcCheckTimeout,
   468  					PortLabel: rpcCheckAddr,
   469  				},
   470  			},
   471  		}
   472  		serfServ := &structs.Service{
   473  			Name:      a.config.Consul.ServerServiceName,
   474  			PortLabel: a.config.AdvertiseAddrs.Serf,
   475  			Tags:      []string{consul.ServiceTagSerf},
   476  			Checks: []*structs.ServiceCheck{
   477  				{
   478  					Name:      "Nomad Server Serf Check",
   479  					Type:      "tcp",
   480  					Interval:  serverSerfCheckInterval,
   481  					Timeout:   serverSerfCheckTimeout,
   482  					PortLabel: serfCheckAddr,
   483  				},
   484  			},
   485  		}
   486  
   487  		// Add the http port check if TLS isn't enabled
   488  		consulServices := []*structs.Service{
   489  			rpcServ,
   490  			serfServ,
   491  			httpServ,
   492  		}
   493  		if err := a.consulService.RegisterAgent(consulRoleServer, consulServices); err != nil {
   494  			return err
   495  		}
   496  	}
   497  
   498  	return nil
   499  }
   500  
   501  // setupKeyrings is used to initialize and load keyrings during agent startup
   502  func (a *Agent) setupKeyrings(config *nomad.Config) error {
   503  	file := filepath.Join(a.config.DataDir, serfKeyring)
   504  
   505  	if a.config.Server.EncryptKey == "" {
   506  		goto LOAD
   507  	}
   508  	if _, err := os.Stat(file); err != nil {
   509  		if err := initKeyring(file, a.config.Server.EncryptKey); err != nil {
   510  			return err
   511  		}
   512  	}
   513  
   514  LOAD:
   515  	if _, err := os.Stat(file); err == nil {
   516  		config.SerfConfig.KeyringFile = file
   517  	}
   518  	if err := loadKeyringFile(config.SerfConfig); err != nil {
   519  		return err
   520  	}
   521  	// Success!
   522  	return nil
   523  }
   524  
   525  // setupClient is used to setup the client if enabled
   526  func (a *Agent) setupClient() error {
   527  	if !a.config.Client.Enabled {
   528  		return nil
   529  	}
   530  
   531  	// Setup the configuration
   532  	conf, err := a.clientConfig()
   533  	if err != nil {
   534  		return fmt.Errorf("client setup failed: %v", err)
   535  	}
   536  
   537  	// Reserve some ports for the plugins if we are on Windows
   538  	if runtime.GOOS == "windows" {
   539  		if err := a.reservePortsForClient(conf); err != nil {
   540  			return err
   541  		}
   542  	}
   543  
   544  	client, err := client.NewClient(conf, a.consulCatalog, a.consulService, a.logger)
   545  	if err != nil {
   546  		return fmt.Errorf("client setup failed: %v", err)
   547  	}
   548  	a.client = client
   549  
   550  	// Create the Nomad Client  services for Consul
   551  	if *a.config.Consul.AutoAdvertise {
   552  		httpServ := &structs.Service{
   553  			Name:      a.config.Consul.ClientServiceName,
   554  			PortLabel: a.config.AdvertiseAddrs.HTTP,
   555  			Tags:      []string{consul.ServiceTagHTTP},
   556  		}
   557  		const isServer = false
   558  		if check := a.agentHTTPCheck(isServer); check != nil {
   559  			httpServ.Checks = []*structs.ServiceCheck{check}
   560  		}
   561  		if err := a.consulService.RegisterAgent(consulRoleClient, []*structs.Service{httpServ}); err != nil {
   562  			return err
   563  		}
   564  	}
   565  
   566  	return nil
   567  }
   568  
   569  // agentHTTPCheck returns a health check for the agent's HTTP API if possible.
   570  // If no HTTP health check can be supported nil is returned.
   571  func (a *Agent) agentHTTPCheck(server bool) *structs.ServiceCheck {
   572  	// Resolve the http check address
   573  	httpCheckAddr := a.config.normalizedAddrs.HTTP
   574  	if *a.config.Consul.ChecksUseAdvertise {
   575  		httpCheckAddr = a.config.AdvertiseAddrs.HTTP
   576  	}
   577  	check := structs.ServiceCheck{
   578  		Name:      "Nomad Client HTTP Check",
   579  		Type:      "http",
   580  		Path:      "/v1/agent/health?type=client",
   581  		Protocol:  "http",
   582  		Interval:  agentHttpCheckInterval,
   583  		Timeout:   agentHttpCheckTimeout,
   584  		PortLabel: httpCheckAddr,
   585  	}
   586  	// Switch to endpoint that doesn't require a leader for servers
   587  	if server {
   588  		check.Name = "Nomad Server HTTP Check"
   589  		check.Path = "/v1/agent/health?type=server"
   590  	}
   591  	if !a.config.TLSConfig.EnableHTTP {
   592  		// No HTTPS, return a plain http check
   593  		return &check
   594  	}
   595  	if !a.consulSupportsTLSSkipVerify {
   596  		a.logger.Printf("[WARN] agent: not registering Nomad HTTPS Health Check because it requires Consul>=0.7.2")
   597  		return nil
   598  	}
   599  	if a.config.TLSConfig.VerifyHTTPSClient {
   600  		a.logger.Printf("[WARN] agent: not registering Nomad HTTPS Health Check because verify_https_client enabled")
   601  		return nil
   602  	}
   603  
   604  	// HTTPS enabled; skip verification
   605  	check.Protocol = "https"
   606  	check.TLSSkipVerify = true
   607  	return &check
   608  }
   609  
   610  // reservePortsForClient reserves a range of ports for the client to use when
   611  // it creates various plugins for log collection, executors, drivers, etc
   612  func (a *Agent) reservePortsForClient(conf *clientconfig.Config) error {
   613  	// finding the device name for loopback
   614  	deviceName, addr, mask, err := a.findLoopbackDevice()
   615  	if err != nil {
   616  		return fmt.Errorf("error finding the device name for loopback: %v", err)
   617  	}
   618  
   619  	// seeing if the user has already reserved some resources on this device
   620  	var nr *structs.NetworkResource
   621  	if conf.Node.Reserved == nil {
   622  		conf.Node.Reserved = &structs.Resources{}
   623  	}
   624  	for _, n := range conf.Node.Reserved.Networks {
   625  		if n.Device == deviceName {
   626  			nr = n
   627  		}
   628  	}
   629  	// If the user hasn't already created the device, we create it
   630  	if nr == nil {
   631  		nr = &structs.NetworkResource{
   632  			Device:        deviceName,
   633  			IP:            addr,
   634  			CIDR:          mask,
   635  			ReservedPorts: make([]structs.Port, 0),
   636  		}
   637  	}
   638  	// appending the port ranges we want to use for the client to the list of
   639  	// reserved ports for this device
   640  	for i := conf.ClientMinPort; i <= conf.ClientMaxPort; i++ {
   641  		nr.ReservedPorts = append(nr.ReservedPorts, structs.Port{Label: fmt.Sprintf("plugin-%d", i), Value: int(i)})
   642  	}
   643  	conf.Node.Reserved.Networks = append(conf.Node.Reserved.Networks, nr)
   644  	return nil
   645  }
   646  
   647  // findLoopbackDevice iterates through all the interfaces on a machine and
   648  // returns the ip addr, mask of the loopback device
   649  func (a *Agent) findLoopbackDevice() (string, string, string, error) {
   650  	var ifcs []net.Interface
   651  	var err error
   652  	ifcs, err = net.Interfaces()
   653  	if err != nil {
   654  		return "", "", "", err
   655  	}
   656  	for _, ifc := range ifcs {
   657  		addrs, err := ifc.Addrs()
   658  		if err != nil {
   659  			return "", "", "", err
   660  		}
   661  		for _, addr := range addrs {
   662  			var ip net.IP
   663  			switch v := addr.(type) {
   664  			case *net.IPNet:
   665  				ip = v.IP
   666  			case *net.IPAddr:
   667  				ip = v.IP
   668  			}
   669  			if ip.IsLoopback() {
   670  				if ip.To4() == nil {
   671  					continue
   672  				}
   673  				return ifc.Name, ip.String(), addr.String(), nil
   674  			}
   675  		}
   676  	}
   677  
   678  	return "", "", "", fmt.Errorf("no loopback devices with IPV4 addr found")
   679  }
   680  
   681  // Leave is used gracefully exit. Clients will inform servers
   682  // of their departure so that allocations can be rescheduled.
   683  func (a *Agent) Leave() error {
   684  	if a.client != nil {
   685  		if err := a.client.Leave(); err != nil {
   686  			a.logger.Printf("[ERR] agent: client leave failed: %v", err)
   687  		}
   688  	}
   689  	if a.server != nil {
   690  		if err := a.server.Leave(); err != nil {
   691  			a.logger.Printf("[ERR] agent: server leave failed: %v", err)
   692  		}
   693  	}
   694  	return nil
   695  }
   696  
   697  // Shutdown is used to terminate the agent.
   698  func (a *Agent) Shutdown() error {
   699  	a.shutdownLock.Lock()
   700  	defer a.shutdownLock.Unlock()
   701  
   702  	if a.shutdown {
   703  		return nil
   704  	}
   705  
   706  	a.logger.Println("[INFO] agent: requesting shutdown")
   707  	if a.client != nil {
   708  		if err := a.client.Shutdown(); err != nil {
   709  			a.logger.Printf("[ERR] agent: client shutdown failed: %v", err)
   710  		}
   711  	}
   712  	if a.server != nil {
   713  		if err := a.server.Shutdown(); err != nil {
   714  			a.logger.Printf("[ERR] agent: server shutdown failed: %v", err)
   715  		}
   716  	}
   717  
   718  	if err := a.consulService.Shutdown(); err != nil {
   719  		a.logger.Printf("[ERR] agent: shutting down Consul client failed: %v", err)
   720  	}
   721  
   722  	a.logger.Println("[INFO] agent: shutdown complete")
   723  	a.shutdown = true
   724  	close(a.shutdownCh)
   725  	return nil
   726  }
   727  
   728  // RPC is used to make an RPC call to the Nomad servers
   729  func (a *Agent) RPC(method string, args interface{}, reply interface{}) error {
   730  	if a.server != nil {
   731  		return a.server.RPC(method, args, reply)
   732  	}
   733  	return a.client.RPC(method, args, reply)
   734  }
   735  
   736  // Client returns the configured client or nil
   737  func (a *Agent) Client() *client.Client {
   738  	return a.client
   739  }
   740  
   741  // Server returns the configured server or nil
   742  func (a *Agent) Server() *nomad.Server {
   743  	return a.server
   744  }
   745  
   746  // Stats is used to return statistics for debugging and insight
   747  // for various sub-systems
   748  func (a *Agent) Stats() map[string]map[string]string {
   749  	stats := make(map[string]map[string]string)
   750  	if a.server != nil {
   751  		subStat := a.server.Stats()
   752  		for k, v := range subStat {
   753  			stats[k] = v
   754  		}
   755  	}
   756  	if a.client != nil {
   757  		subStat := a.client.Stats()
   758  		for k, v := range subStat {
   759  			stats[k] = v
   760  		}
   761  	}
   762  	return stats
   763  }
   764  
   765  // ShouldReload determines if we should reload the configuration and agent
   766  // connections. If the TLS Configuration has not changed, we shouldn't reload.
   767  func (a *Agent) ShouldReload(newConfig *Config) (bool, bool) {
   768  	a.configLock.Lock()
   769  	defer a.configLock.Unlock()
   770  	if a.config.TLSConfig.Equals(newConfig.TLSConfig) {
   771  		return false, false
   772  	}
   773  
   774  	return true, true // requires a reload of both agent and http server
   775  }
   776  
   777  // Reload handles configuration changes for the agent. Provides a method that
   778  // is easier to unit test, as this action is invoked via SIGHUP.
   779  func (a *Agent) Reload(newConfig *Config) error {
   780  	a.configLock.Lock()
   781  	defer a.configLock.Unlock()
   782  
   783  	if newConfig == nil || newConfig.TLSConfig == nil {
   784  		return fmt.Errorf("cannot reload agent with nil configuration")
   785  	}
   786  
   787  	// This is just a TLS configuration reload, we don't need to refresh
   788  	// existing network connections
   789  	if !a.config.TLSConfig.IsEmpty() && !newConfig.TLSConfig.IsEmpty() {
   790  
   791  		// Reload the certificates on the keyloader and on success store the
   792  		// updated TLS config. It is important to reuse the same keyloader
   793  		// as this allows us to dynamically reload configurations not only
   794  		// on the Agent but on the Server and Client too (they are
   795  		// referencing the same keyloader).
   796  		keyloader := a.config.TLSConfig.GetKeyLoader()
   797  		_, err := keyloader.LoadKeyPair(newConfig.TLSConfig.CertFile, newConfig.TLSConfig.KeyFile)
   798  		if err != nil {
   799  			return err
   800  		}
   801  		a.config.TLSConfig = newConfig.TLSConfig
   802  		a.config.TLSConfig.KeyLoader = keyloader
   803  		return nil
   804  	}
   805  
   806  	// Completely reload the agent's TLS configuration (moving from non-TLS to
   807  	// TLS, or vice versa)
   808  	// This does not handle errors in loading the new TLS configuration
   809  	a.config.TLSConfig = newConfig.TLSConfig.Copy()
   810  
   811  	if newConfig.TLSConfig.IsEmpty() {
   812  		a.logger.Println("[WARN] agent: Downgrading agent's existing TLS configuration to plaintext")
   813  	} else {
   814  		a.logger.Println("[INFO] agent: Upgrading from plaintext configuration to TLS")
   815  	}
   816  
   817  	return nil
   818  }
   819  
   820  // GetConfig creates a locked reference to the agent's config
   821  func (a *Agent) GetConfig() *Config {
   822  	a.configLock.Lock()
   823  	defer a.configLock.Unlock()
   824  
   825  	return a.config
   826  }
   827  
   828  // setupConsul creates the Consul client and starts its main Run loop.
   829  func (a *Agent) setupConsul(consulConfig *config.ConsulConfig) error {
   830  	apiConf, err := consulConfig.ApiConfig()
   831  	if err != nil {
   832  		return err
   833  	}
   834  	client, err := api.NewClient(apiConf)
   835  	if err != nil {
   836  		return err
   837  	}
   838  
   839  	// Determine version for TLSSkipVerify
   840  	if self, err := client.Agent().Self(); err == nil {
   841  		a.consulSupportsTLSSkipVerify = consulSupportsTLSSkipVerify(self)
   842  	}
   843  
   844  	// Create Consul Catalog client for service discovery.
   845  	a.consulCatalog = client.Catalog()
   846  
   847  	// Create Consul Service client for service advertisement and checks.
   848  	a.consulService = consul.NewServiceClient(client.Agent(), a.consulSupportsTLSSkipVerify, a.logger)
   849  
   850  	// Run the Consul service client's sync'ing main loop
   851  	go a.consulService.Run()
   852  	return nil
   853  }
   854  
   855  var consulTLSSkipVerifyMinVersion = version.Must(version.NewVersion("0.7.2"))
   856  
   857  // consulSupportsTLSSkipVerify returns true if Consul supports TLSSkipVerify.
   858  func consulSupportsTLSSkipVerify(self map[string]map[string]interface{}) bool {
   859  	member, ok := self["Member"]
   860  	if !ok {
   861  		return false
   862  	}
   863  	tagsI, ok := member["Tags"]
   864  	if !ok {
   865  		return false
   866  	}
   867  	tags, ok := tagsI.(map[string]interface{})
   868  	if !ok {
   869  		return false
   870  	}
   871  	buildI, ok := tags["build"]
   872  	if !ok {
   873  		return false
   874  	}
   875  	build, ok := buildI.(string)
   876  	if !ok {
   877  		return false
   878  	}
   879  	parts := strings.SplitN(build, ":", 2)
   880  	if len(parts) != 2 {
   881  		return false
   882  	}
   883  	v, err := version.NewVersion(parts[0])
   884  	if err != nil {
   885  		return false
   886  	}
   887  	if v.LessThan(consulTLSSkipVerifyMinVersion) {
   888  		return false
   889  	}
   890  	return true
   891  }