diff --git a/cmd/metal-api/internal/metal/network.go b/cmd/metal-api/internal/metal/network.go index 6fd657d19..5a4f6e1ce 100644 --- a/cmd/metal-api/internal/metal/network.go +++ b/cmd/metal-api/internal/metal/network.go @@ -329,9 +329,12 @@ func (nics Nics) FilterByHostname(hostname string) (res Nics) { return res } +// NicMap maps nic names to the corresponding nics +type NicMap map[string]*Nic + // ByName creates a map (nic names --> nic) from a nic list. -func (nics Nics) ByName() map[string]*Nic { - res := make(map[string]*Nic) +func (nics Nics) ByName() NicMap { + res := make(NicMap) for i, n := range nics { res[n.Name] = &nics[i] @@ -341,8 +344,8 @@ func (nics Nics) ByName() map[string]*Nic { } // ByIdentifier creates a map (nic identifier --> nic) from a nic list. -func (nics Nics) ByIdentifier() map[string]*Nic { - res := make(map[string]*Nic) +func (nics Nics) ByIdentifier() NicMap { + res := make(NicMap) for i, n := range nics { res[n.GetIdentifier()] = &nics[i] diff --git a/cmd/metal-api/internal/metal/network_test.go b/cmd/metal-api/internal/metal/network_test.go index a7dcfddfe..e935bd74f 100644 --- a/cmd/metal-api/internal/metal/network_test.go +++ b/cmd/metal-api/internal/metal/network_test.go @@ -23,7 +23,7 @@ func TestNics_ByIdentifier(t *testing.T) { nicArray[i].Neighbors = append(nicArray[0:i], nicArray[i+1:countOfNics]...) } - map1 := map[string]*Nic{} + map1 := NicMap{} for i, n := range nicArray { map1[string(n.MacAddress)] = &nicArray[i] } @@ -31,7 +31,7 @@ func TestNics_ByIdentifier(t *testing.T) { tests := []struct { name string nics Nics - want map[string]*Nic + want NicMap }{ { name: "TestNics_ByIdentifier Test 1", diff --git a/cmd/metal-api/internal/service/partition-service.go b/cmd/metal-api/internal/service/partition-service.go index f231675dc..7a7a9b902 100644 --- a/cmd/metal-api/internal/service/partition-service.go +++ b/cmd/metal-api/internal/service/partition-service.go @@ -436,8 +436,9 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque switch { case m.Allocation != nil: cap.Allocated++ - case m.Waiting && !m.PreAllocated && m.State.Value == metal.AvailableState: - // the free machine count considers the same aspects as the query for electing the machine candidate! + case m.Waiting && !m.PreAllocated && m.State.Value == metal.AvailableState && ec.Liveliness == metal.MachineLivelinessAlive: + // the free and allocatable machine counts consider the same aspects as the query for electing the machine candidate! + cap.Allocatable++ cap.Free++ default: cap.Unavailable++ @@ -460,8 +461,6 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque pc := pc for _, cap := range pc.ServerCapacities { - cap := cap - size := sizesByID[cap.Size] for _, reservation := range size.Reservations.ForPartition(pc.ID) { @@ -474,6 +473,10 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque } } + for _, cap := range pc.ServerCapacities { + cap.RemainingReservations = cap.Reservations - cap.UsedReservations + } + res = append(res, *pc) } diff --git a/cmd/metal-api/internal/service/partition-service_test.go b/cmd/metal-api/internal/service/partition-service_test.go index e1d821a74..943492d95 100644 --- a/cmd/metal-api/internal/service/partition-service_test.go +++ b/cmd/metal-api/internal/service/partition-service_test.go @@ -250,7 +250,7 @@ func TestUpdatePartition(t *testing.T) { func TestPartitionCapacity(t *testing.T) { var ( - mockMachines = func(mock *r.Mock, reservations []metal.Reservation, ms ...metal.Machine) { + mockMachines = func(mock *r.Mock, liveliness metal.MachineLiveliness, reservations []metal.Reservation, ms ...metal.Machine) { var ( sizes metal.Sizes events metal.ProvisioningEventContainers @@ -258,7 +258,7 @@ func TestPartitionCapacity(t *testing.T) { ) for _, m := range ms { - ec := metal.ProvisioningEventContainer{Base: metal.Base{ID: m.ID}, Liveliness: metal.MachineLivelinessAlive} + ec := metal.ProvisioningEventContainer{Base: metal.Base{ID: m.ID}, Liveliness: liveliness} if m.Waiting { ec.Events = append(ec.Events, metal.ProvisioningEvent{ Event: metal.ProvisioningEventWaiting, @@ -327,7 +327,7 @@ func TestPartitionCapacity(t *testing.T) { name: "one allocated machine", mockFn: func(mock *r.Mock) { m1 := machineTpl("1", "partition-a", "size-a", "project-123") - mockMachines(mock, nil, m1) + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) }, want: []*v1.PartitionCapacity{ { @@ -350,7 +350,7 @@ func TestPartitionCapacity(t *testing.T) { mockFn: func(mock *r.Mock) { m1 := machineTpl("1", "partition-a", "size-a", "project-123") m2 := machineTpl("2", "partition-a", "size-a", "project-123") - mockMachines(mock, nil, m1, m2) + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1, m2) }, want: []*v1.PartitionCapacity{ { @@ -373,7 +373,7 @@ func TestPartitionCapacity(t *testing.T) { mockFn: func(mock *r.Mock) { m1 := machineTpl("1", "partition-a", "size-a", "project-123") m1.IPMI.Address = "" - mockMachines(mock, nil, m1) + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) }, want: []*v1.PartitionCapacity{ { @@ -398,7 +398,7 @@ func TestPartitionCapacity(t *testing.T) { mockFn: func(mock *r.Mock) { m1 := machineTpl("1", "partition-a", "size-a", "") m1.Waiting = true - mockMachines(mock, nil, m1) + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) }, want: []*v1.PartitionCapacity{ { @@ -407,10 +407,37 @@ func TestPartitionCapacity(t *testing.T) { }, ServerCapacities: v1.ServerCapacities{ { - Size: "size-a", - Total: 1, - Waiting: 1, - Free: 1, + Size: "size-a", + Total: 1, + Waiting: 1, + Free: 1, + Allocatable: 1, + }, + }, + }, + }, + }, + { + name: "one dead machine", + mockFn: func(mock *r.Mock) { + m1 := machineTpl("1", "partition-a", "size-a", "") + m1.Waiting = true + + mockMachines(mock, metal.MachineLivelinessDead, nil, m1) + }, + want: []*v1.PartitionCapacity{ + { + Common: v1.Common{ + Identifiable: v1.Identifiable{ID: "partition-a"}, Describable: v1.Describable{Name: pointer.Pointer(""), Description: pointer.Pointer("")}, + }, + ServerCapacities: v1.ServerCapacities{ + { + Size: "size-a", + Total: 1, + Waiting: 1, + Faulty: 1, + Unavailable: 1, + FaultyMachines: []string{"1"}, }, }, }, @@ -422,7 +449,7 @@ func TestPartitionCapacity(t *testing.T) { m1 := machineTpl("1", "partition-a", "size-a", "") m1.Waiting = true m2 := machineTpl("2", "partition-a", "size-a", "project-123") - mockMachines(mock, nil, m1, m2) + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1, m2) }, want: []*v1.PartitionCapacity{ { @@ -431,12 +458,13 @@ func TestPartitionCapacity(t *testing.T) { }, ServerCapacities: v1.ServerCapacities{ { - Size: "size-a", - Total: 2, - Allocated: 1, - Waiting: 1, - PhonedHome: 1, - Free: 1, + Size: "size-a", + Total: 2, + Allocated: 1, + Waiting: 1, + PhonedHome: 1, + Free: 1, + Allocatable: 1, }, }, }, @@ -448,7 +476,7 @@ func TestPartitionCapacity(t *testing.T) { m1 := machineTpl("1", "partition-a", "size-a", "") m1.Waiting = true m1.State.Value = metal.AvailableState - mockMachines(mock, nil, m1) + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) }, want: []*v1.PartitionCapacity{ { @@ -457,10 +485,11 @@ func TestPartitionCapacity(t *testing.T) { }, ServerCapacities: v1.ServerCapacities{ { - Size: "size-a", - Total: 1, - Waiting: 1, - Free: 1, + Size: "size-a", + Total: 1, + Waiting: 1, + Free: 1, + Allocatable: 1, }, }, }, @@ -471,7 +500,7 @@ func TestPartitionCapacity(t *testing.T) { mockFn: func(mock *r.Mock) { m1 := machineTpl("1", "partition-a", "size-a", "") m1.Waiting = false - mockMachines(mock, nil, m1) + mockMachines(mock, metal.MachineLivelinessAlive, nil, m1) }, want: []*v1.PartitionCapacity{ { @@ -504,7 +533,7 @@ func TestPartitionCapacity(t *testing.T) { }, } - mockMachines(mock, reservations, m1) + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1) }, want: []*v1.PartitionCapacity{ { @@ -513,12 +542,14 @@ func TestPartitionCapacity(t *testing.T) { }, ServerCapacities: v1.ServerCapacities{ { - Size: "size-a", - Total: 1, - Waiting: 1, - Free: 0, - Reservations: 1, - UsedReservations: 0, + Size: "size-a", + Total: 1, + Waiting: 1, + Free: 0, + Allocatable: 1, + Reservations: 1, + UsedReservations: 0, + RemainingReservations: 1, }, }, }, @@ -543,7 +574,7 @@ func TestPartitionCapacity(t *testing.T) { }, } - mockMachines(mock, reservations, m1) + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1) }, want: []*v1.PartitionCapacity{ { @@ -552,12 +583,14 @@ func TestPartitionCapacity(t *testing.T) { }, ServerCapacities: v1.ServerCapacities{ { - Size: "size-a", - Total: 1, - Waiting: 1, - Free: 0, - Reservations: 3, - UsedReservations: 0, + Size: "size-a", + Total: 1, + Waiting: 1, + Free: 0, + Allocatable: 1, + Reservations: 3, + UsedReservations: 0, + RemainingReservations: 3, }, }, }, @@ -579,7 +612,7 @@ func TestPartitionCapacity(t *testing.T) { }, } - mockMachines(mock, reservations, m1, m2, m3) + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1, m2, m3) }, want: []*v1.PartitionCapacity{ { @@ -593,6 +626,7 @@ func TestPartitionCapacity(t *testing.T) { Allocated: 2, Waiting: 1, Free: 1, + Allocatable: 1, Reservations: 2, UsedReservations: 2, PhonedHome: 2, @@ -617,7 +651,7 @@ func TestPartitionCapacity(t *testing.T) { }, } - mockMachines(mock, reservations, m1, m2, m3) + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1, m2, m3) }, want: []*v1.PartitionCapacity{ { @@ -631,6 +665,7 @@ func TestPartitionCapacity(t *testing.T) { Allocated: 2, Waiting: 1, Free: 1, + Allocatable: 1, Reservations: 1, UsedReservations: 1, PhonedHome: 2, @@ -660,7 +695,7 @@ func TestPartitionCapacity(t *testing.T) { }, } - mockMachines(mock, reservations, m1, m2, m3) + mockMachines(mock, metal.MachineLivelinessAlive, reservations, m1, m2, m3) }, want: []*v1.PartitionCapacity{ { @@ -674,6 +709,7 @@ func TestPartitionCapacity(t *testing.T) { Allocated: 2, Waiting: 1, Free: 1, + Allocatable: 1, Reservations: 2, UsedReservations: 2, PhonedHome: 2, diff --git a/cmd/metal-api/internal/service/v1/partition.go b/cmd/metal-api/internal/service/v1/partition.go index 9741cc61c..942049f7f 100644 --- a/cmd/metal-api/internal/service/v1/partition.go +++ b/cmd/metal-api/internal/service/v1/partition.go @@ -68,6 +68,9 @@ type ServerCapacity struct { // Allocated is the amount of machines that are currently allocated. Allocated int `json:"allocated,omitempty" description:"allocated machines"` + // Allocatable is the amount of machines in a partition is the amount of machines that can be allocated. + // Effectively this is the amount of waiting machines minus the machines that are unavailable due to machine state or un-allocatable. Size reservations are not considered in this count. + Allocatable int `json:"allocatable,omitempty" description:"free machines with this size, size reservations are not considered"` // Free is the amount of machines in a partition that can be freely allocated at any given moment by a project. // Effectively this is the amount of waiting machines minus the machines that are unavailable due to machine state or un-allocatable due to size reservations. Free int `json:"free,omitempty" description:"free machines with this size (freely allocatable)"` @@ -84,6 +87,8 @@ type ServerCapacity struct { Reservations int `json:"reservations,omitempty" description:"the amount of reservations for this size"` // UsedReservations is the amount of reservations already used up for this size. UsedReservations int `json:"usedreservations,omitempty" description:"the amount of used reservations for this size"` + // RemainingReservations is the amount of reservations remaining for this size. + RemainingReservations int `json:"remainingreservations,omitempty" description:"the amount of unused / remaining / open reservations for this size"` } func NewPartitionResponse(p *metal.Partition) *PartitionResponse { diff --git a/spec/metal-api.json b/spec/metal-api.json index aea1918f6..13eca6558 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -4461,6 +4461,11 @@ }, "v1.ServerCapacity": { "properties": { + "allocatable": { + "description": "free machines with this size, size reservations are not considered", + "format": "int32", + "type": "integer" + }, "allocated": { "description": "allocated machines", "format": "int32", @@ -4500,6 +4505,11 @@ "format": "int32", "type": "integer" }, + "remainingreservations": { + "description": "the amount of unused / remaining / open reservations for this size", + "format": "int32", + "type": "integer" + }, "reservations": { "description": "the amount of reservations for this size", "format": "int32",