diff --git a/go/cmd/entrypoint/storage.go b/go/cmd/entrypoint/storage.go index 6188c08..1d9cb88 100644 --- a/go/cmd/entrypoint/storage.go +++ b/go/cmd/entrypoint/storage.go @@ -33,6 +33,86 @@ func indexStorageAllocations( return allocs } +var subCmdStorageAllocationAdd = subCmd{ + name: "add-allocation", + descr: "Adds a new storage allocation to the host", + do: func(ctx subCmdCtx) error { + var alloc daecommon.ConfigStorageAllocation + + ctx.flags.StringVar( + &alloc.DataPath, + "data-path", + "", + "Path to the directory data should be stored in", + ) + + ctx.flags.StringVar( + &alloc.MetaPath, + "meta-path", + "", + "Path to the directory metadata should be stored in. This is a"+ + " smaller dataset which benefits from a faster drive, if"+ + " possible.", + ) + + ctx.flags.IntVar( + &alloc.Capacity, + "capacity", + 0, + "How many gigabytes to allocate.", + ) + + ctx.flags.IntVar( + &alloc.S3APIPort, + "s3-api-port", + 0, + "Which port of the VPN network interface to serve the S3 API on."+ + " Will be automatically assigned if not given.", + ) + + ctx.flags.IntVar( + &alloc.RPCPort, + "rpc-port", + 0, + "Which port of the VPN network interface to serve RPC requests on."+ + " Will be automatically assigned if not given. Once this port"+ + " is defined for an allocation it cannot be changed.", + ) + + ctx.flags.IntVar( + &alloc.AdminPort, + "admin-port", + 0, + "Which port of the VPN network interface to serve admin requests"+ + " on. Will be automatically assigned if not given.", + ) + + ctx, err := ctx.withParsedFlags() + if err != nil { + return fmt.Errorf("parsing flags: %w", err) + } + + if alloc.DataPath == "" || alloc.MetaPath == "" || alloc.Capacity == 0 { + return errors.New( + "--data-path, --meta-path, and --capacity are required", + ) + } + + config, err := ctx.getDaemonRPC().GetConfig(ctx) + if err != nil { + return fmt.Errorf("getting network config: %w", err) + } + + config.Storage.Allocations = append(config.Storage.Allocations, alloc) + + if err := ctx.getDaemonRPC().SetConfig(ctx, config); err != nil { + return fmt.Errorf("updating the network config: %w", err) + } + + return nil + }, +} + var subCmdStorageAllocationList = subCmd{ name: "list-allocation", plural: "s", @@ -120,6 +200,7 @@ var subCmdStorage = subCmd{ descr: "Sub-commands having to do with configuration of storage on this host", do: func(ctx subCmdCtx) error { return ctx.doSubCmd( + subCmdStorageAllocationAdd, subCmdStorageAllocationList, subCmdStorageAllocationRemove, ) diff --git a/go/cmd/entrypoint/storage_test.go b/go/cmd/entrypoint/storage_test.go index e54363d..b386910 100644 --- a/go/cmd/entrypoint/storage_test.go +++ b/go/cmd/entrypoint/storage_test.go @@ -10,6 +10,79 @@ import ( "github.com/stretchr/testify/assert" ) +func TestStorageAllocationAdd(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + args []string + setExpectations func(*daemon.MockRPC) + wantAlloc daecommon.ConfigStorageAllocation + }{ + { + name: "success/no ports", + args: []string{ + "--data-path", "foo", "--meta-path=bar", "--capacity", "1", + }, + wantAlloc: daecommon.ConfigStorageAllocation{ + DataPath: "foo", + MetaPath: "bar", + Capacity: 1, + }, + }, + { + name: "success/all ports", + args: []string{ + "--data-path", "foo", + "--meta-path=bar", + "--capacity", "1", + "--s3-api-port", "1000", + "--rpc-port=2000", + "--admin-port", "3000", + }, + wantAlloc: daecommon.ConfigStorageAllocation{ + DataPath: "foo", + MetaPath: "bar", + Capacity: 1, + S3APIPort: 1000, + RPCPort: 2000, + AdminPort: 3000, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var ( + h = newRunHarness(t) + config = daecommon.NewNetworkConfig(nil) + ) + + h.daemonRPC. + On("GetConfig", toolkit.MockArg[context.Context]()). + Return(config, nil). + Once() + + config.Storage.Allocations = append( + config.Storage.Allocations, test.wantAlloc, + ) + + h.daemonRPC. + On( + "SetConfig", + toolkit.MockArg[context.Context](), + config, + ). + Return(nil). + Once() + + args := []string{"storage", "add-allocation"} + args = append(args, test.args...) + assert.NoError(t, h.run(t, args...)) + }) + } +} + func TestStorageAllocationList(t *testing.T) { t.Parallel() diff --git a/go/daemon/daecommon/config.go b/go/daemon/daecommon/config.go index f291125..8d0b567 100644 --- a/go/daemon/daecommon/config.go +++ b/go/daemon/daecommon/config.go @@ -127,17 +127,21 @@ func (c *NetworkConfig) fillDefaults() { c.VPN.Tun.Device = "isle-tun" } + nextRPCPort := 3900 + for i := range c.Storage.Allocations { if c.Storage.Allocations[i].RPCPort == 0 { - c.Storage.Allocations[i].RPCPort = 3900 + (i * 10) + c.Storage.Allocations[i].RPCPort = nextRPCPort } + nextRPCPort += 10 + if c.Storage.Allocations[i].S3APIPort == 0 { - c.Storage.Allocations[i].S3APIPort = 3901 + (i * 10) + c.Storage.Allocations[i].S3APIPort = c.Storage.Allocations[i].RPCPort + 1 } if c.Storage.Allocations[i].AdminPort == 0 { - c.Storage.Allocations[i].AdminPort = 3902 + (i * 10) + c.Storage.Allocations[i].AdminPort = c.Storage.Allocations[i].RPCPort + 2 } } }