github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/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  	"sync"
    11  
    12  	"github.com/hashicorp/nomad/client"
    13  	"github.com/hashicorp/nomad/nomad"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  )
    16  
    17  // Agent is a long running daemon that is used to run both
    18  // clients and servers. Servers are responsible for managing
    19  // state and making scheduling decisions. Clients can be
    20  // scheduled to, and are responsible for interfacing with
    21  // servers to run allocations.
    22  type Agent struct {
    23  	config    *Config
    24  	logger    *log.Logger
    25  	logOutput io.Writer
    26  
    27  	server *nomad.Server
    28  	client *client.Client
    29  
    30  	shutdown     bool
    31  	shutdownCh   chan struct{}
    32  	shutdownLock sync.Mutex
    33  }
    34  
    35  // NewAgent is used to create a new agent with the given configuration
    36  func NewAgent(config *Config, logOutput io.Writer) (*Agent, error) {
    37  	// Ensure we have a log sink
    38  	if logOutput == nil {
    39  		logOutput = os.Stderr
    40  	}
    41  
    42  	a := &Agent{
    43  		config:     config,
    44  		logger:     log.New(logOutput, "", log.LstdFlags),
    45  		logOutput:  logOutput,
    46  		shutdownCh: make(chan struct{}),
    47  	}
    48  
    49  	if err := a.setupServer(); err != nil {
    50  		return nil, err
    51  	}
    52  	if err := a.setupClient(); err != nil {
    53  		return nil, err
    54  	}
    55  	if a.client == nil && a.server == nil {
    56  		return nil, fmt.Errorf("must have at least client or server mode enabled")
    57  	}
    58  	return a, nil
    59  }
    60  
    61  // serverConfig is used to generate a new server configuration struct
    62  // for initializing a nomad server.
    63  func (a *Agent) serverConfig() (*nomad.Config, error) {
    64  	conf := a.config.NomadConfig
    65  	if conf == nil {
    66  		conf = nomad.DefaultConfig()
    67  	}
    68  	conf.LogOutput = a.logOutput
    69  	conf.DevMode = a.config.DevMode
    70  	conf.Build = fmt.Sprintf("%s%s", a.config.Version, a.config.VersionPrerelease)
    71  	if a.config.Region != "" {
    72  		conf.Region = a.config.Region
    73  	}
    74  	if a.config.Datacenter != "" {
    75  		conf.Datacenter = a.config.Datacenter
    76  	}
    77  	if a.config.NodeName != "" {
    78  		conf.NodeName = a.config.NodeName
    79  	}
    80  	if a.config.Server.BootstrapExpect > 0 {
    81  		if a.config.Server.BootstrapExpect == 1 {
    82  			conf.Bootstrap = true
    83  		} else {
    84  			conf.BootstrapExpect = a.config.Server.BootstrapExpect
    85  		}
    86  	}
    87  	if a.config.DataDir != "" {
    88  		conf.DataDir = filepath.Join(a.config.DataDir, "server")
    89  	}
    90  	if a.config.Server.DataDir != "" {
    91  		conf.DataDir = a.config.Server.DataDir
    92  	}
    93  	if a.config.Server.ProtocolVersion != 0 {
    94  		conf.ProtocolVersion = uint8(a.config.Server.ProtocolVersion)
    95  	}
    96  	if a.config.Server.NumSchedulers != 0 {
    97  		conf.NumSchedulers = a.config.Server.NumSchedulers
    98  	}
    99  	if len(a.config.Server.EnabledSchedulers) != 0 {
   100  		conf.EnabledSchedulers = a.config.Server.EnabledSchedulers
   101  	}
   102  
   103  	// Set up the advertise addrs
   104  	if addr := a.config.AdvertiseAddrs.Serf; addr != "" {
   105  		serfAddr, err := net.ResolveTCPAddr("tcp", addr)
   106  		if err != nil {
   107  			return nil, fmt.Errorf("error resolving serf advertise address: %s", err)
   108  		}
   109  		conf.SerfConfig.MemberlistConfig.AdvertiseAddr = serfAddr.IP.String()
   110  		conf.SerfConfig.MemberlistConfig.AdvertisePort = serfAddr.Port
   111  	}
   112  	if addr := a.config.AdvertiseAddrs.RPC; addr != "" {
   113  		rpcAddr, err := net.ResolveTCPAddr("tcp", addr)
   114  		if err != nil {
   115  			return nil, fmt.Errorf("error resolving rpc advertise address: %s", err)
   116  		}
   117  		conf.RPCAdvertise = rpcAddr
   118  	}
   119  
   120  	// Set up the bind addresses
   121  	if addr := a.config.BindAddr; addr != "" {
   122  		conf.RPCAddr.IP = net.ParseIP(addr)
   123  		conf.SerfConfig.MemberlistConfig.BindAddr = addr
   124  	}
   125  	if addr := a.config.Addresses.RPC; addr != "" {
   126  		conf.RPCAddr.IP = net.ParseIP(addr)
   127  	}
   128  	if addr := a.config.Addresses.Serf; addr != "" {
   129  		conf.SerfConfig.MemberlistConfig.BindAddr = addr
   130  	}
   131  
   132  	// Set up the ports
   133  	if port := a.config.Ports.RPC; port != 0 {
   134  		conf.RPCAddr.Port = port
   135  	}
   136  	if port := a.config.Ports.Serf; port != 0 {
   137  		conf.SerfConfig.MemberlistConfig.BindPort = port
   138  	}
   139  
   140  	return conf, nil
   141  }
   142  
   143  // setupServer is used to setup the server if enabled
   144  func (a *Agent) setupServer() error {
   145  	if !a.config.Server.Enabled {
   146  		return nil
   147  	}
   148  
   149  	// Setup the configuration
   150  	conf, err := a.serverConfig()
   151  	if err != nil {
   152  		return fmt.Errorf("server config setup failed: %s", err)
   153  	}
   154  
   155  	// Create the server
   156  	server, err := nomad.NewServer(conf)
   157  	if err != nil {
   158  		return fmt.Errorf("server setup failed: %v", err)
   159  	}
   160  
   161  	a.server = server
   162  	return nil
   163  }
   164  
   165  // setupClient is used to setup the client if enabled
   166  func (a *Agent) setupClient() error {
   167  	if !a.config.Client.Enabled {
   168  		return nil
   169  	}
   170  
   171  	// Setup the configuration
   172  	conf := a.config.ClientConfig
   173  	if conf == nil {
   174  		conf = client.DefaultConfig()
   175  	}
   176  	if a.server != nil {
   177  		conf.RPCHandler = a.server
   178  	}
   179  	conf.LogOutput = a.logOutput
   180  	conf.DevMode = a.config.DevMode
   181  	if a.config.Region != "" {
   182  		conf.Region = a.config.Region
   183  	}
   184  	if a.config.DataDir != "" {
   185  		conf.StateDir = filepath.Join(a.config.DataDir, "client")
   186  		conf.AllocDir = filepath.Join(a.config.DataDir, "alloc")
   187  	}
   188  	if a.config.Client.StateDir != "" {
   189  		conf.StateDir = a.config.Client.StateDir
   190  	}
   191  	if a.config.Client.AllocDir != "" {
   192  		conf.AllocDir = a.config.Client.AllocDir
   193  	}
   194  	conf.Servers = a.config.Client.Servers
   195  	if a.config.Client.NetworkInterface != "" {
   196  		conf.NetworkInterface = a.config.Client.NetworkInterface
   197  	}
   198  	conf.Options = a.config.Client.Options
   199  	if a.config.Client.NetworkSpeed != 0 {
   200  		conf.NetworkSpeed = a.config.Client.NetworkSpeed
   201  	}
   202  
   203  	// Setup the node
   204  	conf.Node = new(structs.Node)
   205  	conf.Node.Datacenter = a.config.Datacenter
   206  	conf.Node.Name = a.config.NodeName
   207  	conf.Node.ID = a.config.Client.NodeID
   208  	conf.Node.Meta = a.config.Client.Meta
   209  	conf.Node.NodeClass = a.config.Client.NodeClass
   210  
   211  	// Create the client
   212  	client, err := client.NewClient(conf)
   213  	if err != nil {
   214  		return fmt.Errorf("client setup failed: %v", err)
   215  	}
   216  	a.client = client
   217  	return nil
   218  }
   219  
   220  // Leave is used gracefully exit. Clients will inform servers
   221  // of their departure so that allocations can be rescheduled.
   222  func (a *Agent) Leave() error {
   223  	if a.client != nil {
   224  		if err := a.client.Leave(); err != nil {
   225  			a.logger.Printf("[ERR] agent: client leave failed: %v", err)
   226  		}
   227  	}
   228  	if a.server != nil {
   229  		if err := a.server.Leave(); err != nil {
   230  			a.logger.Printf("[ERR] agent: server leave failed: %v", err)
   231  		}
   232  	}
   233  	return nil
   234  }
   235  
   236  // Shutdown is used to terminate the agent.
   237  func (a *Agent) Shutdown() error {
   238  	a.shutdownLock.Lock()
   239  	defer a.shutdownLock.Unlock()
   240  
   241  	if a.shutdown {
   242  		return nil
   243  	}
   244  
   245  	a.logger.Println("[INFO] agent: requesting shutdown")
   246  	if a.client != nil {
   247  		if err := a.client.Shutdown(); err != nil {
   248  			a.logger.Printf("[ERR] agent: client shutdown failed: %v", err)
   249  		}
   250  	}
   251  	if a.server != nil {
   252  		if err := a.server.Shutdown(); err != nil {
   253  			a.logger.Printf("[ERR] agent: server shutdown failed: %v", err)
   254  		}
   255  	}
   256  
   257  	a.logger.Println("[INFO] agent: shutdown complete")
   258  	a.shutdown = true
   259  	close(a.shutdownCh)
   260  	return nil
   261  }
   262  
   263  // RPC is used to make an RPC call to the Nomad servers
   264  func (a *Agent) RPC(method string, args interface{}, reply interface{}) error {
   265  	if a.server != nil {
   266  		return a.server.RPC(method, args, reply)
   267  	}
   268  	return a.client.RPC(method, args, reply)
   269  }
   270  
   271  // Client returns the configured client or nil
   272  func (a *Agent) Client() *client.Client {
   273  	return a.client
   274  }
   275  
   276  // Server returns the configured server or nil
   277  func (a *Agent) Server() *nomad.Server {
   278  	return a.server
   279  }
   280  
   281  // Stats is used to return statistics for debugging and insight
   282  // for various sub-systems
   283  func (a *Agent) Stats() map[string]map[string]string {
   284  	stats := make(map[string]map[string]string)
   285  	if a.server != nil {
   286  		subStat := a.server.Stats()
   287  		for k, v := range subStat {
   288  			stats[k] = v
   289  		}
   290  	}
   291  	if a.client != nil {
   292  		subStat := a.client.Stats()
   293  		for k, v := range subStat {
   294  			stats[k] = v
   295  		}
   296  	}
   297  	return stats
   298  }