diff --git a/go/daemon/config.go b/go/daemon/config.go index 62c3641..e26044e 100644 --- a/go/daemon/config.go +++ b/go/daemon/config.go @@ -44,22 +44,20 @@ var HTTPSocketPath = sync.OnceValue(func() string { func pickNetworkConfig( daemonConfig daecommon.Config, creationParams bootstrap.CreationParams, -) ( - daecommon.NetworkConfig, bool, -) { +) *daecommon.NetworkConfig { if len(daemonConfig.Networks) == 1 { // DEPRECATED if c, ok := daemonConfig.Networks[daecommon.DeprecatedNetworkID]; ok { - return c, true + return &c } } for searchStr, networkConfig := range daemonConfig.Networks { if creationParams.Matches(searchStr) { - return networkConfig, true + return &networkConfig } } - return daecommon.NetworkConfig{}, false + return nil } //////////////////////////////////////////////////////////////////////////////// diff --git a/go/daemon/daemon.go b/go/daemon/daemon.go index c8ee78e..481ca9a 100644 --- a/go/daemon/daemon.go +++ b/go/daemon/daemon.go @@ -19,6 +19,11 @@ import ( var _ RPC = (*Daemon)(nil) +type joinedNetwork struct { + network.Network + config *daecommon.NetworkConfig +} + // Daemon implements all methods of the Daemon interface, plus others used // to manage this particular implementation. // @@ -41,7 +46,7 @@ type Daemon struct { daemonConfig daecommon.Config l sync.RWMutex - networks map[string]network.Network + networks map[string]joinedNetwork } // New initializes and returns a Daemon. @@ -57,7 +62,7 @@ func New( logger: logger, networkLoader: networkLoader, daemonConfig: daemonConfig, - networks: map[string]network.Network{}, + networks: map[string]joinedNetwork{}, } loadableNetworks, err := networkLoader.Loadable(ctx) @@ -69,20 +74,23 @@ func New( ctx = mctx.WithAnnotator(ctx, creationParams) var ( - id = creationParams.ID - networkConfig, _ = pickNetworkConfig(daemonConfig, creationParams) + id = creationParams.ID + networkConfig = pickNetworkConfig(daemonConfig, creationParams) ) - d.networks[id], err = networkLoader.Load( + n, err := networkLoader.Load( ctx, logger.WithNamespace("network"), - networkConfig, creationParams, - nil, + &network.Opts{ + Config: networkConfig, + }, ) if err != nil { return nil, fmt.Errorf("loading network %q: %w", id, err) } + + d.networks[id] = joinedNetwork{n, networkConfig} } return d, nil @@ -105,17 +113,16 @@ func (d *Daemon) CreateNetwork( ctx context.Context, name, domain string, ipNet nebula.IPNet, hostName nebula.HostName, ) error { - creationParams := bootstrap.NewCreationParams(name, domain) - ctx = mctx.WithAnnotator(ctx, creationParams) - - networkConfig, ok := pickNetworkConfig(d.daemonConfig, creationParams) - if !ok { - return errors.New("couldn't find network config for network being created") - } - d.l.Lock() defer d.l.Unlock() + var ( + creationParams = bootstrap.NewCreationParams(name, domain) + networkConfig = pickNetworkConfig(d.daemonConfig, creationParams) + ) + + ctx = mctx.WithAnnotator(ctx, creationParams) + if joined, err := alreadyJoined(ctx, d.networks, creationParams); err != nil { return fmt.Errorf("checking if already joined to network: %w", err) } else if joined { @@ -126,18 +133,19 @@ func (d *Daemon) CreateNetwork( n, err := d.networkLoader.Create( ctx, d.logger.WithNamespace("network"), - networkConfig, creationParams, ipNet, hostName, - nil, + &network.Opts{ + Config: networkConfig, + }, ) if err != nil { return fmt.Errorf("creating network: %w", err) } d.logger.Info(ctx, "Network created successfully") - d.networks[creationParams.ID] = n + d.networks[creationParams.ID] = joinedNetwork{n, networkConfig} return nil } @@ -149,17 +157,17 @@ func (d *Daemon) CreateNetwork( func (d *Daemon) JoinNetwork( ctx context.Context, newBootstrap network.JoiningBootstrap, ) error { + d.l.Lock() + defer d.l.Unlock() + var ( - creationParams = newBootstrap.Bootstrap.NetworkCreationParams - networkConfig, _ = pickNetworkConfig(d.daemonConfig, creationParams) - networkID = creationParams.ID + creationParams = newBootstrap.Bootstrap.NetworkCreationParams + networkID = creationParams.ID + networkConfig = pickNetworkConfig(d.daemonConfig, creationParams) ) ctx = mctx.WithAnnotator(ctx, newBootstrap.Bootstrap.NetworkCreationParams) - d.l.Lock() - defer d.l.Unlock() - if joined, err := alreadyJoined(ctx, d.networks, creationParams); err != nil { return fmt.Errorf("checking if already joined to network: %w", err) } else if joined { @@ -170,9 +178,10 @@ func (d *Daemon) JoinNetwork( n, err := d.networkLoader.Join( ctx, d.logger.WithNamespace("network"), - networkConfig, newBootstrap, - nil, + &network.Opts{ + Config: networkConfig, + }, ) if err != nil { return fmt.Errorf( @@ -181,14 +190,14 @@ func (d *Daemon) JoinNetwork( } d.logger.Info(ctx, "Network joined successfully") - d.networks[networkID] = n + d.networks[networkID] = joinedNetwork{n, networkConfig} return nil } func withNetwork[Res any]( ctx context.Context, d *Daemon, - fn func(context.Context, network.Network) (Res, error), + fn func(context.Context, joinedNetwork) (Res, error), ) ( Res, error, ) { @@ -238,7 +247,7 @@ func (d *Daemon) GetHosts(ctx context.Context) ([]bootstrap.Host, error) { return withNetwork( ctx, d, - func(ctx context.Context, n network.Network) ([]bootstrap.Host, error) { + func(ctx context.Context, n joinedNetwork) ([]bootstrap.Host, error) { return n.GetHosts(ctx) }, ) @@ -254,7 +263,7 @@ func (d *Daemon) GetGarageClientParams( ctx, d, func( - ctx context.Context, n network.Network, + ctx context.Context, n joinedNetwork, ) ( network.GarageClientParams, error, ) { @@ -274,7 +283,7 @@ func (d *Daemon) GetNebulaCAPublicCredentials( ctx, d, func( - ctx context.Context, n network.Network, + ctx context.Context, n joinedNetwork, ) ( nebula.CAPublicCredentials, error, ) { @@ -289,7 +298,7 @@ func (d *Daemon) RemoveHost(ctx context.Context, hostName nebula.HostName) error ctx, d, func( - ctx context.Context, n network.Network, + ctx context.Context, n joinedNetwork, ) ( struct{}, error, ) { @@ -311,7 +320,7 @@ func (d *Daemon) CreateHost( ctx, d, func( - ctx context.Context, n network.Network, + ctx context.Context, n joinedNetwork, ) ( network.JoiningBootstrap, error, ) { @@ -332,7 +341,7 @@ func (d *Daemon) CreateNebulaCertificate( ctx, d, func( - ctx context.Context, n network.Network, + ctx context.Context, n joinedNetwork, ) ( nebula.Certificate, error, ) { @@ -341,6 +350,7 @@ func (d *Daemon) CreateNebulaCertificate( ) } +// GetConfig implements the method for the network.RPC interface. func (d *Daemon) GetConfig( ctx context.Context, ) ( @@ -350,7 +360,7 @@ func (d *Daemon) GetConfig( ctx, d, func( - ctx context.Context, n network.Network, + ctx context.Context, n joinedNetwork, ) ( daecommon.NetworkConfig, error, ) { @@ -359,13 +369,18 @@ func (d *Daemon) GetConfig( ) } +// SetConfig implements the method for the network.RPC interface. func (d *Daemon) SetConfig( ctx context.Context, config daecommon.NetworkConfig, ) error { _, err := withNetwork( ctx, d, - func(ctx context.Context, n network.Network) (struct{}, error) { + func(ctx context.Context, n joinedNetwork) (struct{}, error) { + if n.config != nil { + return struct{}{}, ErrUserManagedNetworkConfig + } + // TODO needs to check that public addresses aren't being shared // across networks, and whatever else happens in Config.Validate. return struct{}{}, n.SetConfig(ctx, config) diff --git a/go/daemon/errors.go b/go/daemon/errors.go index 1ddd242..1e4b2ce 100644 --- a/go/daemon/errors.go +++ b/go/daemon/errors.go @@ -10,6 +10,7 @@ const ( errCodeAlreadyJoined errCodeNoMatchingNetworks errCodeMultipleMatchingNetworks + errCodeUserManagedNetworkConfig ) var ( @@ -33,4 +34,11 @@ var ( errCodeMultipleMatchingNetworks, "Multiple networks matched the search string", ) + + // ErrUserManagedNetworkConfig is returned when attempting to modify a + // network config which is managed by the user. + ErrUserManagedNetworkConfig = jsonrpc2.NewError( + errCodeUserManagedNetworkConfig, + "Network configuration is managed by the user", + ) ) diff --git a/go/daemon/network.go b/go/daemon/network.go index 3eb7b79..138fde4 100644 --- a/go/daemon/network.go +++ b/go/daemon/network.go @@ -10,17 +10,17 @@ import ( func pickNetwork( ctx context.Context, networkLoader network.Loader, - networks map[string]network.Network, + networks map[string]joinedNetwork, ) ( - network.Network, error, + joinedNetwork, error, ) { if len(networks) == 0 { - return nil, ErrNoNetwork + return joinedNetwork{}, ErrNoNetwork } creationParams, err := networkLoader.Loadable(ctx) if err != nil { - return nil, fmt.Errorf("getting loadable networks: %w", err) + return joinedNetwork{}, fmt.Errorf("getting loadable networks: %w", err) } var ( @@ -35,9 +35,9 @@ func pickNetwork( } if len(matchingNetworkIDs) == 0 { - return nil, ErrNoMatchingNetworks + return joinedNetwork{}, ErrNoMatchingNetworks } else if len(matchingNetworkIDs) > 1 { - return nil, ErrMultipleMatchingNetworks + return joinedNetwork{}, ErrMultipleMatchingNetworks } return networks[matchingNetworkIDs[0]], nil @@ -45,7 +45,7 @@ func pickNetwork( func alreadyJoined( ctx context.Context, - networks map[string]network.Network, + networks map[string]joinedNetwork, creationParams bootstrap.CreationParams, ) ( bool, error, diff --git a/go/daemon/network/config.go b/go/daemon/network/config.go new file mode 100644 index 0000000..2eee09e --- /dev/null +++ b/go/daemon/network/config.go @@ -0,0 +1,42 @@ +package network + +import ( + "errors" + "fmt" + "io/fs" + "isle/daemon/daecommon" + "isle/jsonutil" + "isle/toolkit" + "path/filepath" +) + +// loadStoreConfig writes the given NetworkConfig to the networkStateDir if the +// config is non-nil. If the config is nil then a config is read from +// networkStateDir, returning the zero value if no config was previously +// stored. +func loadStoreConfig( + networkStateDir toolkit.Dir, config *daecommon.NetworkConfig, +) ( + daecommon.NetworkConfig, error, +) { + path := filepath.Join(networkStateDir.Path, "config.json") + + if config == nil { + config = new(daecommon.NetworkConfig) + err := jsonutil.LoadFile(&config, path) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return daecommon.NetworkConfig{}, fmt.Errorf( + "loading %q: %w", path, err, + ) + } + return *config, nil + } + + if err := jsonutil.WriteFile(*config, path, 0600); err != nil { + return daecommon.NetworkConfig{}, fmt.Errorf( + "writing to %q: %w", path, err, + ) + } + + return *config, nil +} diff --git a/go/daemon/network/loader.go b/go/daemon/network/loader.go index d6a280e..9bf7d0e 100644 --- a/go/daemon/network/loader.go +++ b/go/daemon/network/loader.go @@ -59,7 +59,6 @@ type Loader interface { Load( context.Context, *mlog.Logger, - daecommon.NetworkConfig, bootstrap.CreationParams, *Opts, ) ( @@ -73,7 +72,6 @@ type Loader interface { Join( context.Context, *mlog.Logger, - daecommon.NetworkConfig, JoiningBootstrap, *Opts, ) ( @@ -91,12 +89,11 @@ type Loader interface { // - hostName: The name of this first host in the network. // // Errors: - // - ErrInvalidConfig - if daemonConfig doesn't have 3 storage allocations - // configured. + // - ErrInvalidConfig - If the Opts.Config field is not valid. It must be + // non-nil and have at least 3 storage allocations. Create( context.Context, *mlog.Logger, - daecommon.NetworkConfig, bootstrap.CreationParams, nebula.IPNet, nebula.HostName, @@ -188,7 +185,7 @@ func (l *loader) Loadable( creationParams := make([]bootstrap.CreationParams, 0, len(networkStateDirs)) for _, networkStateDir := range networkStateDirs { - thisCreationParams, err := LoadCreationParams(networkStateDir) + thisCreationParams, err := loadCreationParams(networkStateDir) if err != nil { return nil, fmt.Errorf( "loading creation params from %q: %w", @@ -205,7 +202,6 @@ func (l *loader) Loadable( func (l *loader) Load( ctx context.Context, logger *mlog.Logger, - networkConfig daecommon.NetworkConfig, creationParams bootstrap.CreationParams, opts *Opts, ) ( @@ -226,7 +222,6 @@ func (l *loader) Load( ctx, logger.WithNamespace("network"), l.envBinDirPath, - networkConfig, networkStateDir, networkRuntimeDir, opts, @@ -236,7 +231,6 @@ func (l *loader) Load( func (l *loader) Join( ctx context.Context, logger *mlog.Logger, - networkConfig daecommon.NetworkConfig, joiningBootstrap JoiningBootstrap, opts *Opts, ) ( @@ -260,7 +254,6 @@ func (l *loader) Join( ctx, logger.WithNamespace("network"), l.envBinDirPath, - networkConfig, joiningBootstrap, networkStateDir, networkRuntimeDir, @@ -271,7 +264,6 @@ func (l *loader) Join( func (l *loader) Create( ctx context.Context, logger *mlog.Logger, - networkConfig daecommon.NetworkConfig, creationParams bootstrap.CreationParams, ipNet nebula.IPNet, hostName nebula.HostName, @@ -294,7 +286,6 @@ func (l *loader) Create( ctx, logger.WithNamespace("network"), l.envBinDirPath, - networkConfig, networkStateDir, networkRuntimeDir, creationParams, diff --git a/go/daemon/network/network.go b/go/daemon/network/network.go index 1122e1f..9b2e3f0 100644 --- a/go/daemon/network/network.go +++ b/go/daemon/network/network.go @@ -154,6 +154,14 @@ type Network interface { // Network instance. A nil Opts is equivalent to a zero value. type Opts struct { GarageAdminToken string // Will be randomly generated if left unset. + + // Config will be used as the configuration of the Network from its + // initialization onwards. + // + // If not given then the most recent NetworkConfig for the network will be + // used, either that which it was most recently initialized with or which + // was passed to [SetConfig]. + Config *daecommon.NetworkConfig } func (o *Opts) withDefaults() *Opts { @@ -189,36 +197,53 @@ type network struct { wg sync.WaitGroup } -// instatiateNetwork returns an instantiated *network instance which has not yet -// been initialized. -func instatiateNetwork( +// newNetwork returns an instantiated *network instance. All initialization +// steps which are common to all *network creation methods (load, join, create) +// are included here as well. +func newNetwork( ctx context.Context, logger *mlog.Logger, - networkConfig daecommon.NetworkConfig, envBinDirPath string, stateDir toolkit.Dir, runtimeDir toolkit.Dir, + dirsMayExist bool, opts *Opts, -) *network { - ctx = context.WithoutCancel(ctx) - ctx, cancel := context.WithCancel(ctx) - return &network{ - logger: logger, - networkConfig: networkConfig, - envBinDirPath: envBinDirPath, - stateDir: stateDir, - runtimeDir: runtimeDir, - opts: opts.withDefaults(), - workerCtx: ctx, - workerCancel: cancel, +) ( + *network, error, +) { + ctx, cancel := context.WithCancel(context.WithoutCancel(ctx)) + + var ( + n = &network{ + logger: logger, + envBinDirPath: envBinDirPath, + stateDir: stateDir, + runtimeDir: runtimeDir, + opts: opts.withDefaults(), + workerCtx: ctx, + workerCancel: cancel, + } + err error + ) + + n.networkConfig, err = loadStoreConfig(n.stateDir, n.opts.Config) + if err != nil { + return nil, fmt.Errorf("resolving network config: %w", err) } + + secretsDir, err := n.stateDir.MkChildDir("secrets", dirsMayExist) + if err != nil { + return nil, fmt.Errorf("creating secrets dir: %w", err) + } + + n.secretsStore = secrets.NewFSStore(secretsDir.Path) + + return n, nil } -// LoadCreationParams returns the CreationParams of a Network which was +// loadCreationParams returns the CreationParams of a Network which was // Created/Joined with the given state directory. -// -// TODO probably can be private -func LoadCreationParams( +func loadCreationParams( stateDir toolkit.Dir, ) ( bootstrap.CreationParams, error, @@ -244,25 +269,23 @@ func load( ctx context.Context, logger *mlog.Logger, envBinDirPath string, - networkConfig daecommon.NetworkConfig, stateDir toolkit.Dir, runtimeDir toolkit.Dir, opts *Opts, ) ( Network, error, ) { - n := instatiateNetwork( + n, err := newNetwork( ctx, logger, - networkConfig, envBinDirPath, stateDir, runtimeDir, + true, opts, ) - - if err := n.initializeDirs(true); err != nil { - return nil, fmt.Errorf("initializing directories: %w", err) + if err != nil { + return nil, fmt.Errorf("instantiating Network: %w", err) } var ( @@ -285,7 +308,6 @@ func join( ctx context.Context, logger *mlog.Logger, envBinDirPath string, - networkConfig daecommon.NetworkConfig, joiningBootstrap JoiningBootstrap, stateDir toolkit.Dir, runtimeDir toolkit.Dir, @@ -293,18 +315,17 @@ func join( ) ( Network, error, ) { - n := instatiateNetwork( + n, err := newNetwork( ctx, logger, - networkConfig, envBinDirPath, stateDir, runtimeDir, + false, opts, ) - - if err := n.initializeDirs(false); err != nil { - return nil, fmt.Errorf("initializing directories: %w", err) + if err != nil { + return nil, fmt.Errorf("instantiating Network: %w", err) } if err := secrets.Import( @@ -324,7 +345,6 @@ func create( ctx context.Context, logger *mlog.Logger, envBinDirPath string, - networkConfig daecommon.NetworkConfig, stateDir toolkit.Dir, runtimeDir toolkit.Dir, creationParams bootstrap.CreationParams, @@ -334,12 +354,6 @@ func create( ) ( Network, error, ) { - if len(networkConfig.Storage.Allocations) < 3 { - return nil, ErrInvalidConfig.WithData( - "At least three storage allocations are required.", - ) - } - nebulaCACreds, err := nebula.NewCACredentials(creationParams.Domain, ipNet) if err != nil { return nil, fmt.Errorf("creating nebula CA cert: %w", err) @@ -347,18 +361,23 @@ func create( garageRPCSecret := toolkit.RandStr(32) - n := instatiateNetwork( + n, err := newNetwork( ctx, logger, - networkConfig, envBinDirPath, stateDir, runtimeDir, + false, opts, ) + if err != nil { + return nil, fmt.Errorf("instantiating Network: %w", err) + } - if err := n.initializeDirs(false); err != nil { - return nil, fmt.Errorf("initializing directories: %w", err) + if len(n.networkConfig.Storage.Allocations) < 3 { + return nil, ErrInvalidConfig.WithData( + "At least three storage allocations are required.", + ) } err = daecommon.SetGarageRPCSecret(ctx, n.secretsStore, garageRPCSecret) @@ -391,16 +410,6 @@ func create( return n, nil } -func (n *network) initializeDirs(mayExist bool) error { - secretsDir, err := n.stateDir.MkChildDir("secrets", mayExist) - if err != nil { - return fmt.Errorf("creating secrets dir: %w", err) - } - - n.secretsStore = secrets.NewFSStore(secretsDir.Path) - return nil -} - func (n *network) periodically( label string, fn func(context.Context) error, @@ -1016,6 +1025,10 @@ func (n *network) GetConfig(context.Context) (daecommon.NetworkConfig, error) { func (n *network) SetConfig( ctx context.Context, config daecommon.NetworkConfig, ) error { + if _, err := loadStoreConfig(n.stateDir, &config); err != nil { + return fmt.Errorf("storing new config: %w", err) + } + prevBootstrap, err := n.reload(ctx, &config, nil) if err != nil { return fmt.Errorf("reloading config: %w", err) diff --git a/go/daemon/network/network_it_test.go b/go/daemon/network/network_it_test.go index 248c304..dd25605 100644 --- a/go/daemon/network/network_it_test.go +++ b/go/daemon/network/network_it_test.go @@ -17,7 +17,7 @@ func TestCreate(t *testing.T) { network = h.createNetwork(t, "primus", nil) ) - gotCreationParams, err := LoadCreationParams(network.stateDir) + gotCreationParams, err := loadCreationParams(network.stateDir) assert.NoError(t, err) assert.Equal( t, gotCreationParams, network.getBootstrap(t).NetworkCreationParams, @@ -25,31 +25,30 @@ func TestCreate(t *testing.T) { } func TestLoad(t *testing.T) { - var ( - h = newIntegrationHarness(t) - network = h.createNetwork(t, "primus", &createNetworkOpts{ - manualShutdown: true, - }) - ) + t.Run("given config", func(t *testing.T) { + var ( + h = newIntegrationHarness(t) + network = h.createNetwork(t, "primus", nil) + networkConfig = network.getConfig(t) + ) - t.Log("Shutting down network") - assert.NoError(t, network.Shutdown()) + network.opts.Config = &networkConfig + network.restart(t) - t.Log("Calling Load") - loadedNetwork, err := load( - h.ctx, - h.logger.WithNamespace("loadedNetwork"), - getEnvBinDirPath(), - network.getConfig(t), - network.stateDir, - h.mkDir(t, "runtime"), - network.opts, - ) - assert.NoError(t, err) + assert.Equal(t, networkConfig, network.getConfig(t)) + }) - t.Cleanup(func() { - t.Log("Shutting down loadedNetwork") - assert.NoError(t, loadedNetwork.Shutdown()) + t.Run("load previous config", func(t *testing.T) { + var ( + h = newIntegrationHarness(t) + network = h.createNetwork(t, "primus", nil) + networkConfig = network.getConfig(t) + ) + + network.opts.Config = nil + network.restart(t) + + assert.Equal(t, networkConfig, network.getConfig(t)) }) } @@ -61,13 +60,7 @@ func TestJoin(t *testing.T) { secondus = h.joinNetwork(t, primus, "secondus", nil) ) - primusHosts, err := primus.GetHosts(h.ctx) - assert.NoError(t, err) - - secondusHosts, err := secondus.GetHosts(h.ctx) - assert.NoError(t, err) - - assert.Equal(t, primusHosts, secondusHosts) + assert.Equal(t, primus.getHostsByName(t), secondus.getHostsByName(t)) }) t.Run("with alloc", func(t *testing.T) { @@ -84,28 +77,10 @@ func TestJoin(t *testing.T) { t.Log("reloading primus' hosts") assert.NoError(t, primus.Network.(*network).reloadHosts(h.ctx)) - primusHosts, err := primus.GetHosts(h.ctx) - assert.NoError(t, err) - - secondusHosts, err := secondus.GetHosts(h.ctx) - assert.NoError(t, err) - - assert.Equal(t, primusHosts, secondusHosts) + assert.Equal(t, primus.getHostsByName(t), secondus.getHostsByName(t)) }) } -func TestNetwork_GetConfig(t *testing.T) { - var ( - h = newIntegrationHarness(t) - network = h.createNetwork(t, "primus", nil) - ) - - config, err := network.GetConfig(h.ctx) - assert.NoError(t, err) - - assert.Equal(t, config, network.getConfig(t)) -} - func TestNetwork_SetConfig(t *testing.T) { allocsToRoles := func( hostName nebula.HostName, allocs []bootstrap.GarageHostInstance, @@ -259,4 +234,22 @@ func TestNetwork_SetConfig(t *testing.T) { assert.NoError(t, err) assert.NotContains(t, layout.Roles, removedRole) }) + + t.Run("changes reflected after restart", func(t *testing.T) { + var ( + h = newIntegrationHarness(t) + network = h.createNetwork(t, "primus", &createNetworkOpts{ + numStorageAllocs: 4, + }) + networkConfig = network.getConfig(t) + ) + + networkConfig.Storage.Allocations = networkConfig.Storage.Allocations[:3] + assert.NoError(t, network.SetConfig(h.ctx, networkConfig)) + + network.opts.Config = nil + network.restart(t) + + assert.Equal(t, networkConfig, network.getConfig(t)) + }) } diff --git a/go/daemon/network/network_it_util_test.go b/go/daemon/network/network_it_util_test.go index 7e30624..722665b 100644 --- a/go/daemon/network/network_it_util_test.go +++ b/go/daemon/network/network_it_util_test.go @@ -203,7 +203,7 @@ func (h *integrationHarness) createNetwork( t *testing.T, hostNameStr string, opts *createNetworkOpts, -) integrationHarnessNetwork { +) *integrationHarnessNetwork { t.Logf("Creating as %q", hostNameStr) opts = opts.withDefaults() @@ -222,6 +222,7 @@ func (h *integrationHarness) createNetwork( networkOpts = &Opts{ GarageAdminToken: "admin_token", + Config: &networkConfig, } ) @@ -229,7 +230,6 @@ func (h *integrationHarness) createNetwork( h.ctx, logger, getEnvBinDirPath(), - networkConfig, stateDir, runtimeDir, opts.creationParams, @@ -241,16 +241,7 @@ func (h *integrationHarness) createNetwork( t.Fatalf("creating Network: %v", err) } - if !opts.manualShutdown { - t.Cleanup(func() { - t.Logf("Shutting down Network %q", hostNameStr) - if err := network.Shutdown(); err != nil { - t.Logf("Shutting down Network %q failed: %v", hostNameStr, err) - } - }) - } - - return integrationHarnessNetwork{ + nh := &integrationHarnessNetwork{ network, h.ctx, logger, @@ -259,6 +250,17 @@ func (h *integrationHarness) createNetwork( runtimeDir, networkOpts, } + + if !opts.manualShutdown { + t.Cleanup(func() { + t.Logf("Shutting down Network %q", hostNameStr) + if err := nh.Shutdown(); err != nil { + t.Logf("Shutting down Network %q failed: %v", hostNameStr, err) + } + }) + } + + return nh } type joinNetworkOpts struct { @@ -279,10 +281,10 @@ func (o *joinNetworkOpts) withDefaults() *joinNetworkOpts { func (h *integrationHarness) joinNetwork( t *testing.T, - network integrationHarnessNetwork, + network *integrationHarnessNetwork, hostNameStr string, opts *joinNetworkOpts, -) integrationHarnessNetwork { +) *integrationHarnessNetwork { opts = opts.withDefaults() hostName := nebula.HostName(hostNameStr) @@ -301,6 +303,7 @@ func (h *integrationHarness) joinNetwork( runtimeDir = h.mkDir(t, "runtime") networkOpts = &Opts{ GarageAdminToken: "admin_token", + Config: &networkConfig, } ) @@ -309,7 +312,6 @@ func (h *integrationHarness) joinNetwork( h.ctx, logger, getEnvBinDirPath(), - networkConfig, joiningBootstrap, stateDir, runtimeDir, @@ -319,16 +321,7 @@ func (h *integrationHarness) joinNetwork( t.Fatalf("joining network: %v", err) } - if !opts.manualShutdown { - t.Cleanup(func() { - t.Logf("Shutting down Network %q", hostNameStr) - if err := joinedNetwork.Shutdown(); err != nil { - t.Logf("Shutting down Network %q failed: %v", hostNameStr, err) - } - }) - } - - return integrationHarnessNetwork{ + nh := &integrationHarnessNetwork{ joinedNetwork, h.ctx, logger, @@ -337,6 +330,34 @@ func (h *integrationHarness) joinNetwork( runtimeDir, networkOpts, } + + if !opts.manualShutdown { + t.Cleanup(func() { + t.Logf("Shutting down Network %q", hostNameStr) + if err := nh.Shutdown(); err != nil { + t.Logf("Shutting down Network %q failed: %v", hostNameStr, err) + } + }) + } + + return nh +} + +func (nh *integrationHarnessNetwork) restart(t *testing.T) { + t.Log("Shutting down network (restart)") + require.NoError(t, nh.Network.Shutdown()) + + t.Log("Loading network (restart)") + var err error + nh.Network, err = load( + nh.ctx, + nh.logger, + getEnvBinDirPath(), + nh.stateDir, + nh.runtimeDir, + nh.opts, + ) + require.NoError(t, err) } func (nh *integrationHarnessNetwork) getConfig(t *testing.T) daecommon.NetworkConfig { diff --git a/go/daemon/rpc.go b/go/daemon/rpc.go index 28f2863..698cc62 100644 --- a/go/daemon/rpc.go +++ b/go/daemon/rpc.go @@ -3,6 +3,7 @@ package daemon import ( "context" "isle/bootstrap" + "isle/daemon/daecommon" "isle/daemon/jsonrpc2" "isle/daemon/network" "isle/nebula" @@ -25,13 +26,23 @@ type RPC interface { GetNetworks(context.Context) ([]bootstrap.CreationParams, error) + // SetConfig extends the [network.RPC] method of the same name such that + // [ErrUserManagedNetworkConfig] 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 + // All network.RPC methods are automatically implemented by Daemon using the // currently joined network. If no network is joined then any call to these // methods will return ErrNoNetwork. // - // All calls to these methods must be accompanied with a context produced by - // WithNetwork, in order to choose the network. These methods may return - // these errors, in addition to those documented on the individual methods: + // If more than one Network is joined then all calls to these methods must + // be accompanied with a context produced by WithNetwork, in order to choose + // the network. These methods may return these errors, in addition to those + // documented on the individual methods: // // Errors: // - ErrNoNetwork