mirror of
https://github.com/slackhq/nebula.git
synced 2026-11-16 00:06:52 +01:00
Add a local control socket and nebula ctl (#1975)
Add support for accessing the previously ssh-only debug commands via `nebula ctl <command>.` - unix socket (named pipe on windows) for communication - sockets scoped to the user + an optional group - in cmd/nebula and cmd/nebula-service, but not nebula.Main Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
9a5bdb508e
commit
0f767ce529
54 files changed
+7581
-925
No files matched your search
+19
-1
@@ -32,6 +32,11 @@ type C struct {
|
||||
|
||||
// Set by the first reload that needs a restart and never cleared. Guarded by reloadLock
|
||||
needsRestart bool
|
||||
|
||||
// callbackLock guards callbacks. Registration is not confined to startup: CatchHUP is
|
||||
// running from inside Main, and anything wired up after Main returns registers while
|
||||
// a HUP could already be reloading, so append and iteration have to be serialized.
|
||||
callbackLock sync.Mutex
|
||||
}
|
||||
|
||||
func NewC(l *slog.Logger) *C {
|
||||
@@ -78,9 +83,22 @@ func (c *C) LoadString(raw string) error {
|
||||
// These functions should return quickly or spawn their own go routine if they will take a while.
|
||||
// Use r to report anything that failed or needs a restart to apply.
|
||||
func (c *C) RegisterReloadCallback(f func(c *C, r *Reload)) {
|
||||
c.callbackLock.Lock()
|
||||
defer c.callbackLock.Unlock()
|
||||
c.callbacks = append(c.callbacks, f)
|
||||
}
|
||||
|
||||
// reloadCallbacks returns a snapshot of the registered callbacks. Reloads iterate the
|
||||
// snapshot rather than the live slice, so a registration from another goroutine cannot
|
||||
// race the loop; a callback registered while a reload is in progress runs from the next
|
||||
// reload on. callbackLock is not held during the calls themselves, so a callback that
|
||||
// registers another callback does not deadlock.
|
||||
func (c *C) reloadCallbacks() []func(*C, *Reload) {
|
||||
c.callbackLock.Lock()
|
||||
defer c.callbackLock.Unlock()
|
||||
return append(make([]func(*C, *Reload), 0, len(c.callbacks)), c.callbacks...)
|
||||
}
|
||||
|
||||
// InitialLoad returns true if this is the first load of the config, and ReloadConfig has not been called yet.
|
||||
func (c *C) InitialLoad() bool {
|
||||
return c.oldSettings == nil
|
||||
@@ -204,7 +222,7 @@ func (c *C) ReloadConfigString(raw string) ReloadResult {
|
||||
|
||||
func (c *C) runCallbacks() ReloadResult {
|
||||
r := &Reload{c: c}
|
||||
for _, v := range c.callbacks {
|
||||
for _, v := range c.reloadCallbacks() {
|
||||
v(c, r)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -220,3 +221,41 @@ firewall:
|
||||
}
|
||||
assert.Equal(t, expected, m)
|
||||
}
|
||||
|
||||
// TestRegisterReloadCallbackDuringReload: registration is not confined to startup. The
|
||||
// HUP handler runs from inside nebula.Main, and anything wired after Main returns
|
||||
// registers while a reload may already be iterating the callbacks. Under -race this is
|
||||
// the data race on the slice; without it, it checks a callback registered mid-reload is
|
||||
// picked up by the next one.
|
||||
func TestRegisterReloadCallbackDuringReload(t *testing.T) {
|
||||
l := test.NewLogger()
|
||||
c := NewC(l)
|
||||
require.NoError(t, c.LoadString("a: 1"))
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 200; i++ {
|
||||
c.RegisterReloadCallback(func(*C, *Reload) {})
|
||||
}
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 200; i++ {
|
||||
assert.NoError(t, c.ReloadConfigString("a: 1").Err)
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
// A callback that registers another callback must not deadlock, and the new one
|
||||
// runs from the following reload on.
|
||||
var ran int
|
||||
c.RegisterReloadCallback(func(c *C, _ *Reload) {
|
||||
c.RegisterReloadCallback(func(*C, *Reload) { ran++ })
|
||||
})
|
||||
require.NoError(t, c.ReloadConfigString("a: 1").Err)
|
||||
assert.Equal(t, 0, ran, "registered during this reload, so not run by it")
|
||||
require.NoError(t, c.ReloadConfigString("a: 1").Err)
|
||||
assert.Equal(t, 1, ran, "and run by the next")
|
||||
}
|
||||
Reference in new issue
Block a user