2018-04-25 23:54:00 +00:00
|
|
|
package proxy
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2018-05-22 13:27:04 +00:00
|
|
|
"strconv"
|
2018-04-25 23:54:00 +00:00
|
|
|
"testing"
|
2018-05-02 19:25:00 +00:00
|
|
|
"time"
|
2018-04-25 23:54:00 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/testutil/retry"
|
|
|
|
"github.com/hashicorp/go-uuid"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
)
|
|
|
|
|
2018-04-27 04:11:56 +00:00
|
|
|
func TestDaemon_impl(t *testing.T) {
|
|
|
|
var _ Proxy = new(Daemon)
|
|
|
|
}
|
|
|
|
|
2018-04-25 23:54:00 +00:00
|
|
|
func TestDaemonStartStop(t *testing.T) {
|
2018-05-01 06:35:23 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2018-04-25 23:54:00 +00:00
|
|
|
require := require.New(t)
|
|
|
|
td, closer := testTempDir(t)
|
|
|
|
defer closer()
|
|
|
|
|
|
|
|
path := filepath.Join(td, "file")
|
|
|
|
uuid, err := uuid.GenerateUUID()
|
|
|
|
require.NoError(err)
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
d := helperProcessDaemon("start-stop", path)
|
|
|
|
d.ProxyId = "tubes"
|
|
|
|
d.ProxyToken = uuid
|
|
|
|
d.Logger = testLogger
|
2018-04-25 23:54:00 +00:00
|
|
|
require.NoError(d.Start())
|
2018-05-06 15:41:25 +00:00
|
|
|
defer d.Stop()
|
2018-04-25 23:54:00 +00:00
|
|
|
|
|
|
|
// Wait for the file to exist
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Fatalf("error: %s", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Verify that the contents of the file is the token. This verifies
|
|
|
|
// that we properly passed the token as an env var.
|
|
|
|
data, err := ioutil.ReadFile(path)
|
|
|
|
require.NoError(err)
|
2018-05-06 15:41:25 +00:00
|
|
|
require.Equal("tubes:"+uuid, string(data))
|
2018-04-25 23:54:00 +00:00
|
|
|
|
|
|
|
// Stop the process
|
|
|
|
require.NoError(d.Stop())
|
|
|
|
|
|
|
|
// File should no longer exist.
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// err might be nil here but that's okay
|
|
|
|
r.Fatalf("should not exist: %s", err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
func TestDaemonDetachesChild(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
require := require.New(t)
|
|
|
|
td, closer := testTempDir(t)
|
|
|
|
defer closer()
|
|
|
|
|
|
|
|
path := filepath.Join(td, "file")
|
|
|
|
pidPath := filepath.Join(td, "child.pid")
|
|
|
|
|
|
|
|
// Start the parent process wrapping a start-stop test. The parent is acting
|
|
|
|
// as our "agent". We need an extra indirection to be able to kill the "agent"
|
|
|
|
// and still be running the test process.
|
|
|
|
parentCmd := helperProcess("parent", pidPath, "start-stop", path)
|
|
|
|
require.NoError(parentCmd.Start())
|
|
|
|
|
|
|
|
// Wait for the pid file to exist so we know parent is running
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(pidPath)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Fatalf("error: %s", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// And wait for the actual file to be sure the child is running (it should be
|
|
|
|
// since parent doesn't write PID until child starts but the child might not
|
|
|
|
// have completed the write to disk yet which causes flakiness below).
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Fatalf("error: %s", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Always cleanup child process after
|
|
|
|
defer func() {
|
|
|
|
_, err := os.Stat(pidPath)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
bs, err := ioutil.ReadFile(pidPath)
|
|
|
|
require.NoError(err)
|
|
|
|
pid, err := strconv.Atoi(string(bs))
|
|
|
|
require.NoError(err)
|
|
|
|
proc, err := os.FindProcess(pid)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
proc.Kill()
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Now kill the parent and wait for it
|
|
|
|
require.NoError(parentCmd.Process.Kill())
|
|
|
|
_, err := parentCmd.Process.Wait()
|
|
|
|
require.NoError(err)
|
|
|
|
|
|
|
|
// The child should still be running so file should still be there
|
|
|
|
_, err = os.Stat(path)
|
|
|
|
require.NoError(err, "child should still be running")
|
|
|
|
|
|
|
|
// Let defer clean up the child process
|
|
|
|
}
|
|
|
|
|
2018-04-25 23:54:00 +00:00
|
|
|
func TestDaemonRestart(t *testing.T) {
|
2018-05-01 06:35:23 +00:00
|
|
|
t.Parallel()
|
|
|
|
|
2018-04-25 23:54:00 +00:00
|
|
|
require := require.New(t)
|
|
|
|
td, closer := testTempDir(t)
|
|
|
|
defer closer()
|
|
|
|
path := filepath.Join(td, "file")
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
d := helperProcessDaemon("restart", path)
|
|
|
|
d.Logger = testLogger
|
2018-04-25 23:54:00 +00:00
|
|
|
require.NoError(d.Start())
|
|
|
|
defer d.Stop()
|
|
|
|
|
|
|
|
// Wait for the file to exist. We save the func so we can reuse the test.
|
|
|
|
waitFile := func() {
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
waitFile()
|
|
|
|
|
|
|
|
// Delete the file
|
|
|
|
require.NoError(os.Remove(path))
|
|
|
|
|
|
|
|
// File should re-appear because the process is restart
|
|
|
|
waitFile()
|
|
|
|
}
|
2018-05-02 18:02:58 +00:00
|
|
|
|
2018-05-02 19:25:00 +00:00
|
|
|
func TestDaemonStop_kill(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
require := require.New(t)
|
|
|
|
td, closer := testTempDir(t)
|
|
|
|
defer closer()
|
|
|
|
|
|
|
|
path := filepath.Join(td, "file")
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
d := helperProcessDaemon("stop-kill", path)
|
|
|
|
d.ProxyToken = "hello"
|
|
|
|
d.Logger = testLogger
|
|
|
|
d.gracefulWait = 200 * time.Millisecond
|
|
|
|
d.pollInterval = 100 * time.Millisecond
|
2018-05-02 19:25:00 +00:00
|
|
|
require.NoError(d.Start())
|
|
|
|
|
|
|
|
// Wait for the file to exist
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Fatalf("error: %s", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Stop the process
|
|
|
|
require.NoError(d.Stop())
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
// Stat the file so that we can get the mtime
|
2018-05-02 19:25:00 +00:00
|
|
|
fi, err := os.Stat(path)
|
|
|
|
require.NoError(err)
|
|
|
|
mtime := fi.ModTime()
|
|
|
|
|
|
|
|
// The mtime shouldn't change
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
fi, err = os.Stat(path)
|
|
|
|
require.NoError(err)
|
|
|
|
require.Equal(mtime, fi.ModTime())
|
|
|
|
}
|
|
|
|
|
2018-05-03 20:56:42 +00:00
|
|
|
func TestDaemonStart_pidFile(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
require := require.New(t)
|
|
|
|
td, closer := testTempDir(t)
|
2018-05-22 13:27:04 +00:00
|
|
|
|
2018-05-03 20:56:42 +00:00
|
|
|
defer closer()
|
|
|
|
|
|
|
|
path := filepath.Join(td, "file")
|
|
|
|
pidPath := filepath.Join(td, "pid")
|
|
|
|
uuid, err := uuid.GenerateUUID()
|
|
|
|
require.NoError(err)
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
d := helperProcessDaemon("start-once", path)
|
|
|
|
d.ProxyToken = uuid
|
|
|
|
d.Logger = testLogger
|
|
|
|
d.PidPath = pidPath
|
2018-05-03 20:56:42 +00:00
|
|
|
require.NoError(d.Start())
|
|
|
|
defer d.Stop()
|
|
|
|
|
|
|
|
// Wait for the file to exist
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(pidPath)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Fatalf("error: %s", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Check the pid file
|
|
|
|
pidRaw, err := ioutil.ReadFile(pidPath)
|
|
|
|
require.NoError(err)
|
|
|
|
require.NotEmpty(pidRaw)
|
2018-05-04 16:44:59 +00:00
|
|
|
|
|
|
|
// Stop
|
|
|
|
require.NoError(d.Stop())
|
|
|
|
|
|
|
|
// Pid file should be gone
|
|
|
|
_, err = os.Stat(pidPath)
|
|
|
|
require.True(os.IsNotExist(err))
|
2018-05-03 20:56:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Verify the pid file changes on restart
|
|
|
|
func TestDaemonRestart_pidFile(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
require := require.New(t)
|
|
|
|
td, closer := testTempDir(t)
|
|
|
|
defer closer()
|
|
|
|
path := filepath.Join(td, "file")
|
|
|
|
pidPath := filepath.Join(td, "pid")
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
d := helperProcessDaemon("restart", path)
|
|
|
|
d.Logger = testLogger
|
|
|
|
d.PidPath = pidPath
|
2018-05-03 20:56:42 +00:00
|
|
|
require.NoError(d.Start())
|
|
|
|
defer d.Stop()
|
|
|
|
|
|
|
|
// Wait for the file to exist. We save the func so we can reuse the test.
|
|
|
|
waitFile := func() {
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
r.Fatalf("error waiting for path: %s", err)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
waitFile()
|
|
|
|
|
|
|
|
// Check the pid file
|
|
|
|
pidRaw, err := ioutil.ReadFile(pidPath)
|
|
|
|
require.NoError(err)
|
|
|
|
require.NotEmpty(pidRaw)
|
|
|
|
|
|
|
|
// Delete the file
|
|
|
|
require.NoError(os.Remove(path))
|
|
|
|
|
|
|
|
// File should re-appear because the process is restart
|
|
|
|
waitFile()
|
|
|
|
|
|
|
|
// Check the pid file and it should not equal
|
|
|
|
pidRaw2, err := ioutil.ReadFile(pidPath)
|
|
|
|
require.NoError(err)
|
|
|
|
require.NotEmpty(pidRaw2)
|
|
|
|
require.NotEqual(pidRaw, pidRaw2)
|
|
|
|
}
|
|
|
|
|
2018-05-02 18:02:58 +00:00
|
|
|
func TestDaemonEqual(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Name string
|
|
|
|
D1, D2 Proxy
|
|
|
|
Expected bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"Different type",
|
2018-05-22 13:27:04 +00:00
|
|
|
&Daemon{},
|
2018-05-02 18:02:58 +00:00
|
|
|
&Noop{},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
"Nil",
|
2018-05-22 13:27:04 +00:00
|
|
|
&Daemon{},
|
2018-05-02 18:02:58 +00:00
|
|
|
nil,
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
"Equal",
|
2018-05-22 13:27:04 +00:00
|
|
|
&Daemon{},
|
|
|
|
&Daemon{},
|
2018-05-02 18:02:58 +00:00
|
|
|
true,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
"Different path",
|
|
|
|
&Daemon{
|
2018-05-22 13:27:04 +00:00
|
|
|
Path: "/foo",
|
2018-05-02 18:02:58 +00:00
|
|
|
},
|
|
|
|
&Daemon{
|
2018-05-22 13:27:04 +00:00
|
|
|
Path: "/bar",
|
2018-05-02 18:02:58 +00:00
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
"Different args",
|
|
|
|
&Daemon{
|
2018-05-22 13:27:04 +00:00
|
|
|
Args: []string{"foo"},
|
2018-05-02 18:02:58 +00:00
|
|
|
},
|
|
|
|
&Daemon{
|
2018-05-22 13:27:04 +00:00
|
|
|
Args: []string{"bar"},
|
2018-05-02 18:02:58 +00:00
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
"Different token",
|
|
|
|
&Daemon{
|
|
|
|
ProxyToken: "one",
|
|
|
|
},
|
|
|
|
&Daemon{
|
|
|
|
ProxyToken: "two",
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
|
|
actual := tc.D1.Equal(tc.D2)
|
|
|
|
require.Equal(t, tc.Expected, actual)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2018-05-03 22:46:00 +00:00
|
|
|
|
|
|
|
func TestDaemonMarshalSnapshot(t *testing.T) {
|
|
|
|
cases := []struct {
|
|
|
|
Name string
|
|
|
|
Proxy Proxy
|
|
|
|
Expected map[string]interface{}
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
"stopped daemon",
|
|
|
|
&Daemon{
|
2018-05-22 13:27:04 +00:00
|
|
|
Path: "/foo",
|
2018-05-03 22:46:00 +00:00
|
|
|
},
|
|
|
|
nil,
|
|
|
|
},
|
|
|
|
|
|
|
|
{
|
|
|
|
"basic",
|
|
|
|
&Daemon{
|
2018-05-22 13:27:04 +00:00
|
|
|
Path: "/foo",
|
2018-05-03 22:46:00 +00:00
|
|
|
process: &os.Process{Pid: 42},
|
|
|
|
},
|
|
|
|
map[string]interface{}{
|
2018-05-22 13:27:04 +00:00
|
|
|
"Pid": 42,
|
|
|
|
"Path": "/foo",
|
|
|
|
"Args": []string(nil),
|
|
|
|
"ProxyToken": "",
|
2018-05-03 22:46:00 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tc := range cases {
|
|
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
|
|
actual := tc.Proxy.MarshalSnapshot()
|
|
|
|
require.Equal(t, tc.Expected, actual)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDaemonUnmarshalSnapshot(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
require := require.New(t)
|
|
|
|
td, closer := testTempDir(t)
|
|
|
|
defer closer()
|
|
|
|
|
|
|
|
path := filepath.Join(td, "file")
|
|
|
|
uuid, err := uuid.GenerateUUID()
|
|
|
|
require.NoError(err)
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
d := helperProcessDaemon("start-stop", path)
|
|
|
|
d.ProxyToken = uuid
|
|
|
|
d.Logger = testLogger
|
2018-05-03 22:55:49 +00:00
|
|
|
defer d.Stop()
|
2018-05-03 22:46:00 +00:00
|
|
|
require.NoError(d.Start())
|
|
|
|
|
|
|
|
// Wait for the file to exist
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Fatalf("error: %s", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Snapshot
|
|
|
|
snap := d.MarshalSnapshot()
|
|
|
|
|
|
|
|
// Stop the original daemon but keep it alive
|
|
|
|
require.NoError(d.stopKeepAlive())
|
|
|
|
|
|
|
|
// Restore the second daemon
|
|
|
|
d2 := &Daemon{Logger: testLogger}
|
|
|
|
require.NoError(d2.UnmarshalSnapshot(snap))
|
|
|
|
|
|
|
|
// Stop the process
|
|
|
|
require.NoError(d2.Stop())
|
|
|
|
|
|
|
|
// File should no longer exist.
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// err might be nil here but that's okay
|
|
|
|
r.Fatalf("should not exist: %s", err)
|
|
|
|
})
|
|
|
|
}
|
2018-05-03 22:55:49 +00:00
|
|
|
|
|
|
|
func TestDaemonUnmarshalSnapshot_notRunning(t *testing.T) {
|
|
|
|
t.Parallel()
|
|
|
|
|
|
|
|
require := require.New(t)
|
|
|
|
td, closer := testTempDir(t)
|
|
|
|
defer closer()
|
|
|
|
|
|
|
|
path := filepath.Join(td, "file")
|
|
|
|
uuid, err := uuid.GenerateUUID()
|
|
|
|
require.NoError(err)
|
|
|
|
|
2018-05-22 13:27:04 +00:00
|
|
|
d := helperProcessDaemon("start-stop", path)
|
|
|
|
d.ProxyToken = uuid
|
|
|
|
d.Logger = testLogger
|
2018-05-03 22:55:49 +00:00
|
|
|
defer d.Stop()
|
|
|
|
require.NoError(d.Start())
|
|
|
|
|
|
|
|
// Wait for the file to exist
|
|
|
|
retry.Run(t, func(r *retry.R) {
|
|
|
|
_, err := os.Stat(path)
|
|
|
|
if err == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
r.Fatalf("error: %s", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
// Snapshot
|
|
|
|
snap := d.MarshalSnapshot()
|
|
|
|
|
|
|
|
// Stop the original daemon
|
|
|
|
require.NoError(d.Stop())
|
|
|
|
|
|
|
|
// Restore the second daemon
|
|
|
|
d2 := &Daemon{Logger: testLogger}
|
|
|
|
require.Error(d2.UnmarshalSnapshot(snap))
|
|
|
|
}
|