isle/go/daemon/network/loader_test.go

436 lines
10 KiB
Go

package network
import (
"context"
"errors"
"fmt"
"io/fs"
"isle/bootstrap"
"isle/daemon/children"
"isle/daemon/daecommon"
"isle/nebula"
"isle/toolkit"
"os"
"path/filepath"
"testing"
"time"
"dev.mediocregopher.com/mediocre-go-lib.git/mlog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type loaderHarness struct {
ctx context.Context
logger *mlog.Logger
envBinDirPath string
stateDirPath string
runtimeDirPath string
constructors *mockConstructors
loader Loader
}
func newLoaderHarness(t *testing.T) *loaderHarness {
t.Parallel()
var (
ctx = context.Background()
logger = toolkit.NewTestLogger(t)
rootDir = toolkit.TempDir(t)
envBinDir, _ = rootDir.MkChildDir("bin", false)
stateDir, _ = rootDir.MkChildDir("state", false)
runtimeDir, _ = rootDir.MkChildDir("runtime", false)
constructors = newMockConstructors(t)
now = time.Date(2024, 12, 17, 16, 15, 14, 0, time.UTC)
)
loader, err := NewLoader(
ctx, logger, envBinDir.Path, &LoaderOpts{
EnvVars: daecommon.EnvVars{
StateDir: stateDir,
RuntimeDir: runtimeDir,
},
constructors: constructors,
nowFunc: func() time.Time { return now },
},
)
require.NoError(t, err)
return &loaderHarness{
ctx,
logger,
envBinDir.Path,
stateDir.Path,
runtimeDir.Path,
constructors,
loader,
}
}
func (h *loaderHarness) networkStateDirPath(networkID string) string {
return filepath.Join(h.stateDirPath, "networks", networkID)
}
func (h *loaderHarness) networkRuntimeDirPath(networkID string) string {
return filepath.Join(h.runtimeDirPath, "networks", networkID)
}
func (h *loaderHarness) join(
t *testing.T, creationParams bootstrap.CreationParams,
) *MockNetwork {
var (
joiningBootstrap = JoiningBootstrap{
Bootstrap: bootstrap.Bootstrap{
NetworkCreationParams: creationParams,
},
}
networkStateDirPath = h.networkStateDirPath(creationParams.ID)
networkRuntimeDirPath = h.networkRuntimeDirPath(creationParams.ID)
network = NewMockNetwork(t)
)
h.constructors.
On(
"join",
toolkit.MockArg[context.Context](),
toolkit.MockArg[*mlog.Logger](),
h.envBinDirPath,
toolkit.MockArg[*children.NebulaDeviceNamer](),
joiningBootstrap,
toolkit.Dir{Path: networkStateDirPath},
toolkit.Dir{Path: networkRuntimeDirPath},
(*Opts)(nil),
).
Return(network, nil).
Once()
got, err := h.loader.Join(
h.ctx,
h.logger,
joiningBootstrap,
nil,
)
require.NoError(t, err)
require.Equal(t, got, network)
require.NoError(t, writeBootstrapToStateDir(
networkStateDirPath, joiningBootstrap.Bootstrap,
))
return network
}
func (h *loaderHarness) assertDirExists(
t *testing.T, exists bool, path string,
) {
stat, err := os.Stat(path)
if exists {
assert.NoError(t, err)
assert.Equal(t, os.ModeDir, stat.Mode().Type())
} else {
assert.ErrorIs(t, err, fs.ErrNotExist)
}
}
func TestLoader_Loadable(t *testing.T) {
allCreationParams := []bootstrap.CreationParams{
bootstrap.NewCreationParams("AAA", "a.com"),
bootstrap.NewCreationParams("BBB", "b.com"),
}
t.Run("empty", func(t *testing.T) {
h := newLoaderHarness(t)
got, err := h.loader.Loadable(h.ctx)
assert.NoError(t, err)
assert.Empty(t, got)
})
t.Run("single", func(t *testing.T) {
h := newLoaderHarness(t)
h.join(t, allCreationParams[0])
got, err := h.loader.Loadable(h.ctx)
assert.NoError(t, err)
assert.Equal(t, allCreationParams[:1], got)
})
t.Run("multiple", func(t *testing.T) {
h := newLoaderHarness(t)
h.join(t, allCreationParams[0])
h.join(t, allCreationParams[1])
got, err := h.loader.Loadable(h.ctx)
assert.NoError(t, err)
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) {
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)
network = h.join(t, creationParams)
)
h.constructors.
On(
"load",
toolkit.MockArg[context.Context](),
toolkit.MockArg[*mlog.Logger](),
h.envBinDirPath,
toolkit.MockArg[*children.NebulaDeviceNamer](),
toolkit.Dir{Path: networkStateDirPath},
toolkit.Dir{Path: networkRuntimeDirPath},
(*Opts)(nil),
).
Return(network, nil).
Once()
got, err := h.loader.Load(
h.ctx,
h.logger,
creationParams,
nil,
)
assert.NoError(t, err)
assert.Equal(t, network, got)
})
t.Run("error/not yet joined", func(t *testing.T) {
var (
h = newLoaderHarness(t)
networkStateDirPath = h.networkStateDirPath(creationParams.ID)
networkRuntimeDirPath = h.networkRuntimeDirPath(creationParams.ID)
)
_, err := h.loader.Load(
h.ctx,
h.logger,
creationParams,
nil,
)
assert.ErrorContains(t, err, "not yet joined")
h.assertDirExists(t, false, networkStateDirPath)
h.assertDirExists(t, false, networkRuntimeDirPath)
})
}
func TestLoader_Join(t *testing.T) {
var (
creationParams = bootstrap.NewCreationParams("AAA", "a.com")
joiningBootstrap = JoiningBootstrap{
Bootstrap: bootstrap.Bootstrap{
NetworkCreationParams: creationParams,
},
}
network = NewMockNetwork(t)
)
t.Run("success", func(t *testing.T) {
var (
h = newLoaderHarness(t)
networkStateDirPath = h.networkStateDirPath(creationParams.ID)
networkRuntimeDirPath = h.networkRuntimeDirPath(creationParams.ID)
)
h.constructors.
On(
"join",
toolkit.MockArg[context.Context](),
toolkit.MockArg[*mlog.Logger](),
h.envBinDirPath,
toolkit.MockArg[*children.NebulaDeviceNamer](),
joiningBootstrap,
toolkit.Dir{Path: networkStateDirPath},
toolkit.Dir{Path: networkRuntimeDirPath},
(*Opts)(nil),
).
Return(network, nil).
Once()
got, err := h.loader.Join(
h.ctx,
h.logger,
joiningBootstrap,
nil,
)
assert.NoError(t, err)
assert.Equal(t, network, got)
h.assertDirExists(t, true, networkStateDirPath)
h.assertDirExists(t, true, networkRuntimeDirPath)
})
t.Run("error/constructor", func(t *testing.T) {
var (
h = newLoaderHarness(t)
networkStateDirPath = h.networkStateDirPath(creationParams.ID)
networkRuntimeDirPath = h.networkRuntimeDirPath(creationParams.ID)
wantErr = errors.New("some error")
)
h.constructors.
On(
"join",
toolkit.MockArg[context.Context](),
toolkit.MockArg[*mlog.Logger](),
h.envBinDirPath,
toolkit.MockArg[*children.NebulaDeviceNamer](),
joiningBootstrap,
toolkit.Dir{Path: networkStateDirPath},
toolkit.Dir{Path: networkRuntimeDirPath},
(*Opts)(nil),
).
Return(nil, wantErr).
Once()
_, err := h.loader.Join(
h.ctx,
h.logger,
joiningBootstrap,
nil,
)
assert.ErrorIs(t, err, wantErr)
h.assertDirExists(t, false, networkStateDirPath)
h.assertDirExists(t, false, networkRuntimeDirPath)
})
}
func TestLoader_Create(t *testing.T) {
var (
creationParams = bootstrap.NewCreationParams("AAA", "a.com")
ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24")
hostName = nebula.HostName("foo")
network = NewMockNetwork(t)
)
t.Run("success", func(t *testing.T) {
var (
h = newLoaderHarness(t)
networkStateDirPath = h.networkStateDirPath(creationParams.ID)
networkRuntimeDirPath = h.networkRuntimeDirPath(creationParams.ID)
)
h.constructors.
On(
"create",
toolkit.MockArg[context.Context](),
toolkit.MockArg[*mlog.Logger](),
h.envBinDirPath,
toolkit.MockArg[*children.NebulaDeviceNamer](),
toolkit.Dir{Path: networkStateDirPath},
toolkit.Dir{Path: networkRuntimeDirPath},
creationParams,
ipNet,
hostName,
(*Opts)(nil),
).
Return(network, nil).
Once()
got, err := h.loader.Create(
h.ctx,
h.logger,
creationParams,
ipNet,
hostName,
nil,
)
assert.NoError(t, err)
assert.Equal(t, network, got)
h.assertDirExists(t, true, networkStateDirPath)
h.assertDirExists(t, true, networkRuntimeDirPath)
})
t.Run("error", func(t *testing.T) {
var (
h = newLoaderHarness(t)
networkStateDirPath = h.networkStateDirPath(creationParams.ID)
networkRuntimeDirPath = h.networkRuntimeDirPath(creationParams.ID)
wantErr = errors.New("some error")
)
h.constructors.
On(
"create",
toolkit.MockArg[context.Context](),
toolkit.MockArg[*mlog.Logger](),
h.envBinDirPath,
toolkit.MockArg[*children.NebulaDeviceNamer](),
toolkit.Dir{Path: networkStateDirPath},
toolkit.Dir{Path: networkRuntimeDirPath},
creationParams,
ipNet,
hostName,
(*Opts)(nil),
).
Return(nil, wantErr).
Once()
_, err := h.loader.Create(
h.ctx,
h.logger,
creationParams,
ipNet,
hostName,
nil,
)
assert.ErrorIs(t, err, wantErr)
h.assertDirExists(t, false, networkStateDirPath)
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)
})
}