d3c7d57209
* Move internal/ to sdk/ * Add a readme to the SDK folder
586 lines
13 KiB
Go
586 lines
13 KiB
Go
package proxyprocess
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/hashicorp/consul/agent/local"
|
|
"github.com/hashicorp/consul/agent/structs"
|
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestManagerClose_noRun(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Really we're testing that it doesn't deadlock here.
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
require.NoError(t, m.Close())
|
|
|
|
// Close again for sanity
|
|
require.NoError(t, m.Close())
|
|
}
|
|
|
|
// Test that Run performs an initial sync (if local.State is already set)
|
|
// rather than waiting for a notification from the local state.
|
|
func TestManagerRun_initialSync(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Add the proxy before we start the manager to verify initial sync
|
|
td, closer := testTempDir(t)
|
|
defer closer()
|
|
path := filepath.Join(td, "file")
|
|
|
|
cmd, destroy := helperProcess("restart", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "web", cmd)
|
|
|
|
// Start the manager
|
|
go m.Run()
|
|
|
|
// We should see the path appear shortly
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
})
|
|
}
|
|
|
|
func TestManagerRun_syncNew(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Start the manager
|
|
go m.Run()
|
|
|
|
// Sleep a bit, this is just an attempt for Run to already be running.
|
|
// Its not a big deal if this sleep doesn't happen (slow CI).
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// Add the first proxy
|
|
td, closer := testTempDir(t)
|
|
defer closer()
|
|
path := filepath.Join(td, "file")
|
|
|
|
cmd, destroy := helperProcess("restart", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "web", cmd)
|
|
|
|
// We should see the path appear shortly
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
})
|
|
|
|
// Add another proxy
|
|
path = path + "2"
|
|
|
|
cmd, destroy = helperProcess("restart", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "db", cmd)
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
})
|
|
}
|
|
|
|
func TestManagerRun_syncDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Start the manager
|
|
go m.Run()
|
|
|
|
// Add the first proxy
|
|
td, closer := testTempDir(t)
|
|
defer closer()
|
|
path := filepath.Join(td, "file")
|
|
|
|
cmd, destroy := helperProcess("restart", path)
|
|
defer destroy()
|
|
|
|
id := testStateProxy(t, state, "web", cmd)
|
|
|
|
// We should see the path appear shortly
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
})
|
|
|
|
// Remove the proxy
|
|
_, err := state.RemoveProxy(id)
|
|
require.NoError(t, err)
|
|
|
|
// File should disappear as process is killed
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
r.Fatalf("path exists")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestManagerRun_syncUpdate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Start the manager
|
|
go m.Run()
|
|
|
|
// Add the first proxy
|
|
td, closer := testTempDir(t)
|
|
defer closer()
|
|
path := filepath.Join(td, "file")
|
|
|
|
cmd, destroy := helperProcess("restart", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "web", cmd)
|
|
|
|
// We should see the path appear shortly
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
})
|
|
|
|
// Update the proxy with a new path
|
|
oldPath := path
|
|
path = path + "2"
|
|
|
|
cmd, destroy = helperProcess("restart", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "web", cmd)
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
})
|
|
|
|
// Old path should be gone
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(oldPath)
|
|
if err == nil {
|
|
r.Fatalf("old path exists")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestManagerRun_daemonLogs(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require := require.New(t)
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Configure a log dir so that we can read the logs
|
|
logDir := filepath.Join(m.DataDir, "logs")
|
|
|
|
// Create the service and calculate the log paths
|
|
path := filepath.Join(m.DataDir, "notify")
|
|
|
|
cmd, destroy := helperProcess("output", path)
|
|
defer destroy()
|
|
|
|
id := testStateProxy(t, state, "web", cmd)
|
|
stdoutPath := logPath(logDir, id, "stdout")
|
|
stderrPath := logPath(logDir, id, "stderr")
|
|
|
|
// Start the manager
|
|
go m.Run()
|
|
|
|
// We should see the path appear shortly
|
|
retry.Run(t, func(r *retry.R) {
|
|
if _, err := os.Stat(path); err != nil {
|
|
r.Fatalf("error waiting for stdout path: %s", err)
|
|
}
|
|
})
|
|
|
|
expectedOut := "hello stdout\n"
|
|
actual, err := ioutil.ReadFile(stdoutPath)
|
|
require.NoError(err)
|
|
require.Equal([]byte(expectedOut), actual)
|
|
|
|
expectedErr := "hello stderr\n"
|
|
actual, err = ioutil.ReadFile(stderrPath)
|
|
require.NoError(err)
|
|
require.Equal([]byte(expectedErr), actual)
|
|
}
|
|
|
|
func TestManagerRun_daemonPid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require := require.New(t)
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Configure a log dir so that we can read the logs
|
|
pidDir := filepath.Join(m.DataDir, "pids")
|
|
|
|
// Create the service and calculate the log paths
|
|
path := filepath.Join(m.DataDir, "notify")
|
|
|
|
cmd, destroy := helperProcess("output", path)
|
|
defer destroy()
|
|
|
|
id := testStateProxy(t, state, "web", cmd)
|
|
pidPath := pidPath(pidDir, id)
|
|
|
|
// Start the manager
|
|
go m.Run()
|
|
|
|
// We should see the path appear shortly
|
|
retry.Run(t, func(r *retry.R) {
|
|
if _, err := os.Stat(path); err != nil {
|
|
r.Fatalf("error waiting for stdout path: %s", err)
|
|
}
|
|
})
|
|
|
|
// Verify the pid file is not empty
|
|
pidRaw, err := ioutil.ReadFile(pidPath)
|
|
require.NoError(err)
|
|
require.NotEmpty(pidRaw)
|
|
}
|
|
|
|
// Test to check if the parent and the child processes
|
|
// have the same environmental variables
|
|
|
|
func TestManagerPassesEnvironment(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require := require.New(t)
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Add Proxy for the test
|
|
td, closer := testTempDir(t)
|
|
defer closer()
|
|
path := filepath.Join(td, "env-variables")
|
|
|
|
cmd, destroy := helperProcess("environ", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "environTest", cmd)
|
|
|
|
//Run the manager
|
|
go m.Run()
|
|
|
|
//Get the environmental variables from the OS
|
|
var fileContent []byte
|
|
var err error
|
|
var data []byte
|
|
envData := os.Environ()
|
|
sort.Strings(envData)
|
|
for _, envVariable := range envData {
|
|
if strings.HasPrefix(envVariable, "CONSUL") || strings.HasPrefix(envVariable, "CONNECT") {
|
|
continue
|
|
}
|
|
data = append(data, envVariable...)
|
|
data = append(data, "\n"...)
|
|
}
|
|
|
|
// Check if the file written to from the spawned process
|
|
// has the necessary environmental variable data
|
|
retry.Run(t, func(r *retry.R) {
|
|
if fileContent, err = ioutil.ReadFile(path); err != nil {
|
|
r.Fatalf("No file ya dummy")
|
|
}
|
|
})
|
|
|
|
require.Equal(data, fileContent)
|
|
}
|
|
|
|
// Test to check if the parent and the child processes
|
|
// have the same environmental variables
|
|
func TestManagerPassesProxyEnv(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require := require.New(t)
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
penv := make([]string, 0, 2)
|
|
penv = append(penv, "HTTP_ADDR=127.0.0.1:8500")
|
|
penv = append(penv, "HTTP_SSL=false")
|
|
m.ProxyEnv = penv
|
|
|
|
// Add Proxy for the test
|
|
td, closer := testTempDir(t)
|
|
defer closer()
|
|
path := filepath.Join(td, "env-variables")
|
|
|
|
cmd, destroy := helperProcess("environ", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "environTest", cmd)
|
|
|
|
//Run the manager
|
|
go m.Run()
|
|
|
|
//Get the environmental variables from the OS
|
|
var fileContent []byte
|
|
var err error
|
|
var data []byte
|
|
envData := os.Environ()
|
|
envData = append(envData, "HTTP_ADDR=127.0.0.1:8500")
|
|
envData = append(envData, "HTTP_SSL=false")
|
|
sort.Strings(envData)
|
|
for _, envVariable := range envData {
|
|
if strings.HasPrefix(envVariable, "CONSUL") || strings.HasPrefix(envVariable, "CONNECT") {
|
|
continue
|
|
}
|
|
data = append(data, envVariable...)
|
|
data = append(data, "\n"...)
|
|
}
|
|
|
|
// Check if the file written to from the spawned process
|
|
// has the necessary environmental variable data
|
|
retry.Run(t, func(r *retry.R) {
|
|
if fileContent, err = ioutil.ReadFile(path); err != nil {
|
|
r.Fatalf("No file ya dummy")
|
|
}
|
|
})
|
|
|
|
require.Equal(data, fileContent)
|
|
}
|
|
|
|
// Test the Snapshot/Restore works.
|
|
func TestManagerRun_snapshotRestore(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
require := require.New(t)
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Add the proxy
|
|
td, closer := testTempDir(t)
|
|
defer closer()
|
|
path := filepath.Join(td, "file")
|
|
|
|
cmd, destroy := helperProcess("start-stop", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "web", cmd)
|
|
|
|
// Set a low snapshot period so we get a snapshot
|
|
m.SnapshotPeriod = 10 * time.Millisecond
|
|
|
|
// Start the manager
|
|
go m.Run()
|
|
|
|
// We should see the path appear shortly
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
})
|
|
|
|
// Wait for the snapshot
|
|
snapPath := m.SnapshotPath()
|
|
retry.Run(t, func(r *retry.R) {
|
|
raw, err := ioutil.ReadFile(snapPath)
|
|
if err != nil {
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
}
|
|
if len(raw) < 30 {
|
|
r.Fatalf("snapshot too small")
|
|
}
|
|
})
|
|
|
|
// Stop the sync
|
|
require.NoError(m.Close())
|
|
|
|
// File should still exist
|
|
_, err := os.Stat(path)
|
|
require.NoError(err)
|
|
|
|
// Restore a manager from a snapshot
|
|
m2, closer := testManager(t)
|
|
m2.State = state
|
|
defer closer()
|
|
defer m2.Kill()
|
|
require.NoError(m2.Restore(snapPath))
|
|
|
|
// Start
|
|
go m2.Run()
|
|
|
|
// Add a second proxy so that we can determine when we're up
|
|
// and running.
|
|
path2 := filepath.Join(td, "file2")
|
|
|
|
cmd, destroy = helperProcess("start-stop", path2)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "db", cmd)
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path2)
|
|
if err == nil {
|
|
return
|
|
}
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
})
|
|
|
|
// Kill m2, which should kill our main process
|
|
require.NoError(m2.Kill())
|
|
|
|
// File should no longer exist
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
r.Fatalf("file still exists: %s", path)
|
|
})
|
|
}
|
|
|
|
// Manager should not run any proxies if we're running as root. Tests
|
|
// stub the value.
|
|
func TestManagerRun_rootDisallow(t *testing.T) {
|
|
// Pretend we are root
|
|
defer testSetRootValue(true)()
|
|
|
|
state := local.TestState(t)
|
|
m, closer := testManager(t)
|
|
defer closer()
|
|
m.State = state
|
|
defer m.Kill()
|
|
|
|
// Add the proxy before we start the manager to verify initial sync
|
|
td, closer := testTempDir(t)
|
|
defer closer()
|
|
path := filepath.Join(td, "file")
|
|
|
|
cmd, destroy := helperProcess("restart", path)
|
|
defer destroy()
|
|
|
|
testStateProxy(t, state, "web", cmd)
|
|
|
|
// Start the manager
|
|
go m.Run()
|
|
|
|
// Sleep a bit just to verify
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// We should see the path appear shortly
|
|
retry.Run(t, func(r *retry.R) {
|
|
_, err := os.Stat(path)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
r.Fatalf("path exists")
|
|
})
|
|
}
|
|
|
|
func testManager(t *testing.T) (*Manager, func()) {
|
|
m := NewManager()
|
|
|
|
// Setup a default state
|
|
m.State = local.TestState(t)
|
|
|
|
// Set these periods low to speed up tests
|
|
m.CoalescePeriod = 1 * time.Millisecond
|
|
m.QuiescentPeriod = 1 * time.Millisecond
|
|
|
|
// Setup a temporary directory for logs
|
|
td, closer := testTempDir(t)
|
|
m.DataDir = td
|
|
|
|
return m, func() { closer() }
|
|
}
|
|
|
|
// testStateProxy registers a proxy with the given local state and the command
|
|
// (expected to be from the helperProcess function call). It returns the
|
|
// ID for deregistration.
|
|
func testStateProxy(t *testing.T, state *local.State, service string, cmd *exec.Cmd) string {
|
|
// *exec.Cmd must manually set args[0] to the binary. We automatically
|
|
// set this when constructing the command for the proxy, so we must strip
|
|
// the zero index. We do this unconditionally (anytime len is > 0) because
|
|
// index zero should ALWAYS be the binary.
|
|
if len(cmd.Args) > 0 {
|
|
cmd.Args = cmd.Args[1:]
|
|
}
|
|
|
|
command := []string{cmd.Path}
|
|
command = append(command, cmd.Args...)
|
|
|
|
require.NoError(t, state.AddService(&structs.NodeService{
|
|
Service: service,
|
|
}, "token"))
|
|
|
|
p, err := state.AddProxy(&structs.ConnectManagedProxy{
|
|
ExecMode: structs.ProxyExecModeDaemon,
|
|
Command: command,
|
|
TargetServiceID: service,
|
|
}, "token", "")
|
|
require.NoError(t, err)
|
|
|
|
return p.Proxy.ProxyService.ID
|
|
}
|