mirror of
https://github.com/kemko/nomad.git
synced 2026-01-02 08:25:43 +03:00
The new client intro test mimics the Consul and Vault compat tests and uses local agents to perform the required setup. This method allows us the flexibility moving forward to test when enforcement mode is in strict. The test suite will now be triggered from the test-e2e CI run and can also be called by a make target.
366 lines
7.9 KiB
Go
366 lines
7.9 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package execagent
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"text/template"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
"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
|
|
name = "{{ or .AgentName "nomad-e2e-test-agent" }}"
|
|
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"
|
|
}
|
|
{{- $retry_join_length := len .RetryJoinAddrs }}{{ if not (eq $retry_join_length 0) }}
|
|
server_join {
|
|
retry_join = [{{ range $index, $element := .RetryJoinAddrs }}{{if $index}}, {{end}}"{{$element}}"{{ end }}]
|
|
}
|
|
{{ end }}
|
|
}
|
|
{{ end }}
|
|
`))
|
|
)
|
|
|
|
type AgentTemplateVars struct {
|
|
HTTP int
|
|
RPC int
|
|
Serf int
|
|
EnableClient bool
|
|
EnableServer bool
|
|
|
|
// AgentName is the name to apply to the Nomad agent. This is optional, but
|
|
// allows for multiple agents to be run on the same host. If not set, it
|
|
// will default to "nomad-e2e-test-agent".
|
|
AgentName string
|
|
|
|
LogLevel string
|
|
|
|
// NodePool is the Nomad node pool to assign the agent to when running with
|
|
// client mode enabled. This will default to the "default" node pool if not
|
|
// set.
|
|
NodePool string
|
|
|
|
// RetryJoinAddrs is a list of addresses to use for the retry_join config
|
|
// block.
|
|
RetryJoinAddrs []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,
|
|
LogLevel: hclog.Warn.String(),
|
|
NodePool: "default",
|
|
}
|
|
|
|
return &vars, nil
|
|
}
|
|
|
|
// SetMode is a helper function to allow setting the agent mode (client, server,
|
|
// or both).
|
|
func (a *AgentTemplateVars) SetMode(mode AgentMode) {
|
|
switch mode {
|
|
case ModeClient:
|
|
a.EnableClient = true
|
|
a.EnableServer = false
|
|
case ModeServer:
|
|
a.EnableClient = false
|
|
a.EnableServer = true
|
|
case ModeBoth:
|
|
a.EnableClient = true
|
|
a.EnableServer = true
|
|
}
|
|
}
|
|
|
|
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 := os.MkdirTemp(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 := os.MkdirTemp(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 := os.MkdirTemp(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
|
|
}
|
|
|
|
// TemplateVariableCallbackFunc is a callback function that allow callers to
|
|
// modify the template variables before the config file is written out.
|
|
type TemplateVariableCallbackFunc func(c *AgentTemplateVars)
|
|
|
|
func NewSingleModeAgent(
|
|
bin, baseDir string,
|
|
mode AgentMode,
|
|
writer io.Writer,
|
|
varCallbackFn TemplateVariableCallbackFunc,
|
|
) (*NomadAgent, error) {
|
|
|
|
templateVars, err := newAgentTemplateVars()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Allow the caller to modify the template variables before we write out the
|
|
// config file.
|
|
if varCallbackFn != nil {
|
|
varCallbackFn(templateVars)
|
|
}
|
|
|
|
// Set the mode (client, server, or both)
|
|
templateVars.SetMode(mode)
|
|
|
|
baseDataDir := BaseDir
|
|
|
|
if baseDir != "" {
|
|
baseDataDir = baseDir
|
|
}
|
|
|
|
if err := os.MkdirAll(baseDataDir, 0755); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
agentDir, err := os.MkdirTemp(baseDataDir, "agent")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
agentConfig := filepath.Join(agentDir, "agent.hcl")
|
|
if err := writeConfig(agentConfig, templateVars); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nomadAgent := &NomadAgent{
|
|
BinPath: bin,
|
|
DataDir: agentDir,
|
|
ConfFile: agentConfig,
|
|
Vars: templateVars,
|
|
Cmd: exec.Command(bin, "agent", "-config", agentConfig, "-data-dir", agentDir),
|
|
}
|
|
|
|
nomadAgent.Cmd.Stdout = writer
|
|
nomadAgent.Cmd.Stderr = writer
|
|
|
|
return nomadAgent, nil
|
|
}
|
|
|
|
// 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
|
|
}
|