265 lines
5.2 KiB
Go
265 lines
5.2 KiB
Go
package execagent
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"text/template"
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
)
|
|
|
|
type AgentMode int
|
|
|
|
const (
|
|
// Conf enum is for configuring either a client, server, or mixed agent.
|
|
ModeClient AgentMode = 1
|
|
ModeServer AgentMode = 2
|
|
ModeBoth = ModeClient | ModeServer
|
|
)
|
|
|
|
func init() {
|
|
if d := os.Getenv("NOMAD_TEST_DIR"); d != "" {
|
|
BaseDir = d
|
|
}
|
|
}
|
|
|
|
var (
|
|
// BaseDir is where tests will store state and can be overridden by
|
|
// setting NOMAD_TEST_DIR. Defaults to "/opt/nomadtest"
|
|
BaseDir = "/opt/nomadtest"
|
|
|
|
agentTemplate = template.Must(template.New("agent").Parse(`
|
|
enable_debug = true
|
|
log_level = "{{ or .LogLevel "DEBUG" }}"
|
|
|
|
ports {
|
|
http = {{.HTTP}}
|
|
rpc = {{.RPC}}
|
|
serf = {{.Serf}}
|
|
}
|
|
|
|
{{ if .EnableServer }}
|
|
server {
|
|
enabled = true
|
|
bootstrap_expect = 1
|
|
}
|
|
{{ end }}
|
|
|
|
{{ if .EnableClient }}
|
|
client {
|
|
enabled = true
|
|
options = {
|
|
"driver.raw_exec.enable" = "1"
|
|
}
|
|
}
|
|
{{ end }}
|
|
`))
|
|
)
|
|
|
|
type AgentTemplateVars struct {
|
|
HTTP int
|
|
RPC int
|
|
Serf int
|
|
EnableClient bool
|
|
EnableServer bool
|
|
LogLevel string
|
|
}
|
|
|
|
func newAgentTemplateVars() (*AgentTemplateVars, error) {
|
|
httpPort, err := getFreePort()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rpcPort, err := getFreePort()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
serfPort, err := getFreePort()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vars := AgentTemplateVars{
|
|
HTTP: httpPort,
|
|
RPC: rpcPort,
|
|
Serf: serfPort,
|
|
}
|
|
|
|
return &vars, nil
|
|
}
|
|
|
|
func writeConfig(path string, vars *AgentTemplateVars) error {
|
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
return agentTemplate.Execute(f, vars)
|
|
}
|
|
|
|
// NomadAgent manages an external Nomad agent process.
|
|
type NomadAgent struct {
|
|
// BinPath is the path to the Nomad binary
|
|
BinPath string
|
|
|
|
// DataDir is the path state will be saved in
|
|
DataDir string
|
|
|
|
// ConfFile is the path to the agent's conf file
|
|
ConfFile string
|
|
|
|
// Cmd is the agent process
|
|
Cmd *exec.Cmd
|
|
|
|
// Vars are the config parameters used to template
|
|
Vars *AgentTemplateVars
|
|
}
|
|
|
|
// NewMixedAgent creates a new Nomad agent in mixed server+client mode but does
|
|
// not start the agent process until the Start() method is called.
|
|
func NewMixedAgent(bin string) (*NomadAgent, error) {
|
|
if err := os.MkdirAll(BaseDir, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
dir, err := ioutil.TempDir(BaseDir, "agent")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
vars, err := newAgentTemplateVars()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vars.EnableClient = true
|
|
vars.EnableServer = true
|
|
|
|
conf := filepath.Join(dir, "config.hcl")
|
|
if err := writeConfig(conf, vars); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
na := &NomadAgent{
|
|
BinPath: bin,
|
|
DataDir: dir,
|
|
ConfFile: conf,
|
|
Vars: vars,
|
|
Cmd: exec.Command(bin, "agent", "-config", conf, "-data-dir", dir),
|
|
}
|
|
return na, nil
|
|
}
|
|
|
|
// NewClientServerPair creates a pair of Nomad agents: 1 server, 1 client.
|
|
func NewClientServerPair(bin string, serverOut, clientOut io.Writer) (
|
|
server *NomadAgent, client *NomadAgent, err error) {
|
|
|
|
if err := os.MkdirAll(BaseDir, 0755); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
sdir, err := ioutil.TempDir(BaseDir, "server")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
svars, err := newAgentTemplateVars()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
svars.LogLevel = "WARN"
|
|
svars.EnableServer = true
|
|
|
|
sconf := filepath.Join(sdir, "config.hcl")
|
|
if err := writeConfig(sconf, svars); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
server = &NomadAgent{
|
|
BinPath: bin,
|
|
DataDir: sdir,
|
|
ConfFile: sconf,
|
|
Vars: svars,
|
|
Cmd: exec.Command(bin, "agent", "-config", sconf, "-data-dir", sdir),
|
|
}
|
|
server.Cmd.Stdout = serverOut
|
|
server.Cmd.Stderr = serverOut
|
|
|
|
cdir, err := ioutil.TempDir(BaseDir, "client")
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
cvars, err := newAgentTemplateVars()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
cvars.EnableClient = true
|
|
|
|
cconf := filepath.Join(cdir, "config.hcl")
|
|
if err := writeConfig(cconf, cvars); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
client = &NomadAgent{
|
|
BinPath: bin,
|
|
DataDir: cdir,
|
|
ConfFile: cconf,
|
|
Vars: cvars,
|
|
Cmd: exec.Command(bin, "agent",
|
|
"-config", cconf,
|
|
"-data-dir", cdir,
|
|
"-servers", fmt.Sprintf("127.0.0.1:%d", svars.RPC),
|
|
),
|
|
}
|
|
client.Cmd.Stdout = clientOut
|
|
client.Cmd.Stderr = clientOut
|
|
return
|
|
}
|
|
|
|
// Start the agent command.
|
|
func (n *NomadAgent) Start() error {
|
|
return n.Cmd.Start()
|
|
}
|
|
|
|
// Stop sends an interrupt signal and returns the command's Wait error.
|
|
func (n *NomadAgent) Stop() error {
|
|
if err := n.Cmd.Process.Signal(os.Interrupt); err != nil {
|
|
return err
|
|
}
|
|
|
|
return n.Cmd.Wait()
|
|
}
|
|
|
|
// Destroy stops the agent and removes the data dir.
|
|
func (n *NomadAgent) Destroy() error {
|
|
if err := n.Stop(); err != nil {
|
|
return err
|
|
}
|
|
return os.RemoveAll(n.DataDir)
|
|
}
|
|
|
|
// Client returns an api.Client for the agent.
|
|
func (n *NomadAgent) Client() (*api.Client, error) {
|
|
conf := api.DefaultConfig()
|
|
conf.Address = fmt.Sprintf("http://127.0.0.1:%d", n.Vars.HTTP)
|
|
return api.NewClient(conf)
|
|
}
|
|
|
|
func getFreePort() (int, error) {
|
|
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
l, err := net.ListenTCP("tcp", addr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer l.Close()
|
|
return l.Addr().(*net.TCPAddr).Port, nil
|
|
}
|