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) }) }