Implement 'network leave'
This commit is contained in:
parent
53a1dc0cc2
commit
279b70124c
@ -177,6 +177,31 @@ var subCmdNetworkJoin = subCmd{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subCmdNetworkLeave = subCmd{
|
||||||
|
name: "leave",
|
||||||
|
descr: "Leaves a network which was previously joined or created",
|
||||||
|
do: func(ctx subCmdCtx) error {
|
||||||
|
yesImSure := ctx.flags.Bool("yes-im-sure", false, "Must be given")
|
||||||
|
|
||||||
|
ctx, err := ctx.withParsedFlags(nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("parsing flags: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !*yesImSure {
|
||||||
|
return errors.New("--yes-im-sure must be given")
|
||||||
|
}
|
||||||
|
|
||||||
|
daemonRPC, err := ctx.newDaemonRPC()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("creating daemon RPC client: %w", err)
|
||||||
|
}
|
||||||
|
defer daemonRPC.Close()
|
||||||
|
|
||||||
|
return daemonRPC.LeaveNetwork(ctx)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var subCmdNetworkList = subCmd{
|
var subCmdNetworkList = subCmd{
|
||||||
name: "list",
|
name: "list",
|
||||||
descr: "Lists all networks which have been joined",
|
descr: "Lists all networks which have been joined",
|
||||||
@ -290,6 +315,7 @@ var subCmdNetwork = subCmd{
|
|||||||
return ctx.doSubCmd(
|
return ctx.doSubCmd(
|
||||||
subCmdNetworkCreate,
|
subCmdNetworkCreate,
|
||||||
subCmdNetworkJoin,
|
subCmdNetworkJoin,
|
||||||
|
subCmdNetworkLeave,
|
||||||
subCmdNetworkList,
|
subCmdNetworkList,
|
||||||
subCmdNetworkGetConfig,
|
subCmdNetworkGetConfig,
|
||||||
)
|
)
|
||||||
|
@ -106,6 +106,15 @@ func (c *rpcClient) JoinNetwork(ctx context.Context, j1 network.JoiningBootstrap
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *rpcClient) LeaveNetwork(ctx context.Context) (err error) {
|
||||||
|
err = c.client.Call(
|
||||||
|
ctx,
|
||||||
|
nil,
|
||||||
|
"LeaveNetwork",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (c *rpcClient) RemoveHost(ctx context.Context, hostName nebula.HostName) (err error) {
|
func (c *rpcClient) RemoveHost(ctx context.Context, hostName nebula.HostName) (err error) {
|
||||||
err = c.client.Call(
|
err = c.client.Call(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -257,6 +257,30 @@ func withNetwork[Res any](
|
|||||||
return fn(ctx, network)
|
return fn(ctx, network)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LeaveNetwork picks the network out of the Context which was embedded by
|
||||||
|
// WithNetwork, and leaves it. The Network will no longer be considered joined
|
||||||
|
// and will not be active after this returns.
|
||||||
|
//
|
||||||
|
// Errors:
|
||||||
|
// - ErrNoNetwork
|
||||||
|
// - ErrNoMatchingNetworks
|
||||||
|
// - ErrMultipleMatchingNetworks
|
||||||
|
func (d *Daemon) LeaveNetwork(ctx context.Context) error {
|
||||||
|
d.l.Lock()
|
||||||
|
defer d.l.Unlock()
|
||||||
|
|
||||||
|
network, err := pickNetwork(ctx, d.networkLoader, d.networks)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdownErr := network.Shutdown()
|
||||||
|
loaderLeaveErr := d.networkLoader.Leave(ctx, network.creationParams)
|
||||||
|
delete(d.networks, network.creationParams.ID)
|
||||||
|
|
||||||
|
return errors.Join(shutdownErr, loaderLeaveErr)
|
||||||
|
}
|
||||||
|
|
||||||
// GetNetworks returns all networks which have been joined by the Daemon,
|
// GetNetworks returns all networks which have been joined by the Daemon,
|
||||||
// ordered by their name.
|
// ordered by their name.
|
||||||
func (d *Daemon) GetNetworks(
|
func (d *Daemon) GetNetworks(
|
||||||
@ -401,7 +425,13 @@ func (d *Daemon) GetConfig(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetConfig implements the method for the network.RPC interface.
|
// SetConfig extends the [network.RPC] method of the same name such that
|
||||||
|
// [ErrManagedNetworkConfig] is returned if the picked network is
|
||||||
|
// configured as part of the [daecommon.Config] which the Daemon was
|
||||||
|
// initialized with.
|
||||||
|
//
|
||||||
|
// See the `network.RPC` documentation in this interface for more usage
|
||||||
|
// details.
|
||||||
func (d *Daemon) SetConfig(
|
func (d *Daemon) SetConfig(
|
||||||
ctx context.Context, networkConfig daecommon.NetworkConfig,
|
ctx context.Context, networkConfig daecommon.NetworkConfig,
|
||||||
) error {
|
) error {
|
||||||
|
@ -363,6 +363,35 @@ func TestDaemon_CreateNetwork(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDaemon_LeaveNetwork(t *testing.T) {
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
networkA = network.NewMockNetwork(t)
|
||||||
|
creationParamsA = bootstrap.NewCreationParams("AAA", "a.com")
|
||||||
|
h = newHarness(t, &harnessOpts{
|
||||||
|
expectNetworksLoaded: []expectNetworkLoad{{
|
||||||
|
creationParamsA, nil, networkA,
|
||||||
|
}},
|
||||||
|
expectStoredConfigs: map[string]daecommon.NetworkConfig{
|
||||||
|
creationParamsA.ID: daecommon.NewNetworkConfig(nil),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
h.networkLoader.
|
||||||
|
On("Leave", toolkit.MockArg[context.Context](), creationParamsA).
|
||||||
|
Return(nil).
|
||||||
|
Once()
|
||||||
|
|
||||||
|
ctx := WithNetwork(h.ctx, "AAA")
|
||||||
|
assert.NoError(t, h.daemon.LeaveNetwork(ctx))
|
||||||
|
|
||||||
|
joinedCreationParams, err := h.daemon.GetNetworks(h.ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, joinedCreationParams)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestDaemon_SetConfig(t *testing.T) {
|
func TestDaemon_SetConfig(t *testing.T) {
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
var (
|
var (
|
||||||
|
@ -30,6 +30,25 @@ func writeBootstrapToStateDir(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadBootstrapFromStateDir(
|
||||||
|
stateDirPath string,
|
||||||
|
) (
|
||||||
|
bootstrap.Bootstrap, error,
|
||||||
|
) {
|
||||||
|
var (
|
||||||
|
currBootstrap bootstrap.Bootstrap
|
||||||
|
bootstrapFilePath = bootstrap.StateDirPath(stateDirPath)
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := jsonutil.LoadFile(&currBootstrap, bootstrapFilePath); err != nil {
|
||||||
|
return bootstrap.Bootstrap{}, fmt.Errorf(
|
||||||
|
"loading bootstrap from %q: %w", bootstrapFilePath, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return currBootstrap, nil
|
||||||
|
}
|
||||||
|
|
||||||
func coalesceNetworkConfigAndBootstrap(
|
func coalesceNetworkConfigAndBootstrap(
|
||||||
networkConfig daecommon.NetworkConfig, hostBootstrap bootstrap.Bootstrap,
|
networkConfig daecommon.NetworkConfig, hostBootstrap bootstrap.Bootstrap,
|
||||||
) (
|
) (
|
||||||
|
@ -12,6 +12,9 @@ import (
|
|||||||
"isle/nebula"
|
"isle/nebula"
|
||||||
"isle/toolkit"
|
"isle/toolkit"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
)
|
)
|
||||||
@ -132,6 +135,9 @@ type Loader interface {
|
|||||||
) (
|
) (
|
||||||
Network, error,
|
Network, error,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Leave marks a previously loadable Network as being no longer loadable.
|
||||||
|
Leave(context.Context, bootstrap.CreationParams) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoaderOpts are optional parameters which can be passed in when initializing a
|
// LoaderOpts are optional parameters which can be passed in when initializing a
|
||||||
@ -141,6 +147,7 @@ type LoaderOpts struct {
|
|||||||
EnvVars daecommon.EnvVars
|
EnvVars daecommon.EnvVars
|
||||||
|
|
||||||
constructors constructors // defaults to newConstructors()
|
constructors constructors // defaults to newConstructors()
|
||||||
|
nowFunc func() time.Time // defaults to time.Now
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *LoaderOpts) withDefaults() *LoaderOpts {
|
func (o *LoaderOpts) withDefaults() *LoaderOpts {
|
||||||
@ -156,6 +163,10 @@ func (o *LoaderOpts) withDefaults() *LoaderOpts {
|
|||||||
o.constructors = newConstructors()
|
o.constructors = newConstructors()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if o.nowFunc == nil {
|
||||||
|
o.nowFunc = time.Now
|
||||||
|
}
|
||||||
|
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,6 +237,10 @@ func (l *loader) Loadable(
|
|||||||
creationParams := make([]bootstrap.CreationParams, 0, len(networkStateDirs))
|
creationParams := make([]bootstrap.CreationParams, 0, len(networkStateDirs))
|
||||||
|
|
||||||
for _, networkStateDir := range networkStateDirs {
|
for _, networkStateDir := range networkStateDirs {
|
||||||
|
if n := filepath.Base(networkStateDir.Path); strings.HasPrefix(n, ".") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
thisCreationParams, err := loadCreationParams(networkStateDir)
|
thisCreationParams, err := loadCreationParams(networkStateDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
@ -390,3 +405,50 @@ func (l *loader) Create(
|
|||||||
|
|
||||||
return n, nil
|
return n, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *loader) Leave(
|
||||||
|
ctx context.Context, creationParams bootstrap.CreationParams,
|
||||||
|
) error {
|
||||||
|
networkID := creationParams.ID
|
||||||
|
|
||||||
|
if isJoined, err := l.isJoined(ctx, networkID); err != nil {
|
||||||
|
return fmt.Errorf("checking if network is already joined: %w", err)
|
||||||
|
} else if !isJoined {
|
||||||
|
return errors.New("network is not yet joined")
|
||||||
|
}
|
||||||
|
|
||||||
|
networkStateDir, networkRuntimeDir, err := networkDirs(
|
||||||
|
l.networksStateDir, l.networksRuntimeDir, networkID, true,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"creating sub-directories for network %q: %w", networkID, err,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
newNetworkStateDirName = fmt.Sprintf(
|
||||||
|
".%s.%s.bak",
|
||||||
|
creationParams.ID,
|
||||||
|
l.opts.nowFunc().UTC().Format("20060102-150405"),
|
||||||
|
)
|
||||||
|
newNetworkStateDirPath = filepath.Join(
|
||||||
|
l.networksStateDir.Path,
|
||||||
|
newNetworkStateDirName,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
if err := os.Rename(
|
||||||
|
networkStateDir.Path, newNetworkStateDirPath,
|
||||||
|
); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(networkRuntimeDir.Path); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.Join(errs...)
|
||||||
|
}
|
||||||
|
@ -80,6 +80,24 @@ func (_m *MockLoader) Join(_a0 context.Context, _a1 *mlog.Logger, _a2 JoiningBoo
|
|||||||
return r0, r1
|
return r0, r1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Leave provides a mock function with given fields: _a0, _a1
|
||||||
|
func (_m *MockLoader) Leave(_a0 context.Context, _a1 bootstrap.CreationParams) error {
|
||||||
|
ret := _m.Called(_a0, _a1)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for Leave")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, bootstrap.CreationParams) error); ok {
|
||||||
|
r0 = rf(_a0, _a1)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// Load provides a mock function with given fields: _a0, _a1, _a2, _a3
|
// Load provides a mock function with given fields: _a0, _a1, _a2, _a3
|
||||||
func (_m *MockLoader) Load(_a0 context.Context, _a1 *mlog.Logger, _a2 bootstrap.CreationParams, _a3 *Opts) (Network, error) {
|
func (_m *MockLoader) Load(_a0 context.Context, _a1 *mlog.Logger, _a2 bootstrap.CreationParams, _a3 *Opts) (Network, error) {
|
||||||
ret := _m.Called(_a0, _a1, _a2, _a3)
|
ret := _m.Called(_a0, _a1, _a2, _a3)
|
||||||
|
@ -3,6 +3,7 @@ package network
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"isle/bootstrap"
|
"isle/bootstrap"
|
||||||
"isle/daemon/children"
|
"isle/daemon/children"
|
||||||
@ -12,6 +13,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -40,6 +42,7 @@ func newLoaderHarness(t *testing.T) *loaderHarness {
|
|||||||
stateDir, _ = rootDir.MkChildDir("state", false)
|
stateDir, _ = rootDir.MkChildDir("state", false)
|
||||||
runtimeDir, _ = rootDir.MkChildDir("runtime", false)
|
runtimeDir, _ = rootDir.MkChildDir("runtime", false)
|
||||||
constructors = newMockConstructors(t)
|
constructors = newMockConstructors(t)
|
||||||
|
now = time.Date(2024, 12, 17, 16, 15, 14, 0, time.UTC)
|
||||||
)
|
)
|
||||||
|
|
||||||
loader, err := NewLoader(
|
loader, err := NewLoader(
|
||||||
@ -49,6 +52,7 @@ func newLoaderHarness(t *testing.T) *loaderHarness {
|
|||||||
RuntimeDir: runtimeDir,
|
RuntimeDir: runtimeDir,
|
||||||
},
|
},
|
||||||
constructors: constructors,
|
constructors: constructors,
|
||||||
|
nowFunc: func() time.Time { return now },
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -159,6 +163,16 @@ func TestLoader_Loadable(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.ElementsMatch(t, allCreationParams, got)
|
assert.ElementsMatch(t, allCreationParams, got)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("after Leave", func(t *testing.T) {
|
||||||
|
h := newLoaderHarness(t)
|
||||||
|
h.join(t, allCreationParams[0])
|
||||||
|
h.join(t, allCreationParams[1])
|
||||||
|
assert.NoError(t, h.loader.Leave(h.ctx, allCreationParams[1]))
|
||||||
|
got, err := h.loader.Loadable(h.ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, allCreationParams[:1], got)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoader_Load(t *testing.T) {
|
func TestLoader_Load(t *testing.T) {
|
||||||
@ -387,3 +401,35 @@ func TestLoader_Create(t *testing.T) {
|
|||||||
h.assertDirExists(t, false, networkRuntimeDirPath)
|
h.assertDirExists(t, false, networkRuntimeDirPath)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoader_Leave(t *testing.T) {
|
||||||
|
var (
|
||||||
|
creationParams = bootstrap.NewCreationParams("AAA", "a.com")
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
h = newLoaderHarness(t)
|
||||||
|
networkStateDirPath = h.networkStateDirPath(creationParams.ID)
|
||||||
|
networkRuntimeDirPath = h.networkRuntimeDirPath(creationParams.ID)
|
||||||
|
)
|
||||||
|
|
||||||
|
h.join(t, creationParams)
|
||||||
|
|
||||||
|
assert.NoError(t, h.loader.Leave(h.ctx, creationParams))
|
||||||
|
h.assertDirExists(t, false, networkStateDirPath)
|
||||||
|
h.assertDirExists(t, false, networkRuntimeDirPath)
|
||||||
|
|
||||||
|
wantStateDirRenamedTo := h.networkStateDirPath(fmt.Sprintf(
|
||||||
|
".%s.20241217-161514.bak",
|
||||||
|
creationParams.ID,
|
||||||
|
))
|
||||||
|
t.Logf("wantStateDirRenamedTo:%q", wantStateDirRenamedTo)
|
||||||
|
h.assertDirExists(t, true, wantStateDirRenamedTo)
|
||||||
|
|
||||||
|
// Make sure the data inside the state directory was actually preserved.
|
||||||
|
bootstrap, err := loadBootstrapFromStateDir(wantStateDirRenamedTo)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, creationParams, bootstrap.NetworkCreationParams)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -288,16 +288,12 @@ func (constructorsImpl) load(
|
|||||||
return nil, fmt.Errorf("instantiating Network: %w", err)
|
return nil, fmt.Errorf("instantiating Network: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
currBootstrap, err := loadBootstrapFromStateDir(n.stateDir.Path)
|
||||||
currBootstrap bootstrap.Bootstrap
|
if err != nil {
|
||||||
bootstrapFilePath = bootstrap.StateDirPath(n.stateDir.Path)
|
return nil, fmt.Errorf("loading bootstrap from state dir: %w", err)
|
||||||
)
|
}
|
||||||
|
|
||||||
if err := jsonutil.LoadFile(&currBootstrap, bootstrapFilePath); err != nil {
|
if err := n.initialize(ctx, currBootstrap, false); err != nil {
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"loading bootstrap from %q: %w", bootstrapFilePath, err,
|
|
||||||
)
|
|
||||||
} else if err := n.initialize(ctx, currBootstrap, false); err != nil {
|
|
||||||
return nil, fmt.Errorf("initializing with bootstrap: %w", err)
|
return nil, fmt.Errorf("initializing with bootstrap: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,15 +43,10 @@ type RPC interface {
|
|||||||
|
|
||||||
JoinNetwork(context.Context, network.JoiningBootstrap) error
|
JoinNetwork(context.Context, network.JoiningBootstrap) error
|
||||||
|
|
||||||
|
LeaveNetwork(context.Context) error
|
||||||
|
|
||||||
GetNetworks(context.Context) ([]bootstrap.CreationParams, error)
|
GetNetworks(context.Context) ([]bootstrap.CreationParams, error)
|
||||||
|
|
||||||
// SetConfig extends the [network.RPC] method of the same name such that
|
|
||||||
// [ErrManagedNetworkConfig] is returned if the picked network is
|
|
||||||
// configured as part of the [daecommon.Config] which the Daemon was
|
|
||||||
// initialized with.
|
|
||||||
//
|
|
||||||
// See the `network.RPC` documentation in this interface for more usage
|
|
||||||
// details.
|
|
||||||
SetConfig(context.Context, daecommon.NetworkConfig) error
|
SetConfig(context.Context, daecommon.NetworkConfig) error
|
||||||
|
|
||||||
// All network.RPC methods are automatically implemented by Daemon using the
|
// All network.RPC methods are automatically implemented by Daemon using the
|
||||||
|
@ -226,6 +226,24 @@ func (_m *MockRPC) JoinNetwork(_a0 context.Context, _a1 network.JoiningBootstrap
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LeaveNetwork provides a mock function with given fields: _a0
|
||||||
|
func (_m *MockRPC) LeaveNetwork(_a0 context.Context) error {
|
||||||
|
ret := _m.Called(_a0)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for LeaveNetwork")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||||
|
r0 = rf(_a0)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveHost provides a mock function with given fields: ctx, hostName
|
// RemoveHost provides a mock function with given fields: ctx, hostName
|
||||||
func (_m *MockRPC) RemoveHost(ctx context.Context, hostName nebula.HostName) error {
|
func (_m *MockRPC) RemoveHost(ctx context.Context, hostName nebula.HostName) error {
|
||||||
ret := _m.Called(ctx, hostName)
|
ret := _m.Called(ctx, hostName)
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
type: task
|
|
||||||
---
|
|
||||||
|
|
||||||
# Implement `network leave` sub-command
|
|
||||||
|
|
||||||
When leaving a network the daemon should remove the now-defunct state-directory,
|
|
||||||
or at least mark it as inactive in some way.
|
|
Loading…
Reference in New Issue
Block a user