From 3111d2ca7449e7a154089e2c34ebd3b6e97f6257 Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Tue, 17 Dec 2024 11:33:19 +0100 Subject: [PATCH] Allow creating a network without configuring it in daemon.yml --- docs/admin/creating-a-new-network.md | 2 +- docs/dev/releases.md | 14 +- docs/dev/testing.md | 5 + go/cmd/entrypoint/flags.go | 33 ++++- go/cmd/entrypoint/main_test.go | 19 ++- go/cmd/entrypoint/network.go | 72 ++++++++++- go/cmd/entrypoint/network_test.go | 119 +++++++++++++++++ go/cmd/entrypoint/sub_cmd.go | 8 ++ go/cmd/entrypoint/vpn_public_addr.go | 14 +- go/daemon/client.go | 6 +- go/daemon/daemon.go | 42 +++--- go/daemon/daemon_test.go | 129 +++++++++++++++++++ go/daemon/network.go | 21 +-- go/daemon/rpc.go | 19 ++- go/daemon/rpc_mock.go | 10 +- go/nebula/ip_net.go | 10 ++ go/toolkit/toolkit.go | 5 + tasks/soon/code/storage-allocation-modify.md | 12 ++ tasks/v0.0.3/update-documentation.md | 12 +- 19 files changed, 488 insertions(+), 64 deletions(-) create mode 100644 tasks/soon/code/storage-allocation-modify.md diff --git a/docs/admin/creating-a-new-network.md b/docs/admin/creating-a-new-network.md index 7081500..beba061 100644 --- a/docs/admin/creating-a-new-network.md +++ b/docs/admin/creating-a-new-network.md @@ -30,7 +30,7 @@ The requirements for this host are: * At least 3 directories should be chosen, each of which will be committing at least 1GB. Ideally these directories should be on different physical disks, - but if that's not possible it's ok. See the Next Steps section. + but if that's not possible it's ok. * None of the resources being used for this network (the UDP port or storage locations) should be being used by other networks. diff --git a/docs/dev/releases.md b/docs/dev/releases.md index 926258f..832f62e 100644 --- a/docs/dev/releases.md +++ b/docs/dev/releases.md @@ -25,8 +25,12 @@ create the signature. ## Releasing -Releases are uploaded to the repository's Releases page, and release naming -follows the conventional semantic versioning system. Each release should be -accompanied by a set of changes which have occurred since the last release, -described both in the `CHANGELOG.md` file and in the description on the Release -itself. +Release artifactes are hosted at `micropelago.net` under +`/isle/releases/`. An `index.gmi` page should be created in that +directory which includes links to each artifact, as well as a changelog +detailing all new features and fixes included since the previous release. + +A link to the new release should be included at `/isle/releases/index.gmi`. + +The release shoulld be tagged in the git repo using its release name as well, +with the tag notes linking to the `micropelago.net` page. diff --git a/docs/dev/testing.md b/docs/dev/testing.md index e0763be..32cf168 100644 --- a/docs/dev/testing.md +++ b/docs/dev/testing.md @@ -11,6 +11,11 @@ go test ./... # Test everything ## Integration Tests +NOTE: before running integration tests you will want to make sure you can [build +Isle][building] in the first place. + +[building]: ./building.md + Integration tests are those which require processes or state external to the test itself. Integration tests are marked using the `toolkit.MarkIntegrationTest` function, which will cause them to be skipped diff --git a/go/cmd/entrypoint/flags.go b/go/cmd/entrypoint/flags.go index e1bc372..8fb2a95 100644 --- a/go/cmd/entrypoint/flags.go +++ b/go/cmd/entrypoint/flags.go @@ -6,7 +6,9 @@ import ( "fmt" "isle/nebula" "isle/toolkit" + "net" "net/netip" + "strconv" "dev.mediocregopher.com/mediocre-go-lib.git/mlog" ) @@ -34,14 +36,41 @@ func (f *textUnmarshalerFlag[T, P]) String() string { func (f *textUnmarshalerFlag[T, P]) Type() string { return "string" } -//////////////////////////////////////////////////////////////////////////////// - type ( hostNameFlag = textUnmarshalerFlag[nebula.HostName, *nebula.HostName] ipNetFlag = textUnmarshalerFlag[nebula.IPNet, *nebula.IPNet] ipFlag = textUnmarshalerFlag[netip.Addr, *netip.Addr] ) +//////////////////////////////////////////////////////////////////////////////// + +type ( + addrFlagStr string + addrFlag = textUnmarshalerFlag[addrFlagStr, *addrFlagStr] +) + +func (f addrFlagStr) MarshalText() ([]byte, error) { + return []byte(f), nil +} + +func (f *addrFlagStr) UnmarshalText(b []byte) error { + str := string(b) + + _, portStr, err := net.SplitHostPort(str) + if err != nil { + return err + } + + if _, err := strconv.ParseUint(portStr, 10, 16); err != nil { + return fmt.Errorf("invalid port %q", portStr) + } + + *f = addrFlagStr(str) + return nil +} + +//////////////////////////////////////////////////////////////////////////////// + type logLevelFlag struct { mlog.Level } diff --git a/go/cmd/entrypoint/main_test.go b/go/cmd/entrypoint/main_test.go index 9d44c7d..2a825f4 100644 --- a/go/cmd/entrypoint/main_test.go +++ b/go/cmd/entrypoint/main_test.go @@ -3,6 +3,8 @@ package main import ( "bytes" "context" + "fmt" + "isle/bootstrap" "isle/daemon" "isle/daemon/jsonrpc2" "isle/toolkit" @@ -15,6 +17,14 @@ import ( "gopkg.in/yaml.v3" ) +func bootstrapNewCreationParams(name, domain string) bootstrap.CreationParams { + return bootstrap.CreationParams{ + ID: fmt.Sprintf("%s-%s", name, domain), + Name: name, + Domain: domain, + } +} + type runHarness struct { ctx context.Context logger *mlog.Logger @@ -59,10 +69,11 @@ func (h *runHarness) run(t *testing.T, args ...string) error { ) return doRootCmd(h.ctx, h.logger, &subCmdCtxOpts{ - args: args, - stdout: h.stdout, - changeStager: h.changeStager, - daemonRPC: daemonRPCClient, + args: args, + stdout: h.stdout, + changeStager: h.changeStager, + daemonRPC: daemonRPCClient, + bootstrapNewCreationParams: bootstrapNewCreationParams, }) } diff --git a/go/cmd/entrypoint/network.go b/go/cmd/entrypoint/network.go index a17333f..8fe2b46 100644 --- a/go/cmd/entrypoint/network.go +++ b/go/cmd/entrypoint/network.go @@ -6,10 +6,15 @@ import ( "fmt" "isle/bootstrap" "isle/daemon" + "isle/daemon/daecommon" "isle/daemon/network" "isle/jsonutil" "isle/nebula" + "isle/toolkit" + "path/filepath" "slices" + "strconv" + "strings" ) var subCmdNetworkCreate = subCmd{ @@ -17,8 +22,9 @@ var subCmdNetworkCreate = subCmd{ descr: "Create's a new network, with this host being the first host in that network.", do: func(ctx subCmdCtx) error { var ( - ipNet ipNetFlag - hostName hostNameFlag + ipNet ipNetFlag + hostName hostNameFlag + vpnPublicAddr addrFlag ) name := ctx.flags.StringP( @@ -44,6 +50,19 @@ var subCmdNetworkCreate = subCmd{ "Name of this host, which will be the first host in the network", ) + vpnPublicAddrF := ctx.flags.VarPF( + &vpnPublicAddr, + "vpn-public-address", + "", + "Public address (host:port) that this host is publicly available on", + ) + + storageAllocStrs := ctx.flags.StringArray( + "storage-allocation", + nil, + "Storage allocation on this host, in the form '@`", + ) + ctx, err := ctx.withParsedFlags(&withParsedFlagsOpts{ noNetwork: true, }) @@ -58,14 +77,61 @@ var subCmdNetworkCreate = subCmd{ return errors.New("--name, --domain, --ip-net, and --hostname are required") } + type storageAlloc struct { + capacity uint64 + path string + } + + storageAllocs := make([]storageAlloc, len(*storageAllocStrs)) + for i, str := range *storageAllocStrs { + capStr, path, ok := strings.Cut(str, "@") + if !ok { + return fmt.Errorf( + "malformed --storage-allocation %q, no '@' found", str, + ) + } + + capacity, err := strconv.ParseUint(capStr, 10, 64) + if err != nil { + return fmt.Errorf( + "invalid --storage-allocation capacity %q", capStr, + ) + } + + storageAllocs[i] = storageAlloc{capacity, path} + } + daemonRPC, err := ctx.newDaemonRPC() if err != nil { return fmt.Errorf("creating daemon RPC client: %w", err) } defer daemonRPC.Close() + var networkConfig *daecommon.NetworkConfig + if vpnPublicAddrF.Changed || len(storageAllocs) > 0 { + networkConfig = toolkit.Ptr( + daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { + c.VPN.PublicAddr = string(vpnPublicAddr.V) + for _, a := range storageAllocs { + c.Storage.Allocations = append( + c.Storage.Allocations, + daecommon.ConfigStorageAllocation{ + DataPath: filepath.Join(a.path, "data"), + MetaPath: filepath.Join(a.path, "meta"), + Capacity: int(a.capacity), + }, + ) + } + }), + ) + } + err = daemonRPC.CreateNetwork( - ctx, *name, *domain, ipNet.V, hostName.V, + ctx, + ctx.opts.bootstrapNewCreationParams(*name, *domain), + ipNet.V, + hostName.V, + &daemon.CreateNetworkOpts{Config: networkConfig}, ) if err != nil { return fmt.Errorf("creating network: %w", err) diff --git a/go/cmd/entrypoint/network_test.go b/go/cmd/entrypoint/network_test.go index a6e5931..04b0563 100644 --- a/go/cmd/entrypoint/network_test.go +++ b/go/cmd/entrypoint/network_test.go @@ -5,14 +5,133 @@ import ( "fmt" "isle/bootstrap" "isle/daemon" + "isle/daemon/daecommon" "isle/nebula" "isle/toolkit" "net/netip" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestNetworkCreate(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + expect func(*testing.T, *daemon.MockRPC) + flags []string + }{ + { + name: "no given config", + expect: func(t *testing.T, daemonRPC *daemon.MockRPC) { + daemonRPC. + On( + "CreateNetwork", + toolkit.MockArg[context.Context](), + bootstrapNewCreationParams("aaa", "a.com"), + nebula.MustParseIPNet(t, "172.16.1.0/24"), + nebula.HostName("foo"), + &daemon.CreateNetworkOpts{}, + ). + Return(nil). + Once() + }, + flags: []string{ + "--name=aaa", + "--domain=a.com", + "--ip-net=172.16.1.0/24", + "--hostname=foo", + }, + }, + { + name: "partially given config", + expect: func(t *testing.T, daemonRPC *daemon.MockRPC) { + networkConfig := daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { + c.VPN.PublicAddr = "1.2.3.4:5" + }) + + daemonRPC. + On( + "CreateNetwork", + toolkit.MockArg[context.Context](), + bootstrapNewCreationParams("aaa", "a.com"), + nebula.MustParseIPNet(t, "172.16.1.0/24"), + nebula.HostName("foo"), + &daemon.CreateNetworkOpts{ + Config: &networkConfig, + }, + ). + Return(nil). + Once() + }, + flags: []string{ + "--name=aaa", + "--domain=a.com", + "--ip-net=172.16.1.0/24", + "--hostname=foo", + "--vpn-public-address=1.2.3.4:5", + }, + }, + { + name: "fully given config", + expect: func(t *testing.T, daemonRPC *daemon.MockRPC) { + networkConfig := daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) { + c.VPN.PublicAddr = "1.2.3.4:5" + c.Storage.Allocations = []daecommon.ConfigStorageAllocation{ + { + DataPath: "/a/data", + MetaPath: "/a/meta", + Capacity: 100, + }, + { + DataPath: "/b/data", + MetaPath: "/b/meta", + Capacity: 200, + }, + } + }) + + daemonRPC. + On( + "CreateNetwork", + toolkit.MockArg[context.Context](), + bootstrapNewCreationParams("aaa", "a.com"), + nebula.MustParseIPNet(t, "172.16.1.0/24"), + nebula.HostName("foo"), + &daemon.CreateNetworkOpts{ + Config: &networkConfig, + }, + ). + Return(nil). + Once() + }, + flags: []string{ + "--name=aaa", + "--domain=a.com", + "--ip-net=172.16.1.0/24", + "--hostname=foo", + "--vpn-public-address=1.2.3.4:5", + "--storage-allocation=100@/a", + "--storage-allocation=200@/b", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var ( + h = newRunHarness(t) + args = append([]string{"network", "create"}, test.flags...) + ) + + test.expect(t, h.daemonRPC) + assert.NoError(t, h.run(t, args...)) + }) + } +} + func TestNetworkList(t *testing.T) { t.Parallel() diff --git a/go/cmd/entrypoint/sub_cmd.go b/go/cmd/entrypoint/sub_cmd.go index 63017dc..a4617c7 100644 --- a/go/cmd/entrypoint/sub_cmd.go +++ b/go/cmd/entrypoint/sub_cmd.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "isle/bootstrap" "isle/daemon" "isle/daemon/jsonrpc2" "isle/jsonutil" @@ -40,6 +41,9 @@ type subCmdCtxOpts struct { stdout io.Writer changeStager *changeStager daemonRPC daemon.RPC + + // defaults to bootstrap.NewCreationParams + bootstrapNewCreationParams func(name, domain string) bootstrap.CreationParams } func (o *subCmdCtxOpts) withDefaults() *subCmdCtxOpts { @@ -55,6 +59,10 @@ func (o *subCmdCtxOpts) withDefaults() *subCmdCtxOpts { o.stdout = os.Stdout } + if o.bootstrapNewCreationParams == nil { + o.bootstrapNewCreationParams = bootstrap.NewCreationParams + } + return o } diff --git a/go/cmd/entrypoint/vpn_public_addr.go b/go/cmd/entrypoint/vpn_public_addr.go index 109eafc..146e9cf 100644 --- a/go/cmd/entrypoint/vpn_public_addr.go +++ b/go/cmd/entrypoint/vpn_public_addr.go @@ -3,7 +3,6 @@ package main import ( "errors" "fmt" - "net" ) var subCmdVPNPublicAddressGet = subCmd{ @@ -38,8 +37,11 @@ var subCmdVPNPublicAddressSet = subCmd{ name: "set", descr: "Set the public address of the host, or overwrite the already configured one", do: func(ctx subCmdCtx) error { - publicAddr := ctx.flags.String( - "public-addr", + var publicAddr addrFlag + + publicAddrF := ctx.flags.VarPF( + &publicAddr, + "to", "", "Public address (host:port) that this host is publicly available on", ) @@ -49,10 +51,8 @@ var subCmdVPNPublicAddressSet = subCmd{ return fmt.Errorf("parsing flags: %w", err) } - if *publicAddr == "" { + if !publicAddrF.Changed { return errors.New("--public-addr is required") - } else if _, _, err := net.SplitHostPort(*publicAddr); err != nil { - return fmt.Errorf("invalid --public-addr: %w", err) } daemonRPC, err := ctx.newDaemonRPC() @@ -66,7 +66,7 @@ var subCmdVPNPublicAddressSet = subCmd{ return fmt.Errorf("getting network config: %w", err) } - config.VPN.PublicAddr = *publicAddr + config.VPN.PublicAddr = string(publicAddr.V) return daemonRPC.SetConfig(ctx, config) }, diff --git a/go/daemon/client.go b/go/daemon/client.go index c780a11..ddac843 100644 --- a/go/daemon/client.go +++ b/go/daemon/client.go @@ -47,15 +47,15 @@ func (c *rpcClient) CreateNebulaCertificate(ctx context.Context, h1 nebula.HostN return } -func (c *rpcClient) CreateNetwork(ctx context.Context, name string, domain string, ipNet nebula.IPNet, hostName nebula.HostName) (err error) { +func (c *rpcClient) CreateNetwork(ctx context.Context, creationParams bootstrap.CreationParams, ipNet nebula.IPNet, hostName nebula.HostName, opts *CreateNetworkOpts) (err error) { err = c.client.Call( ctx, nil, "CreateNetwork", - name, - domain, + creationParams, ipNet, hostName, + opts, ) return } diff --git a/go/daemon/daemon.go b/go/daemon/daemon.go index df9170f..74ad137 100644 --- a/go/daemon/daemon.go +++ b/go/daemon/daemon.go @@ -21,7 +21,8 @@ var _ RPC = (*Daemon)(nil) type joinedNetwork struct { id string network.Network - config *daecommon.NetworkConfig + creationParams bootstrap.CreationParams + config *daecommon.NetworkConfig } // Daemon implements all methods of the Daemon interface, plus others used @@ -96,7 +97,7 @@ func New( return nil, fmt.Errorf("loading network %q: %w", id, err) } - d.networks[id] = joinedNetwork{id, n, networkConfig} + d.networks[id] = joinedNetwork{id, n, creationParams, networkConfig} } return d, nil @@ -114,26 +115,37 @@ func New( // and will have full administrative privileges. // // Errors: -// - network.ErrInvalidConfig -// - ErrAlreadyJoined +// - [network.ErrInvalidConfig] +// - [ErrAlreadyJoined] +// - [ErrManagedNetworkConfig] - if `opts.NetworkConfig` is given, but a +// NetworkConfig is also provided in the Daemon's Config. func (d *Daemon) CreateNetwork( ctx context.Context, - name, domain string, ipNet nebula.IPNet, hostName nebula.HostName, + creationParams bootstrap.CreationParams, + ipNet nebula.IPNet, + hostName nebula.HostName, + opts *CreateNetworkOpts, ) error { + opts = opts.withDefaults() + d.l.Lock() defer d.l.Unlock() var ( - creationParams = bootstrap.NewCreationParams(name, domain) - networkConfig = pickNetworkConfig( + networkConfig = pickNetworkConfig( d.daemonConfig.Networks, creationParams, ) networkLogger = networkLogger(d.logger, 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 { + if opts.Config != nil { + if networkConfig != nil { + return ErrManagedNetworkConfig + } + networkConfig = opts.Config + } + + if alreadyJoined(d.networks, creationParams) { return ErrAlreadyJoined } @@ -154,7 +166,7 @@ func (d *Daemon) CreateNetwork( networkLogger.Info(ctx, "Network created successfully") d.networks[creationParams.ID] = joinedNetwork{ - creationParams.ID, n, networkConfig, + creationParams.ID, n, creationParams, networkConfig, } return nil } @@ -181,9 +193,7 @@ func (d *Daemon) JoinNetwork( ) ) - if joined, err := alreadyJoined(ctx, d.networks, creationParams); err != nil { - return fmt.Errorf("checking if already joined to network: %w", err) - } else if joined { + if alreadyJoined(d.networks, creationParams) { return ErrAlreadyJoined } @@ -203,7 +213,9 @@ func (d *Daemon) JoinNetwork( } networkLogger.Info(ctx, "Network joined successfully") - d.networks[networkID] = joinedNetwork{networkID, n, networkConfig} + d.networks[networkID] = joinedNetwork{ + networkID, n, creationParams, networkConfig, + } return nil } diff --git a/go/daemon/daemon_test.go b/go/daemon/daemon_test.go index afb966e..d78fa70 100644 --- a/go/daemon/daemon_test.go +++ b/go/daemon/daemon_test.go @@ -5,6 +5,7 @@ import ( "isle/bootstrap" "isle/daemon/daecommon" "isle/daemon/network" + "isle/nebula" "isle/toolkit" "testing" @@ -234,6 +235,134 @@ func TestNew(t *testing.T) { }) } +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_SetConfig(t *testing.T) { t.Run("success", func(t *testing.T) { var ( diff --git a/go/daemon/network.go b/go/daemon/network.go index 815031d..5a440df 100644 --- a/go/daemon/network.go +++ b/go/daemon/network.go @@ -58,22 +58,13 @@ func pickNetwork( } func alreadyJoined( - ctx context.Context, - networks map[string]joinedNetwork, - creationParams bootstrap.CreationParams, -) ( - bool, error, -) { - for networkID, network := range networks { - existingCreationParams, err := network.GetNetworkCreationParams(ctx) - if err != nil { - return false, fmt.Errorf( - "getting creation params of network %q: %w", networkID, err, - ) - } else if existingCreationParams.Conflicts(creationParams) { - return true, nil + networks map[string]joinedNetwork, creationParams bootstrap.CreationParams, +) bool { + for _, network := range networks { + if network.creationParams.Conflicts(creationParams) { + return true } } - return false, nil + return false } diff --git a/go/daemon/rpc.go b/go/daemon/rpc.go index 0cc436b..c927d94 100644 --- a/go/daemon/rpc.go +++ b/go/daemon/rpc.go @@ -14,16 +14,31 @@ import ( "dev.mediocregopher.com/mediocre-go-lib.git/mlog" ) +// CreateNetworkOpts are optional arguments to the CreateNetwork method. A nil +// value is equivalent to a zero value. +type CreateNetworkOpts struct { + // Config will be used as the NetworkConfig for the new Network, rather than + // picking one provided in the Daemon's Config. + Config *daecommon.NetworkConfig +} + +func (o *CreateNetworkOpts) withDefaults() *CreateNetworkOpts { + if o == nil { + o = new(CreateNetworkOpts) + } + return o +} + // RPC defines the methods which the Daemon exposes over RPC (via the RPCHandler // or HTTPHandler methods). Method documentation can be found on the Daemon // type. type RPC interface { CreateNetwork( ctx context.Context, - name string, - domain string, + creationParams bootstrap.CreationParams, ipNet nebula.IPNet, hostName nebula.HostName, + opts *CreateNetworkOpts, ) error JoinNetwork(context.Context, network.JoiningBootstrap) error diff --git a/go/daemon/rpc_mock.go b/go/daemon/rpc_mock.go index e2e0209..6772aec 100644 --- a/go/daemon/rpc_mock.go +++ b/go/daemon/rpc_mock.go @@ -76,17 +76,17 @@ func (_m *MockRPC) CreateNebulaCertificate(_a0 context.Context, _a1 nebula.HostN return r0, r1 } -// CreateNetwork provides a mock function with given fields: ctx, name, domain, ipNet, hostName -func (_m *MockRPC) CreateNetwork(ctx context.Context, name string, domain string, ipNet nebula.IPNet, hostName nebula.HostName) error { - ret := _m.Called(ctx, name, domain, ipNet, hostName) +// CreateNetwork provides a mock function with given fields: ctx, creationParams, ipNet, hostName, opts +func (_m *MockRPC) CreateNetwork(ctx context.Context, creationParams bootstrap.CreationParams, ipNet nebula.IPNet, hostName nebula.HostName, opts *CreateNetworkOpts) error { + ret := _m.Called(ctx, creationParams, ipNet, hostName, opts) if len(ret) == 0 { panic("no return value specified for CreateNetwork") } var r0 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, nebula.IPNet, nebula.HostName) error); ok { - r0 = rf(ctx, name, domain, ipNet, hostName) + if rf, ok := ret.Get(0).(func(context.Context, bootstrap.CreationParams, nebula.IPNet, nebula.HostName, *CreateNetworkOpts) error); ok { + r0 = rf(ctx, creationParams, ipNet, hostName, opts) } else { r0 = ret.Error(0) } diff --git a/go/nebula/ip_net.go b/go/nebula/ip_net.go index 4bf35cb..d824f3e 100644 --- a/go/nebula/ip_net.go +++ b/go/nebula/ip_net.go @@ -4,11 +4,21 @@ import ( "fmt" "net" "net/netip" + "testing" ) // IPNet is the CIDR of a nebula network. type IPNet net.IPNet +// MustParseIPNet is a test helper for parsing a string into an IPNet. +func MustParseIPNet(t *testing.T, str string) IPNet { + var ipNet IPNet + if err := ipNet.UnmarshalText([]byte(str)); err != nil { + t.Fatal(err) + } + return ipNet +} + // UnmarshalText parses and validates an IPNet from a text string. func (n *IPNet) UnmarshalText(b []byte) error { str := string(b) diff --git a/go/toolkit/toolkit.go b/go/toolkit/toolkit.go index b4606fa..9fe9184 100644 --- a/go/toolkit/toolkit.go +++ b/go/toolkit/toolkit.go @@ -10,3 +10,8 @@ func IsZero[T any](v T) bool { var zero T return reflect.DeepEqual(v, zero) } + +// Ptr returns a pointer to the given value. +func Ptr[T any](v T) *T { + return &v +} diff --git a/tasks/soon/code/storage-allocation-modify.md b/tasks/soon/code/storage-allocation-modify.md new file mode 100644 index 0000000..2279117 --- /dev/null +++ b/tasks/soon/code/storage-allocation-modify.md @@ -0,0 +1,12 @@ +--- +type: task +--- + +# Implement `storage allocation modify` Sub-Command + +It should be possible to modify an allocation from the command-line, including +changing its capacity, location, and port numbers. + +Location will be the difficult one. It will require shutting down the process, +copying the data from the old location, starting the process back up, and either +removing or renaming the old data to mark it as being a backup. diff --git a/tasks/v0.0.3/update-documentation.md b/tasks/v0.0.3/update-documentation.md index 6fc661e..9b1f3d7 100644 --- a/tasks/v0.0.3/update-documentation.md +++ b/tasks/v0.0.3/update-documentation.md @@ -1,7 +1,5 @@ --- type: task -after: - - code/** --- # Update Documentation @@ -11,3 +9,13 @@ updated. Check through all development documentation, especially that surrounding testing. + +Doc changes to included are: + +- New page on network configuration, how it can be done via the `daemon.yml` + file or via the command-line (but not both!) + +- Rework "Contributing a Lighthouse" so that it doesn't directly mention nebula + or lighthouses at all. + +- Remove nebula reference from `daemon.yml` comments.