Implement 'storage remove-allocation'
This commit is contained in:
parent
8eb3b1d98f
commit
cedd9f2c99
@ -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,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 == "" {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user