isle/go/cmd/entrypoint/storage.go

209 lines
5.0 KiB
Go

package main
import (
"cmp"
"errors"
"fmt"
"isle/daemon/daecommon"
"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 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.daemonRPC.GetConfig(ctx)
if err != nil {
return fmt.Errorf("getting network config: %w", err)
}
config.Storage.Allocations = append(config.Storage.Allocations, alloc)
if err := ctx.daemonRPC.SetConfig(ctx, config); err != nil {
return fmt.Errorf("updating the network config: %w", err)
}
return nil
},
}
var subCmdStorageAllocationList = subCmd{
name: "list-allocation",
plural: "s",
descr: "Lists all storage which is currently allocated on this host",
do: doWithOutput(func(ctx subCmdCtx) (any, error) {
ctx, err := ctx.withParsedFlags()
if err != nil {
return nil, fmt.Errorf("parsing flags: %w", err)
}
config, err := ctx.daemonRPC.GetConfig(ctx)
if err != nil {
return nil, fmt.Errorf("getting network config: %w", err)
}
return indexStorageAllocations(config), nil
}),
}
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.daemonRPC.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(
newAllocs,
func(i, j daecommon.ConfigStorageAllocation) int {
return cmp.Compare(i.RPCPort, j.RPCPort)
},
)
config.Storage.Allocations = newAllocs
if err := ctx.daemonRPC.SetConfig(ctx, config); err != nil {
return fmt.Errorf("updating the network config: %w", err)
}
return nil
},
}
var subCmdStorage = subCmd{
name: "storage",
descr: "Sub-commands related to configuration of storage on this host",
do: func(ctx subCmdCtx) error {
return ctx.doSubCmd(
subCmdStorageAllocationAdd,
subCmdStorageAllocationList,
subCmdStorageAllocationRemove,
)
},
}