Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/admin/v2/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func AddCmds(cmd *cobra.Command, c *config.Config) {

adminCmd.AddCommand(newComponentCmd(c))
adminCmd.AddCommand(newImageCmd(c))
adminCmd.AddCommand(newPartitionCmd(c))
adminCmd.AddCommand(newProjectCmd(c))
adminCmd.AddCommand(newTenantCmd(c))
adminCmd.AddCommand(newTokenCmd(c))
Expand Down
137 changes: 137 additions & 0 deletions cmd/admin/v2/partition.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package v2

import (
"fmt"
"strings"

adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2"
apiv2 "github.com/metal-stack/api/go/metalstack/api/v2"
"github.com/metal-stack/cli/cmd/config"
"github.com/metal-stack/cli/cmd/sorters"
"github.com/metal-stack/metal-lib/pkg/genericcli"
"github.com/metal-stack/metal-lib/pkg/genericcli/printers"
"github.com/metal-stack/metal-lib/pkg/pointer"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

type partition struct {
c *config.Config
}

func newPartitionCmd(c *config.Config) *cobra.Command {
w := &partition{
c: c,
}

gcli := genericcli.NewGenericCLI(w).WithFS(c.Fs)

cmdsConfig := &genericcli.CmdsConfig[any, any, *apiv2.Partition]{
BinaryName: config.BinaryName,
GenericCLI: gcli,
Singular: "partition",
Plural: "partitions",
Description: "manage partitions",
DescribePrinter: func() printers.Printer { return c.DescribePrinter },
ListPrinter: func() printers.Printer { return c.ListPrinter },
OnlyCmds: genericcli.OnlyCmds(genericcli.DescribeCmd, genericcli.ListCmd),
DescribeCmdMutateFn: func(cmd *cobra.Command) {
cmd.RunE = func(cmd *cobra.Command, args []string) error {
return gcli.DescribeAndPrint("", w.c.DescribePrinter)
}
},
}

capacityCmd := &cobra.Command{
Use: "capacity",
Short: "show partition capacity",
RunE: func(cmd *cobra.Command, args []string) error {
return w.capacity()
},
}

capacityCmd.Flags().StringP("id", "", "", "filter on partition id.")
capacityCmd.Flags().StringP("size", "", "", "filter on size id.")
capacityCmd.Flags().StringP("project", "", "", "consider project-specific counts, e.g. size reservations.")
capacityCmd.Flags().StringSlice("sort-by", []string{}, fmt.Sprintf("order by (comma separated) column(s), sort direction can be changed by appending :asc or :desc behind the column identifier. possible values: %s", strings.Join(sorters.PartitionCapacitySorter().AvailableKeys(), "|")))
genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("id", c.Completion.PartitionListCompletion))
genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("project", c.Completion.ProjectListCompletion))
genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("size", c.Completion.SizeListCompletion))
genericcli.Must(capacityCmd.RegisterFlagCompletionFunc("sort-by", cobra.FixedCompletions(sorters.PartitionCapacitySorter().AvailableKeys(), cobra.ShellCompDirectiveNoFileComp)))

return genericcli.NewCmds(cmdsConfig, capacityCmd)
}

func (c *partition) capacity() error {
ctx, cancel := c.c.NewRequestContext()
defer cancel()

req := &adminv2.PartitionServiceCapacityRequest{}

if viper.IsSet("id") {
req.Id = new(viper.GetString("id"))
}
if viper.IsSet("size") {
req.Size = new(viper.GetString("size"))
}
if viper.IsSet("project") {
req.Project = new(viper.GetString("project"))
}
resp, err := c.c.Client.Adminv2().Partition().Capacity(ctx, req)
if err != nil {
return fmt.Errorf("failed to get partition capacity: %w", err)
}

err = sorters.PartitionCapacitySorter().SortBy(resp.PartitionCapacity)
if err != nil {
return err
}

return c.c.ListPrinter.Print(resp.PartitionCapacity)
}

func (c *partition) Get(id string) (*apiv2.Partition, error) {
ctx, cancel := c.c.NewRequestContext()
defer cancel()

req := &apiv2.PartitionServiceGetRequest{Id: id}

resp, err := c.c.Client.Apiv2().Partition().Get(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get partition: %w", err)
}

return resp.Partition, nil
}

func (c *partition) List() ([]*apiv2.Partition, error) {
ctx, cancel := c.c.NewRequestContext()
defer cancel()

req := &apiv2.PartitionServiceListRequest{Query: &apiv2.PartitionQuery{
Id: pointer.PointerOrNil(viper.GetString("id")),
}}

resp, err := c.c.Client.Apiv2().Partition().List(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to get partitions: %w", err)
}

return resp.Partitions, nil
}

func (c *partition) Create(rq any) (*apiv2.Partition, error) {
panic("unimplemented")
}

func (c *partition) Delete(id string) (*apiv2.Partition, error) {
panic("unimplemented")
}

func (t *partition) Convert(r *apiv2.Partition) (string, any, any, error) {
panic("unimplemented")
}

func (t *partition) Update(rq any) (*apiv2.Partition, error) {
panic("unimplemented")
}
250 changes: 250 additions & 0 deletions cmd/admin/v2/partition_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
package v2_test

import (
"testing"

adminv2 "github.com/metal-stack/api/go/metalstack/admin/v2"
apiv2 "github.com/metal-stack/api/go/metalstack/api/v2"
apitests "github.com/metal-stack/api/go/tests"
"github.com/metal-stack/cli/pkg/test"
"github.com/stretchr/testify/mock"
)

// Generated with AI

var (
testPartition1 = &apiv2.Partition{
Id: "1",
Description: "partition 1",
MgmtServiceAddresses: []string{
"192.168.1.1:1234",
},
BootConfiguration: &apiv2.PartitionBootConfiguration{
Commandline: "commandline",
ImageUrl: "imageurl",
KernelUrl: "kernelurl",
},
Meta: &apiv2.Meta{
Labels: &apiv2.Labels{
Labels: map[string]string{
"a": "b",
},
},
},
}
testPartition2 = &apiv2.Partition{
Id: "2",
Description: "partition 2",
MgmtServiceAddresses: []string{
"192.168.1.2:1234",
},
BootConfiguration: &apiv2.PartitionBootConfiguration{
Commandline: "commandline",
ImageUrl: "imageurl",
KernelUrl: "kernelurl",
},
Meta: &apiv2.Meta{
Labels: &apiv2.Labels{
Labels: nil,
},
},
}
)

func Test_AdminPartitionCmd_List(t *testing.T) {
tests := []*test.Test[[]*apiv2.Partition]{
{
Name: "list",
Cmd: func(want []*apiv2.Partition) []string {
return []string{"admin", "partition", "list"}
},
ClientMocks: &apitests.ClientMockFns{
Apiv2Mocks: &apitests.Apiv2MockFns{
Partition: func(m *mock.Mock) {
m.On("List", mock.Anything, mock.Anything).Return(&apiv2.PartitionServiceListResponse{
Partitions: []*apiv2.Partition{
testPartition1,
testPartition2,
},
}, nil)
},
},
},
Want: []*apiv2.Partition{
testPartition1,
testPartition2,
},
WantTable: new(`
ID DESCRIPTION
1 partition 1
2 partition 2
`),
},
}
for _, tt := range tests {
tt.TestCmd(t)
}
}

func Test_AdminPartitionCmd_Describe(t *testing.T) {
tests := []*test.Test[*apiv2.Partition]{
{
Name: "describe",
Cmd: func(want *apiv2.Partition) []string {
return []string{"admin", "partition", "describe", want.Id}
},
ClientMocks: &apitests.ClientMockFns{
Apiv2Mocks: &apitests.Apiv2MockFns{
Partition: func(m *mock.Mock) {
m.On("Get", mock.Anything, mock.Anything).Return(&apiv2.PartitionServiceGetResponse{
Partition: testPartition1,
}, nil)
},
},
},
Want: testPartition1,
WantTable: new(`
ID DESCRIPTION
1 partition 1
`),
},
}
for _, tt := range tests {
tt.TestCmd(t)
}
}

func Test_AdminPartitionCmd_Capacity(t *testing.T) {
tests := []*test.Test[[]*adminv2.PartitionCapacity]{
{
Name: "capacity",
Cmd: func(want []*adminv2.PartitionCapacity) []string {
return []string{"admin", "partition", "capacity"}
},
ClientMocks: &apitests.ClientMockFns{
Adminv2Mocks: &apitests.Adminv2MockFns{
Partition: func(m *mock.Mock) {
m.On("Capacity", mock.Anything, mock.Anything).Return(&adminv2.PartitionServiceCapacityResponse{
PartitionCapacity: []*adminv2.PartitionCapacity{
{
Partition: "partition-1",
MachineSizeCapacities: []*adminv2.MachineSizeCapacity{
{
Size: "size-1",
Free: 3,
Allocated: 1,
Total: 5,
Faulty: 2,
Reservations: 3,
UsedReservations: 1,
},
},
},
},
}, nil)
},
},
},
Want: []*adminv2.PartitionCapacity{
{
Partition: "partition-1",
MachineSizeCapacities: []*adminv2.MachineSizeCapacity{
{
Size: "size-1",
Free: 3,
Allocated: 1,
Total: 5,
Faulty: 2,
Reservations: 3,
UsedReservations: 1,
},
},
},
},
WantTable: new(`
PARTITION SIZE ALLOCATED FREE UNAVAILABLE RESERVATIONS | TOTAL | FAULTY
partition-1 size-1 1 3 0 2 (1/3 used) | 5 | 2
Total 1 3 0 2 | 5 | 2
`),
},
{
Name: "capacity with filters",
Cmd: func(want []*adminv2.PartitionCapacity) []string {
return []string{"admin", "partition", "capacity", "--id", "partition-1", "--size", "size-1", "--project", "project-123", "--sort-by", "id"}
},
ClientMocks: &apitests.ClientMockFns{
Adminv2Mocks: &apitests.Adminv2MockFns{
Partition: func(m *mock.Mock) {
m.On("Capacity", mock.Anything, mock.Anything).Return(&adminv2.PartitionServiceCapacityResponse{
PartitionCapacity: []*adminv2.PartitionCapacity{
{
Partition: "partition-1",
MachineSizeCapacities: []*adminv2.MachineSizeCapacity{
{
Size: "size-1",
Free: 3,
Allocated: 1,
Total: 5,
Faulty: 2,
Reservations: 3,
UsedReservations: 1,
},
},
},
},
}, nil)
},
},
},
Want: []*adminv2.PartitionCapacity{
{
Partition: "partition-1",
MachineSizeCapacities: []*adminv2.MachineSizeCapacity{
{
Size: "size-1",
Free: 3,
Allocated: 1,
Total: 5,
Faulty: 2,
Reservations: 3,
UsedReservations: 1,
},
},
},
},
WantTable: new(`
PARTITION SIZE ALLOCATED FREE UNAVAILABLE RESERVATIONS | TOTAL | FAULTY
partition-1 size-1 1 3 0 2 (1/3 used) | 5 | 2
Total 1 3 0 2 | 5 | 2
`),
},
}
for _, tt := range tests {
tt.TestCmd(t)
}
}

func Test_AdminPartitionCmd_ExhaustiveArgs(t *testing.T) {
tests := []struct {
name string
args []string
}{
{
name: "list",
args: []string{"admin", "partition", "list"},
},
{
name: "describe",
args: []string{"admin", "partition", "describe", "1"},
},
{
name: "capacity",
args: []string{"admin", "partition", "capacity", "--id", "partition-1", "--size", "size-1", "--project", "project-123", "--sort-by", "id"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
test.AssertExhaustiveArgs(t, tt.args)
})
}
}
Loading