package agent import ( "fmt" "io" "log" "os" "sync" "github.com/hashicorp/nomad/client" "github.com/hashicorp/nomad/nomad" ) // Agent is a long running daemon that is used to run both // clients and servers. Servers are responsible for managing // state and making scheduling decisions. Clients can be // scheduled to, and are responsible for interfacing with // servers to run allocations. type Agent struct { config *Config logger *log.Logger logOutput io.Writer server *nomad.Server client *client.Client shutdown bool shutdownCh chan struct{} shutdownLock sync.Mutex } // NewAgent is used to create a new agent with the given configuration func NewAgent(config *Config, logOutput io.Writer) (*Agent, error) { // Ensure we have a log sink if logOutput == nil { logOutput = os.Stderr } a := &Agent{ config: config, logger: log.New(logOutput, "", log.LstdFlags), logOutput: logOutput, shutdownCh: make(chan struct{}), } if err := a.setupServer(); err != nil { return nil, err } if err := a.setupClient(); err != nil { return nil, err } if a.client == nil && a.server == nil { return nil, fmt.Errorf("must have at least client or server mode enabled") } return a, nil } // setupServer is used to setup the server if enabled func (a *Agent) setupServer() error { if !a.config.Server.Enabled { return nil } // Setup the configuration conf := nomad.DefaultConfig() conf.LogOutput = a.logOutput conf.DevMode = a.config.DevMode // TODO: Merge configuration server, err := nomad.NewServer(conf) if err != nil { return fmt.Errorf("server setup failed: %v", err) } a.server = server return nil } // setupClient is used to setup the client if enabled func (a *Agent) setupClient() error { if !a.config.Client.Enabled { return nil } // Setup the configuration conf := client.DefaultConfig() if a.server != nil { conf.RPCHandler = a.server } conf.LogOutput = a.logOutput conf.DevMode = a.config.DevMode // TODO: Merge configuration client, err := client.NewClient(conf) if err != nil { return fmt.Errorf("client setup failed: %v", err) } a.client = client return nil } // Leave is used gracefully exit. Clients will inform servers // of their departure so that allocations can be rescheduled. func (a *Agent) Leave() error { if a.client != nil { if err := a.client.Leave(); err != nil { a.logger.Printf("[ERR] agent: client leave failed: %v", err) } } if a.server != nil { if err := a.server.Leave(); err != nil { a.logger.Printf("[ERR] agent: server leave failed: %v", err) } } return nil } // Shutdown is used to terminate the agent. func (a *Agent) Shutdown() error { a.shutdownLock.Lock() defer a.shutdownLock.Unlock() if a.shutdown { return nil } a.logger.Println("[INFO] agent: requesting shutdown") if a.client != nil { if err := a.client.Shutdown(); err != nil { a.logger.Printf("[ERR] agent: client shutdown failed: %v", err) } } if a.server != nil { if err := a.server.Shutdown(); err != nil { a.logger.Printf("[ERR] agent: server shutdown failed: %v", err) } } a.logger.Println("[INFO] agent: shutdown complete") a.shutdown = true close(a.shutdownCh) return nil } // RPC is used to make an RPC call to the Nomad servers func (a *Agent) RPC(method string, args interface{}, reply interface{}) error { if a.server != nil { return a.server.RPC(method, args, reply) } return a.client.RPC(method, args, reply) } // Client returns the configured client or nil func (a *Agent) Client() *client.Client { return a.client } // Server returns the configured server or nil func (a *Agent) Server() *nomad.Server { return a.server }