agent/proxy: detect config change to stop/start proxies
This commit is contained in:
parent
8ce3deac5d
commit
6884654c9d
|
@ -5,6 +5,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
@ -198,3 +199,18 @@ func (p *Daemon) Stop() error {
|
||||||
return err
|
return err
|
||||||
//return p.Command.Process.Kill()
|
//return p.Command.Process.Kill()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Equal implements Proxy to check for equality.
|
||||||
|
func (p *Daemon) Equal(raw Proxy) bool {
|
||||||
|
p2, ok := raw.(*Daemon)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We compare equality on a subset of the command configuration
|
||||||
|
return p.ProxyToken == p2.ProxyToken &&
|
||||||
|
p.Command.Path == p2.Command.Path &&
|
||||||
|
p.Command.Dir == p2.Command.Dir &&
|
||||||
|
reflect.DeepEqual(p.Command.Args, p2.Command.Args) &&
|
||||||
|
reflect.DeepEqual(p.Command.Env, p2.Command.Env)
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package proxy
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -97,3 +98,93 @@ func TestDaemonRestart(t *testing.T) {
|
||||||
// File should re-appear because the process is restart
|
// File should re-appear because the process is restart
|
||||||
waitFile()
|
waitFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDaemonEqual(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Name string
|
||||||
|
D1, D2 Proxy
|
||||||
|
Expected bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Different type",
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{},
|
||||||
|
},
|
||||||
|
&Noop{},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Nil",
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{},
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Equal",
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{},
|
||||||
|
},
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Different path",
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{Path: "/foo"},
|
||||||
|
},
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{Path: "/bar"},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Different dir",
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{Dir: "/foo"},
|
||||||
|
},
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{Dir: "/bar"},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Different args",
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{Args: []string{"foo"}},
|
||||||
|
},
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{Args: []string{"bar"}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"Different token",
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{},
|
||||||
|
ProxyToken: "one",
|
||||||
|
},
|
||||||
|
&Daemon{
|
||||||
|
Command: &exec.Cmd{},
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -225,22 +225,35 @@ func (m *Manager) sync() {
|
||||||
for id, proxy := range m.proxies {
|
for id, proxy := range m.proxies {
|
||||||
// Get the proxy.
|
// Get the proxy.
|
||||||
stateProxy, ok := state[id]
|
stateProxy, ok := state[id]
|
||||||
if !ok {
|
if ok {
|
||||||
// Proxy is deregistered. Remove it from our map and stop it
|
// Remove the proxy from the state so we don't start it new.
|
||||||
delete(m.proxies, id)
|
delete(state, id)
|
||||||
if err := proxy.Stop(); err != nil {
|
|
||||||
m.Logger.Printf("[ERROR] agent/proxy: failed to stop deregistered proxy for %q: %s", id, err)
|
// Make the proxy so we can compare. This does not start it.
|
||||||
|
proxy2, err := m.newProxy(stateProxy)
|
||||||
|
if err != nil {
|
||||||
|
m.Logger.Printf("[ERROR] agent/proxy: failed to initialize proxy for %q: %s", id, err)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
// If the proxies are equal, then do nothing
|
||||||
|
if proxy.Equal(proxy2) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxies are not equal, so we should stop it. We add it
|
||||||
|
// back to the state here (unlikely case) so the loop below starts
|
||||||
|
// the new one.
|
||||||
|
state[id] = stateProxy
|
||||||
|
|
||||||
|
// Continue out of `if` as if proxy didn't exist so we stop it
|
||||||
}
|
}
|
||||||
|
|
||||||
// Proxy is in the state. Always delete it so that the remainder
|
// Proxy is deregistered. Remove it from our map and stop it
|
||||||
// are NEW proxies that we start after this loop.
|
delete(m.proxies, id)
|
||||||
delete(state, id)
|
if err := proxy.Stop(); err != nil {
|
||||||
|
m.Logger.Printf("[ERROR] agent/proxy: failed to stop deregistered proxy for %q: %s", id, err)
|
||||||
// TODO: diff and restart if necessary
|
}
|
||||||
println("DIFF", id, stateProxy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remaining entries in state are new proxies. Start them!
|
// Remaining entries in state are new proxies. Start them!
|
||||||
|
|
|
@ -134,6 +134,53 @@ func TestManagerRun_syncDelete(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestManagerRun_syncUpdate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
state := local.TestState(t)
|
||||||
|
m := NewManager()
|
||||||
|
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")
|
||||||
|
testStateProxy(t, state, "web", helperProcess("restart", path))
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
testStateProxy(t, state, "web", helperProcess("restart", path))
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// testStateProxy registers a proxy with the given local state and the command
|
// testStateProxy registers a proxy with the given local state and the command
|
||||||
// (expected to be from the helperProcess function call). It returns the
|
// (expected to be from the helperProcess function call). It returns the
|
||||||
// ID for deregistration.
|
// ID for deregistration.
|
||||||
|
|
|
@ -3,5 +3,6 @@ package proxy
|
||||||
// Noop implements Proxy and does nothing.
|
// Noop implements Proxy and does nothing.
|
||||||
type Noop struct{}
|
type Noop struct{}
|
||||||
|
|
||||||
func (p *Noop) Start() error { return nil }
|
func (p *Noop) Start() error { return nil }
|
||||||
func (p *Noop) Stop() error { return nil }
|
func (p *Noop) Stop() error { return nil }
|
||||||
|
func (p *Noop) Equal(Proxy) bool { return true }
|
||||||
|
|
|
@ -31,4 +31,12 @@ type Proxy interface {
|
||||||
// it should disallow Start from working again. If the proxy is already
|
// it should disallow Start from working again. If the proxy is already
|
||||||
// stopped, this should not return an error.
|
// stopped, this should not return an error.
|
||||||
Stop() error
|
Stop() error
|
||||||
|
|
||||||
|
// Equal returns true if the argument is equal to the proxy being called.
|
||||||
|
// This is called by the manager to determine if a change in configuration
|
||||||
|
// results in a proxy that needs to be restarted or not. If Equal returns
|
||||||
|
// false, then the manager will stop the old proxy and start the new one.
|
||||||
|
// If Equal returns true, the old proxy will remain running and the new
|
||||||
|
// one will be ignored.
|
||||||
|
Equal(Proxy) bool
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue