From 45a6bf7acd78f88d36278415cda78b6acd1e98df Mon Sep 17 00:00:00 2001 From: Nick Ethier Date: Mon, 26 Nov 2018 23:40:55 -0500 Subject: [PATCH 1/3] client/plugin: add generic plugin mananger interface and orchestration --- client/pluginmanager/group.go | 61 ++++++++++++++++++++++++++++ client/pluginmanager/group_test.go | 64 ++++++++++++++++++++++++++++++ client/pluginmanager/manager.go | 14 +++++++ client/pluginmanager/testing.go | 10 +++++ 4 files changed, 149 insertions(+) create mode 100644 client/pluginmanager/group.go create mode 100644 client/pluginmanager/group_test.go create mode 100644 client/pluginmanager/manager.go create mode 100644 client/pluginmanager/testing.go diff --git a/client/pluginmanager/group.go b/client/pluginmanager/group.go new file mode 100644 index 000000000..c84af1ce7 --- /dev/null +++ b/client/pluginmanager/group.go @@ -0,0 +1,61 @@ +package pluginmanager + +import ( + "fmt" + "sync" + + log "github.com/hashicorp/go-hclog" +) + +// PluginGroup is a utility struct to manage a collectivly orchestrate a +// set of PluginManagers +type PluginGroup struct { + // managers is the set of managers managed, access is synced by mLock + managers []PluginManager + + // shutdown indicates if shutdown has been called, access is synced by mLock + shutdown bool + + // mLock gaurds manangers and shutdown + mLock sync.Mutex + + logger log.Logger +} + +// New returns an initialized PluginGroup +func New(logger log.Logger) *PluginGroup { + return &PluginGroup{ + managers: []PluginManager{}, + logger: logger.Named("plugin"), + } +} + +// RegisterAndRun registers the manager and starts it in a seperate goroutine +func (m *PluginGroup) RegisterAndRun(manager PluginManager) error { + m.mLock.Lock() + if m.shutdown { + return fmt.Errorf("plugin group already shutdown") + } + m.managers = append(m.managers, manager) + m.mLock.Unlock() + + go func() { + m.logger.Info("starting plugin manager", "plugin-type", manager.PluginType()) + manager.Run() + m.logger.Info("plugin manager finished", "plugin-type", manager.PluginType()) + }() + return nil +} + +// Shutdown shutsdown all registered PluginManagers in reverse order of how +// they were started. +func (m *PluginGroup) Shutdown() { + m.mLock.Lock() + defer m.mLock.Unlock() + for i := len(m.managers) - 1; i >= 0; i-- { + fmt.Println(i) + m.logger.Info("shutting down plugin manager", "plugin-type", m.managers[i].PluginType()) + m.managers[i].Shutdown() + } + m.shutdown = true +} diff --git a/client/pluginmanager/group_test.go b/client/pluginmanager/group_test.go new file mode 100644 index 000000000..97621531b --- /dev/null +++ b/client/pluginmanager/group_test.go @@ -0,0 +1,64 @@ +package pluginmanager + +import ( + "sync" + "testing" + + "github.com/hashicorp/nomad/helper/testlog" + "github.com/stretchr/testify/require" +) + +func TestPluginGroup_RegisterAndRun(t *testing.T) { + t.Parallel() + require := require.New(t) + + var hasRun bool + var wg sync.WaitGroup + wg.Add(1) + mananger := &MockPluginManager{RunF: func() { + hasRun = true + wg.Done() + }} + + group := New(testlog.HCLogger(t)) + require.NoError(group.RegisterAndRun(mananger)) + wg.Wait() + require.True(hasRun) +} + +func TestPluginGroup_Shutdown(t *testing.T) { + t.Parallel() + require := require.New(t) + + var stack []int + var stackMu sync.Mutex + var runWg sync.WaitGroup + var shutdownWg sync.WaitGroup + group := New(testlog.HCLogger(t)) + for i := 1; i < 4; i++ { + i := i + runWg.Add(1) + shutdownWg.Add(1) + mananger := &MockPluginManager{RunF: func() { + stackMu.Lock() + defer stackMu.Unlock() + defer runWg.Done() + stack = append(stack, i) + }, ShutdownF: func() { + stackMu.Lock() + defer stackMu.Unlock() + defer shutdownWg.Done() + idx := len(stack) - 1 + val := stack[idx] + require.Equal(i, val) + stack = stack[:idx] + }} + require.NoError(group.RegisterAndRun(mananger)) + runWg.Wait() + } + group.Shutdown() + shutdownWg.Wait() + require.Empty(stack) + + require.Error(group.RegisterAndRun(&MockPluginManager{})) +} diff --git a/client/pluginmanager/manager.go b/client/pluginmanager/manager.go new file mode 100644 index 000000000..0acce2ac9 --- /dev/null +++ b/client/pluginmanager/manager.go @@ -0,0 +1,14 @@ +package pluginmanager + +// PluginManager orchestrates the lifecycle of a set of plugins +type PluginManager interface { + // Run starts a plugin manager and must block until shutdown + Run() + + // Shutdown should gracefully shutdown all plugins managed by the manager. + // It must block until shutdown is complete + Shutdown() + + // PluginType is the type of plugin which the manager manages + PluginType() string +} diff --git a/client/pluginmanager/testing.go b/client/pluginmanager/testing.go new file mode 100644 index 000000000..acb378414 --- /dev/null +++ b/client/pluginmanager/testing.go @@ -0,0 +1,10 @@ +package pluginmanager + +type MockPluginManager struct { + RunF func() + ShutdownF func() +} + +func (m *MockPluginManager) Run() { m.RunF() } +func (m *MockPluginManager) Shutdown() { m.ShutdownF() } +func (m *MockPluginManager) PluginType() string { return "mock" } From 600738e9912047a60a152bddefad60867e636c92 Mon Sep 17 00:00:00 2001 From: Nick Ethier Date: Tue, 27 Nov 2018 16:48:59 -0500 Subject: [PATCH 2/3] client/plugin: lint/spelling errors --- client/pluginmanager/group.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/pluginmanager/group.go b/client/pluginmanager/group.go index c84af1ce7..a8e4ace63 100644 --- a/client/pluginmanager/group.go +++ b/client/pluginmanager/group.go @@ -7,7 +7,7 @@ import ( log "github.com/hashicorp/go-hclog" ) -// PluginGroup is a utility struct to manage a collectivly orchestrate a +// PluginGroup is a utility struct to manage a collectively orchestrate a // set of PluginManagers type PluginGroup struct { // managers is the set of managers managed, access is synced by mLock @@ -30,7 +30,7 @@ func New(logger log.Logger) *PluginGroup { } } -// RegisterAndRun registers the manager and starts it in a seperate goroutine +// RegisterAndRun registers the manager and starts it in a separate goroutine func (m *PluginGroup) RegisterAndRun(manager PluginManager) error { m.mLock.Lock() if m.shutdown { From 60c6907ea520496a65b53f4f0c39e43944847e3f Mon Sep 17 00:00:00 2001 From: Nick Ethier Date: Tue, 27 Nov 2018 22:36:55 -0500 Subject: [PATCH 3/3] client/plugin: remove println from plugin group func --- client/pluginmanager/group.go | 1 - 1 file changed, 1 deletion(-) diff --git a/client/pluginmanager/group.go b/client/pluginmanager/group.go index a8e4ace63..db9c75674 100644 --- a/client/pluginmanager/group.go +++ b/client/pluginmanager/group.go @@ -53,7 +53,6 @@ func (m *PluginGroup) Shutdown() { m.mLock.Lock() defer m.mLock.Unlock() for i := len(m.managers) - 1; i >= 0; i-- { - fmt.Println(i) m.logger.Info("shutting down plugin manager", "plugin-type", m.managers[i].PluginType()) m.managers[i].Shutdown() }