Move storage allocation sub-commands under 'storage allocation(s)'
This commit is contained in:
parent
aac7b30cf0
commit
5669123c99
@ -1,208 +1,11 @@
|
|||||||
package main
|
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(nil)
|
|
||||||
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(nil)
|
|
||||||
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(nil)
|
|
||||||
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{
|
var subCmdStorage = subCmd{
|
||||||
name: "storage",
|
name: "storage",
|
||||||
descr: "Sub-commands related to configuration of storage on this host",
|
descr: "Sub-commands related to configuration of storage on this host",
|
||||||
do: func(ctx subCmdCtx) error {
|
do: func(ctx subCmdCtx) error {
|
||||||
return ctx.doSubCmd(
|
return ctx.doSubCmd(
|
||||||
subCmdStorageAllocationAdd,
|
subCmdStorageAllocation,
|
||||||
subCmdStorageAllocationList,
|
|
||||||
subCmdStorageAllocationRemove,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
208
go/cmd/entrypoint/storage_allocation.go
Normal file
208
go/cmd/entrypoint/storage_allocation.go
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
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",
|
||||||
|
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(nil)
|
||||||
|
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",
|
||||||
|
descr: "Lists all storage which is currently allocated on this host",
|
||||||
|
do: doWithOutput(func(ctx subCmdCtx) (any, error) {
|
||||||
|
ctx, err := ctx.withParsedFlags(nil)
|
||||||
|
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",
|
||||||
|
descr: "Removes an allocation which has been previously added. " +
|
||||||
|
"Allocations are identified by their index field from the output of " +
|
||||||
|
"`storage allocation list`.",
|
||||||
|
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(nil)
|
||||||
|
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 subCmdStorageAllocation = subCmd{
|
||||||
|
name: "allocation",
|
||||||
|
descr: "Manage storage allocations configured on this host",
|
||||||
|
plural: "s",
|
||||||
|
do: func(ctx subCmdCtx) error {
|
||||||
|
return ctx.doSubCmd(
|
||||||
|
subCmdStorageAllocationAdd,
|
||||||
|
subCmdStorageAllocationList,
|
||||||
|
subCmdStorageAllocationRemove,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
@ -76,7 +76,7 @@ func TestStorageAllocationAdd(t *testing.T) {
|
|||||||
Return(nil).
|
Return(nil).
|
||||||
Once()
|
Once()
|
||||||
|
|
||||||
args := []string{"storage", "add-allocation"}
|
args := []string{"storage", "allocation", "add"}
|
||||||
args = append(args, test.args...)
|
args = append(args, test.args...)
|
||||||
assert.NoError(t, h.run(t, args...))
|
assert.NoError(t, h.run(t, args...))
|
||||||
})
|
})
|
||||||
@ -155,7 +155,7 @@ func TestStorageAllocationList(t *testing.T) {
|
|||||||
Return(config, nil).
|
Return(config, nil).
|
||||||
Once()
|
Once()
|
||||||
|
|
||||||
h.runAssertStdout(t, test.want, "storage", "list-allocations")
|
h.runAssertStdout(t, test.want, "storage", "allocation", "list")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,7 +275,7 @@ func TestStorageAllocationRemove(t *testing.T) {
|
|||||||
test.setExpectations(h.daemonRPC)
|
test.setExpectations(h.daemonRPC)
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"storage", "remove-allocation"}
|
args := []string{"storage", "allocation", "remove"}
|
||||||
args = append(args, test.args...)
|
args = append(args, test.args...)
|
||||||
err := h.run(t, args...)
|
err := h.run(t, args...)
|
||||||
|
|
@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
type: task
|
|
||||||
---
|
|
||||||
|
|
||||||
# Rename Storage Allocation-Related Sub-Commands
|
|
||||||
|
|
||||||
Rather than having sub-commands like `storage list-allocations` there should
|
|
||||||
instead be `storage allocation(s) list`.
|
|
Loading…
Reference in New Issue
Block a user