package daemon import ( "context" "isle/bootstrap" "isle/daemon/daecommon" "isle/daemon/network" "isle/nebula" "isle/toolkit" "testing" "dev.mediocregopher.com/mediocre-go-lib.git/mlog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type expectNetworkLoad struct { creationParams bootstrap.CreationParams networkConfig *daecommon.NetworkConfig network *network.MockNetwork } type harnessOpts struct { config daecommon.Config expectNetworksLoaded []expectNetworkLoad expectStoredConfigs map[string]daecommon.NetworkConfig } func (o *harnessOpts) withDefaults() *harnessOpts { if o == nil { o = new(harnessOpts) } return o } type harness struct { ctx context.Context networkLoader *network.MockLoader daemon *Daemon } func newHarness(t *testing.T, opts *harnessOpts) *harness { t.Parallel() opts = opts.withDefaults() var ( ctx = context.Background() logger = toolkit.NewTestLogger(t) networkLoader = network.NewMockLoader(t) ) expectLoadable := make( []bootstrap.CreationParams, len(opts.expectNetworksLoaded), ) for i, expectNetworkLoaded := range opts.expectNetworksLoaded { expectLoadable[i] = expectNetworkLoaded.creationParams networkLoader. On( "Load", toolkit.MockArg[context.Context](), toolkit.MockArg[*mlog.Logger](), expectNetworkLoaded.creationParams, &network.Opts{ Config: expectNetworkLoaded.networkConfig, }, ). Return(expectNetworkLoaded.network, nil). Once() expectNetworkLoaded.network.On("Shutdown").Return(nil).Once() } for id, networkConfig := range opts.expectStoredConfigs { networkLoader. On("StoredConfig", toolkit.MockArg[context.Context](), id). Return(networkConfig, nil). Once() } networkLoader. On("Loadable", toolkit.MockArg[context.Context]()). Return(expectLoadable, nil) daemon, err := New(ctx, logger, networkLoader, opts.config) require.NoError(t, err) t.Cleanup(func() { t.Log("Shutting down Daemon") assert.NoError(t, daemon.Shutdown()) }) return &harness{ctx, networkLoader, daemon} } func TestNew(t *testing.T) { t.Run("no networks loaded", func(t *testing.T) { _ = newHarness(t, nil) }) t.Run("DEPRECATED network config matching", func(t *testing.T) { var ( creationParams = bootstrap.NewCreationParams("AAA", "a.com") networkConfig = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.DNS.Resolvers = []string{"foo"} }) config = daecommon.Config{ Networks: map[string]daecommon.NetworkConfig{ daecommon.DeprecatedNetworkID: networkConfig, }, } ) _ = newHarness(t, &harnessOpts{ config: config, expectNetworksLoaded: []expectNetworkLoad{ { creationParams, &networkConfig, network.NewMockNetwork(t), }, }, }) }) t.Run("network config matching", func(t *testing.T) { var ( creationParamsA = bootstrap.NewCreationParams("AAA", "a.com") creationParamsB = bootstrap.NewCreationParams("BBB", "b.com") creationParamsC = bootstrap.NewCreationParams("CCC", "c.com") creationParamsD = bootstrap.NewCreationParams("DDD", "d.com") networkConfigA = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.DNS.Resolvers = []string{"foo"} }) networkConfigB = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.VPN.PublicAddr = "1.2.3.4:5" }) networkConfigC = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.Storage.Allocations = []daecommon.ConfigStorageAllocation{ { DataPath: "/path/data", MetaPath: "/path/meta", Capacity: 1, }, } }) config = daecommon.Config{ Networks: map[string]daecommon.NetworkConfig{ creationParamsA.ID: networkConfigA, creationParamsB.Name: networkConfigB, creationParamsC.Domain: networkConfigC, "unknown": {}, }, } ) _ = newHarness(t, &harnessOpts{ config: config, expectNetworksLoaded: []expectNetworkLoad{ { creationParamsA, &networkConfigA, network.NewMockNetwork(t), }, { creationParamsB, &networkConfigB, network.NewMockNetwork(t), }, { creationParamsC, &networkConfigC, network.NewMockNetwork(t), }, { creationParamsD, nil, network.NewMockNetwork(t), }, }, expectStoredConfigs: map[string]daecommon.NetworkConfig{ creationParamsD.ID: daecommon.NewNetworkConfig(nil), }, }) }) t.Run("invalid config", func(t *testing.T) { var ( ctx = context.Background() logger = toolkit.NewTestLogger(t) networkLoader = network.NewMockLoader(t) creationParamsA = bootstrap.NewCreationParams("AAA", "a.com") creationParamsB = bootstrap.NewCreationParams("BBB", "b.com") networkConfigA = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.VPN.PublicAddr = "1.1.1.1:5" }) networkConfigB = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.VPN.PublicAddr = "2.2.2.2:5" }) config = daecommon.Config{ Networks: map[string]daecommon.NetworkConfig{ creationParamsA.ID: networkConfigA, }, } ) networkLoader. On("Loadable", toolkit.MockArg[context.Context]()). Return( []bootstrap.CreationParams{creationParamsA, creationParamsB}, nil, ). Once() networkLoader. On( "StoredConfig", toolkit.MockArg[context.Context](), creationParamsB.ID, ). Return(networkConfigB, nil). Once() _, err := New(ctx, logger, networkLoader, config) assert.ErrorContains(t, err, "two networks with the same vpn.public_addr port") }) } func TestDaemon_CreateNetwork(t *testing.T) { t.Run("ErrManagedNetworkConfig", func(t *testing.T) { var ( networkConfig = daecommon.NewNetworkConfig(nil) h = newHarness(t, &harnessOpts{ config: daecommon.Config{ Networks: map[string]daecommon.NetworkConfig{ "AAA": networkConfig, }, }, }) creationParams = bootstrap.NewCreationParams("AAA", "a.com") ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24") ) err := h.daemon.CreateNetwork( h.ctx, creationParams, ipNet, nebula.HostName("foo"), &CreateNetworkOpts{Config: &networkConfig}, ) assert.ErrorIs(t, err, ErrManagedNetworkConfig) }) t.Run("ErrAlreadyJoined", func(t *testing.T) { var ( creationParams = bootstrap.NewCreationParams("AAA", "a.com") networkConfig = daecommon.NewNetworkConfig(nil) h = newHarness(t, &harnessOpts{ expectNetworksLoaded: []expectNetworkLoad{{ creationParams, nil, network.NewMockNetwork(t), }}, expectStoredConfigs: map[string]daecommon.NetworkConfig{ creationParams.ID: networkConfig, }, }) ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24") ) err := h.daemon.CreateNetwork( h.ctx, bootstrap.NewCreationParams("AAA", "aaa.com"), ipNet, nebula.HostName("foo"), nil, ) assert.ErrorIs(t, err, ErrAlreadyJoined) }) t.Run("success/config given", func(t *testing.T) { networkA := network.NewMockNetwork(t) networkA.On("Shutdown").Return(nil).Once() var ( h = newHarness(t, nil) creationParams = bootstrap.NewCreationParams("AAA", "a.com") networkConfig = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.VPN.PublicAddr = "1.2.3.4:50" }) ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24") hostName = nebula.HostName("foo") ) h.networkLoader. On( "Create", toolkit.MockArg[context.Context](), toolkit.MockArg[*mlog.Logger](), creationParams, ipNet, hostName, &network.Opts{ Config: &networkConfig, }, ). Return(networkA, nil). Once() err := h.daemon.CreateNetwork( h.ctx, creationParams, ipNet, hostName, &CreateNetworkOpts{ Config: &networkConfig, }, ) assert.NoError(t, err) assert.Contains(t, h.daemon.networks, creationParams.ID) }) t.Run("success/no config given", func(t *testing.T) { networkA := network.NewMockNetwork(t) networkA.On("Shutdown").Return(nil).Once() var ( h = newHarness(t, nil) creationParams = bootstrap.NewCreationParams("AAA", "a.com") ipNet = nebula.MustParseIPNet(t, "172.16.0.0/24") hostName = nebula.HostName("foo") ) h.networkLoader. On( "Create", toolkit.MockArg[context.Context](), toolkit.MockArg[*mlog.Logger](), creationParams, ipNet, hostName, &network.Opts{}, ). Return(networkA, nil). Once() err := h.daemon.CreateNetwork( h.ctx, creationParams, ipNet, hostName, nil, ) assert.NoError(t, err) assert.Contains(t, h.daemon.networks, creationParams.ID) }) } 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) { 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), }, }) networkConfig = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.VPN.PublicAddr = "1.2.3.4:5" }) ) networkA. On("SetConfig", toolkit.MockArg[context.Context](), networkConfig). Return(nil). Once() err := h.daemon.SetConfig(h.ctx, networkConfig) assert.NoError(t, err) }) t.Run("ErrManagedNetworkConfig", func(t *testing.T) { var ( creationParams = bootstrap.NewCreationParams("AAA", "a.com") networkA = network.NewMockNetwork(t) networkConfig = daecommon.NewNetworkConfig(nil) h = newHarness(t, &harnessOpts{ config: daecommon.Config{ Networks: map[string]daecommon.NetworkConfig{ creationParams.Name: networkConfig, }, }, expectNetworksLoaded: []expectNetworkLoad{ {creationParams, &networkConfig, networkA}, }, }) ) networkConfig.VPN.PublicAddr = "1.2.3.4:5" err := h.daemon.SetConfig(h.ctx, networkConfig) assert.ErrorIs(t, err, ErrManagedNetworkConfig) }) t.Run("ErrInvalidConfig", func(t *testing.T) { var ( creationParamsA = bootstrap.NewCreationParams("AAA", "a.com") networkA = network.NewMockNetwork(t) networkConfigA = daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { c.VPN.PublicAddr = "1.2.3.4:5" }) creationParamsB = bootstrap.NewCreationParams("BBB", "b.com") networkB = network.NewMockNetwork(t) networkConfigB = daecommon.NewNetworkConfig(nil) h = newHarness(t, &harnessOpts{ expectNetworksLoaded: []expectNetworkLoad{ {creationParamsA, nil, networkA}, {creationParamsB, nil, networkB}, }, expectStoredConfigs: map[string]daecommon.NetworkConfig{ creationParamsA.ID: networkConfigA, creationParamsB.ID: networkConfigB, }, }) ) networkA. On("GetConfig", toolkit.MockArg[context.Context]()). Return(networkConfigA, nil). Once() networkConfigB.VPN.PublicAddr = "1.1.1.1:5" err := h.daemon.SetConfig(WithNetwork(h.ctx, "BBB"), networkConfigB) assert.ErrorIs(t, err, network.ErrInvalidConfig) }) }