Implement 'storage remove-allocation'

This commit is contained in:
Brian Picciano 2024-11-17 14:19:46 +01:00
parent 8eb3b1d98f
commit cedd9f2c99
4 changed files with 232 additions and 34 deletions

View File

@ -2,11 +2,37 @@ package main
import ( import (
"cmp" "cmp"
"errors"
"fmt" "fmt"
"isle/daemon/daecommon" "isle/daemon/daecommon"
"slices" "slices"
"golang.org/x/exp/maps"
) )
type storageAllocation struct {
Index int `yaml:"index"`
daecommon.ConfigStorageAllocation `yaml:",inline"`
}
func indexStorageAllocations(
config daecommon.NetworkConfig,
) []storageAllocation {
slices.SortFunc(
config.Storage.Allocations,
func(i, j daecommon.ConfigStorageAllocation) int {
return cmp.Compare(i.RPCPort, j.RPCPort)
},
)
allocs := make([]storageAllocation, len(config.Storage.Allocations))
for i := range config.Storage.Allocations {
allocs[i] = storageAllocation{i, config.Storage.Allocations[i]}
}
return allocs
}
var subCmdStorageAllocationList = subCmd{ var subCmdStorageAllocationList = subCmd{
name: "list-allocation", name: "list-allocation",
plural: "s", plural: "s",
@ -22,25 +48,71 @@ var subCmdStorageAllocationList = subCmd{
return nil, fmt.Errorf("getting network config: %w", err) return nil, fmt.Errorf("getting network config: %w", err)
} }
type alloc struct { return indexStorageAllocations(config), nil
Index int `yaml:"index"` }),
daecommon.ConfigStorageAllocation `yaml:",inline"`
} }
var subCmdStorageAllocationRemove = subCmd{
name: "remove-allocation",
descr: "Removes an allocation which has been previously added. " +
"Allocations are identified by their index field from the output of " +
"`storage list-allocation(s)`.",
do: func(ctx subCmdCtx) error {
indexes := ctx.flags.IntSlice(
"index", nil,
"Index of the storage allocation which should be removed. Can be "+
"specified more than once",
)
ctx, err := ctx.withParsedFlags()
if err != nil {
return fmt.Errorf("parsing flags: %w", err)
}
if len(*indexes) == 0 {
return errors.New("At least one --index must be specified")
}
config, err := ctx.getDaemonRPC().GetConfig(ctx)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
var (
allocs = indexStorageAllocations(config)
allocsByIndex = map[int]daecommon.ConfigStorageAllocation{}
)
for _, alloc := range allocs {
allocsByIndex[alloc.Index] = alloc.ConfigStorageAllocation
}
for _, index := range *indexes {
if _, ok := allocsByIndex[index]; !ok {
return fmt.Errorf(
"Index %d not found in configured storage allocations: %w",
index, err,
)
}
delete(allocsByIndex, index)
}
// we sort the new allocation set so that tests are deterministic
newAllocs := maps.Values(allocsByIndex)
slices.SortFunc( slices.SortFunc(
config.Storage.Allocations, newAllocs,
func(i, j daecommon.ConfigStorageAllocation) int { func(i, j daecommon.ConfigStorageAllocation) int {
return cmp.Compare(i.RPCPort, j.RPCPort) return cmp.Compare(i.RPCPort, j.RPCPort)
}, },
) )
allocs := make([]alloc, len(config.Storage.Allocations)) config.Storage.Allocations = newAllocs
for i := range config.Storage.Allocations { if err := ctx.getDaemonRPC().SetConfig(ctx, config); err != nil {
allocs[i] = alloc{i, config.Storage.Allocations[i]} return fmt.Errorf("updating the network config: %w", err)
} }
return allocs, nil return nil
}), },
} }
var subCmdStorage = subCmd{ var subCmdStorage = subCmd{
@ -49,6 +121,7 @@ var subCmdStorage = subCmd{
do: func(ctx subCmdCtx) error { do: func(ctx subCmdCtx) error {
return ctx.doSubCmd( return ctx.doSubCmd(
subCmdStorageAllocationList, subCmdStorageAllocationList,
subCmdStorageAllocationRemove,
) )
}, },
} }

View File

@ -2,9 +2,12 @@ package main
import ( import (
"context" "context"
"isle/daemon"
"isle/daemon/daecommon" "isle/daemon/daecommon"
"isle/toolkit" "isle/toolkit"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestStorageAllocationList(t *testing.T) { func TestStorageAllocationList(t *testing.T) {
@ -83,3 +86,131 @@ func TestStorageAllocationList(t *testing.T) {
}) })
} }
} }
func TestStorageAllocationRemove(t *testing.T) {
t.Parallel()
config := func(rpcPorts ...int) daecommon.NetworkConfig {
return daecommon.NewNetworkConfig(func(c *daecommon.NetworkConfig) {
for _, rpcPort := range rpcPorts {
c.Storage.Allocations = append(
c.Storage.Allocations,
daecommon.ConfigStorageAllocation{RPCPort: rpcPort},
)
}
})
}
tests := []struct {
name string
args []string
setExpectations func(*daemon.MockRPC)
wantErr string
}{
{
name: "error/no indexes",
args: nil,
wantErr: "At least one --index must be specified",
},
{
name: "error/unknown index",
args: []string{"--index", "1"},
setExpectations: func(daemonRPC *daemon.MockRPC) {
daemonRPC.
On("GetConfig", toolkit.MockArg[context.Context]()).
Return(config(1000), nil).
Once()
},
wantErr: "Index 1 not found",
},
{
name: "success/remove single",
args: []string{"--index", "0"},
setExpectations: func(daemonRPC *daemon.MockRPC) {
config := config(1000, 2000)
daemonRPC.
On("GetConfig", toolkit.MockArg[context.Context]()).
Return(config, nil).
Once()
config.Storage.Allocations = config.Storage.Allocations[1:]
daemonRPC.
On(
"SetConfig",
toolkit.MockArg[context.Context](),
config,
).
Return(nil).
Once()
},
},
{
name: "success/remove multiple",
args: []string{"--index", "0", "--index", "2"},
setExpectations: func(daemonRPC *daemon.MockRPC) {
config := config(1000, 2000, 3000)
daemonRPC.
On("GetConfig", toolkit.MockArg[context.Context]()).
Return(config, nil).
Once()
config.Storage.Allocations = config.Storage.Allocations[1:2]
daemonRPC.
On(
"SetConfig",
toolkit.MockArg[context.Context](),
config,
).
Return(nil).
Once()
},
},
{
name: "success/remove all",
args: []string{"--index", "0", "--index", "1"},
setExpectations: func(daemonRPC *daemon.MockRPC) {
config := config(1000, 2000)
daemonRPC.
On("GetConfig", toolkit.MockArg[context.Context]()).
Return(config, nil).
Once()
config.Storage.Allocations = config.Storage.Allocations[:0]
daemonRPC.
On(
"SetConfig",
toolkit.MockArg[context.Context](),
config,
).
Return(nil).
Once()
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
h := newRunHarness(t)
if test.setExpectations != nil {
test.setExpectations(h.daemonRPC)
}
args := []string{"storage", "remove-allocation"}
args = append(args, test.args...)
err := h.run(t, args...)
if test.wantErr == "" {
assert.NoError(t, err)
} else {
assert.Contains(t, err.Error(), test.wantErr)
}
})
}
}

View File

@ -9,6 +9,7 @@ import (
"isle/toolkit" "isle/toolkit"
"net" "net"
"path/filepath" "path/filepath"
"strconv"
"code.betamike.com/micropelago/pmux/pmuxlib" "code.betamike.com/micropelago/pmux/pmuxlib"
"dev.mediocregopher.com/mediocre-go-lib.git/mctx" "dev.mediocregopher.com/mediocre-go-lib.git/mctx"
@ -86,6 +87,23 @@ func nebulaConfig(
hostBootstrap.PrivateCredentials.EncryptingPrivateKey.Bytes(), hostBootstrap.PrivateCredentials.EncryptingPrivateKey.Bytes(),
) )
firewall := networkConfig.VPN.Firewall
for _, alloc := range networkConfig.Storage.Allocations {
firewall.Inbound = append(
firewall.Inbound,
daecommon.ConfigFirewallRule{
Port: strconv.Itoa(alloc.S3APIPort),
Proto: "tcp",
Host: "any",
},
daecommon.ConfigFirewallRule{
Port: strconv.Itoa(alloc.RPCPort),
Proto: "tcp",
Host: "any",
},
)
}
config := map[string]any{ config := map[string]any{
"pki": map[string]string{ "pki": map[string]string{
"ca": string(caCertPEM), "ca": string(caCertPEM),
@ -100,7 +118,7 @@ func nebulaConfig(
"tun": map[string]any{ "tun": map[string]any{
"dev": networkConfig.VPN.Tun.Device, "dev": networkConfig.VPN.Tun.Device,
}, },
"firewall": networkConfig.VPN.Firewall, "firewall": firewall,
} }
if publicAddr := networkConfig.VPN.PublicAddr; publicAddr == "" { if publicAddr := networkConfig.VPN.PublicAddr; publicAddr == "" {

View File

@ -8,7 +8,6 @@ import (
"isle/toolkit" "isle/toolkit"
"isle/yamlutil" "isle/yamlutil"
"net" "net"
"strconv"
_ "embed" _ "embed"
@ -128,8 +127,6 @@ func (c *NetworkConfig) fillDefaults() {
c.VPN.Tun.Device = "isle-tun" c.VPN.Tun.Device = "isle-tun"
} }
var firewallGarageInbound []ConfigFirewallRule
for i := range c.Storage.Allocations { for i := range c.Storage.Allocations {
if c.Storage.Allocations[i].RPCPort == 0 { if c.Storage.Allocations[i].RPCPort == 0 {
c.Storage.Allocations[i].RPCPort = 3900 + (i * 10) c.Storage.Allocations[i].RPCPort = 3900 + (i * 10)
@ -142,28 +139,7 @@ func (c *NetworkConfig) fillDefaults() {
if c.Storage.Allocations[i].AdminPort == 0 { if c.Storage.Allocations[i].AdminPort == 0 {
c.Storage.Allocations[i].AdminPort = 3902 + (i * 10) c.Storage.Allocations[i].AdminPort = 3902 + (i * 10)
} }
alloc := c.Storage.Allocations[i]
firewallGarageInbound = append(
firewallGarageInbound,
ConfigFirewallRule{
Port: strconv.Itoa(alloc.S3APIPort),
Proto: "tcp",
Host: "any",
},
ConfigFirewallRule{
Port: strconv.Itoa(alloc.RPCPort),
Proto: "tcp",
Host: "any",
},
)
} }
c.VPN.Firewall.Inbound = append(
c.VPN.Firewall.Inbound,
firewallGarageInbound...,
)
} }
// UnmarshalYAML implements the yaml.Unmarshaler interface. It will attempt to // UnmarshalYAML implements the yaml.Unmarshaler interface. It will attempt to