From 500f1f5a25966bc9db482005d999ae306ad788f6 Mon Sep 17 00:00:00 2001 From: Gerrit91 Date: Mon, 9 Oct 2023 14:07:58 +0200 Subject: [PATCH] Let partition capacity use issue evaluation. --- .../internal/issues/asn-uniqueness.go | 4 +- .../internal/issues/bmc-info-outdated.go | 4 +- .../internal/issues/bmc-without-ip.go | 4 +- .../internal/issues/bmc-without-mac.go | 4 +- cmd/metal-api/internal/issues/crash-loop.go | 4 +- .../internal/issues/failed-machine-reclaim.go | 4 +- cmd/metal-api/internal/issues/issues.go | 26 +-- cmd/metal-api/internal/issues/issues_test.go | 2 +- .../internal/issues/last-event-error.go | 4 +- .../internal/issues/liveliness-dead.go | 4 +- .../issues/liveliness-not-available.go | 4 +- .../internal/issues/liveliness-unknown.go | 4 +- .../internal/issues/no-event-container.go | 4 +- cmd/metal-api/internal/issues/no-partition.go | 4 +- .../internal/issues/non-distinct-bmc-ip.go | 4 +- cmd/metal-api/internal/issues/types.go | 2 +- cmd/metal-api/internal/metal/provisioning.go | 4 +- .../internal/metal/provisioning_test.go | 2 +- .../internal/service/machine-service.go | 29 +-- .../internal/service/partition-service.go | 185 +++++++++--------- .../service/partition-service_test.go | 24 ++- .../internal/service/v1/partition.go | 15 +- spec/metal-api.json | 31 --- 23 files changed, 178 insertions(+), 194 deletions(-) diff --git a/cmd/metal-api/internal/issues/asn-uniqueness.go b/cmd/metal-api/internal/issues/asn-uniqueness.go index e01a07756..d2fe39e3e 100644 --- a/cmd/metal-api/internal/issues/asn-uniqueness.go +++ b/cmd/metal-api/internal/issues/asn-uniqueness.go @@ -18,8 +18,8 @@ type ( } ) -func (i *IssueASNUniqueness) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueASNUniqueness) Spec() *spec { + return &spec{ Type: IssueTypeASNUniqueness, Severity: IssueSeverityMinor, Description: "The ASN is not unique (only impact on firewalls)", diff --git a/cmd/metal-api/internal/issues/bmc-info-outdated.go b/cmd/metal-api/internal/issues/bmc-info-outdated.go index d7766d875..7c76b1b3d 100644 --- a/cmd/metal-api/internal/issues/bmc-info-outdated.go +++ b/cmd/metal-api/internal/issues/bmc-info-outdated.go @@ -37,8 +37,8 @@ func (i *IssueBMCInfoOutdated) Evaluate(m metal.Machine, ec metal.ProvisioningEv return false } -func (*IssueBMCInfoOutdated) Spec() *issueSpec { - return &issueSpec{ +func (*IssueBMCInfoOutdated) Spec() *spec { + return &spec{ Type: IssueTypeBMCInfoOutdated, Severity: IssueSeverityMajor, Description: "BMC has not been updated from either metal-hammer or metal-bmc", diff --git a/cmd/metal-api/internal/issues/bmc-without-ip.go b/cmd/metal-api/internal/issues/bmc-without-ip.go index 3552b440e..dad08b13f 100644 --- a/cmd/metal-api/internal/issues/bmc-without-ip.go +++ b/cmd/metal-api/internal/issues/bmc-without-ip.go @@ -10,8 +10,8 @@ type ( IssueBMCWithoutIP struct{} ) -func (i *IssueBMCWithoutIP) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueBMCWithoutIP) Spec() *spec { + return &spec{ Type: IssueTypeBMCWithoutIP, Severity: IssueSeverityMajor, Description: "BMC has no ip address", diff --git a/cmd/metal-api/internal/issues/bmc-without-mac.go b/cmd/metal-api/internal/issues/bmc-without-mac.go index bb220b9da..28b6e4197 100644 --- a/cmd/metal-api/internal/issues/bmc-without-mac.go +++ b/cmd/metal-api/internal/issues/bmc-without-mac.go @@ -10,8 +10,8 @@ type ( IssueBMCWithoutMAC struct{} ) -func (i *IssueBMCWithoutMAC) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueBMCWithoutMAC) Spec() *spec { + return &spec{ Type: IssueTypeBMCWithoutMAC, Severity: IssueSeverityMajor, Description: "BMC has no mac address", diff --git a/cmd/metal-api/internal/issues/crash-loop.go b/cmd/metal-api/internal/issues/crash-loop.go index 668d69e03..c50d30f84 100644 --- a/cmd/metal-api/internal/issues/crash-loop.go +++ b/cmd/metal-api/internal/issues/crash-loop.go @@ -13,8 +13,8 @@ type ( IssueCrashLoop struct{} ) -func (i *IssueCrashLoop) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueCrashLoop) Spec() *spec { + return &spec{ Type: IssueTypeCrashLoop, Severity: IssueSeverityMajor, Description: "machine is in a provisioning crash loop (⭕)", diff --git a/cmd/metal-api/internal/issues/failed-machine-reclaim.go b/cmd/metal-api/internal/issues/failed-machine-reclaim.go index 02b62ebfa..b66a2a9b3 100644 --- a/cmd/metal-api/internal/issues/failed-machine-reclaim.go +++ b/cmd/metal-api/internal/issues/failed-machine-reclaim.go @@ -13,8 +13,8 @@ type ( IssueFailedMachineReclaim struct{} ) -func (i *IssueFailedMachineReclaim) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueFailedMachineReclaim) Spec() *spec { + return &spec{ Type: IssueTypeFailedMachineReclaim, Severity: IssueSeverityCritical, Description: "machine phones home but not allocated", diff --git a/cmd/metal-api/internal/issues/issues.go b/cmd/metal-api/internal/issues/issues.go index f7c584502..19538b52e 100644 --- a/cmd/metal-api/internal/issues/issues.go +++ b/cmd/metal-api/internal/issues/issues.go @@ -38,20 +38,20 @@ type ( // MachineIssues is map of a machine response to a list of machine issues MachineIssues []*MachineWithIssues - machineIssueMap map[*metal.Machine]Issues + MachineIssuesMap map[*metal.Machine]Issues - issueImpl interface { + issue interface { // Evaluate decides whether a given machine has the machine issue. // the third argument contains additional information that may be required for the issue evaluation Evaluate(m metal.Machine, ec metal.ProvisioningEventContainer, c *IssueConfig) bool // Spec returns the issue spec of this issue. - Spec() *issueSpec + Spec() *spec // Details returns additional information on the issue after the evaluation. Details() string } - // issueSpec defines the specification of an issue. - issueSpec struct { + // spec defines the specification of an issue. + spec struct { Type IssueType Severity IssueSeverity Description string @@ -74,7 +74,7 @@ func AllIssues() Issues { return res } -func toIssue(i issueImpl) Issue { +func toIssue(i issue) Issue { return Issue{ Type: i.Spec().Type, Severity: i.Spec().Severity, @@ -84,8 +84,12 @@ func toIssue(i issueImpl) Issue { } } -func FindIssues(c *IssueConfig) (MachineIssues, error) { - res := machineIssueMap{} +func FindIssues(c *IssueConfig) (MachineIssuesMap, error) { + if c.LastErrorThreshold == 0 { + c.LastErrorThreshold = DefaultLastErrorThreshold() + } + + res := MachineIssuesMap{} ecs := c.EventContainers.ByID() @@ -114,7 +118,7 @@ func FindIssues(c *IssueConfig) (MachineIssues, error) { } } - return res.toList(), nil + return res, nil } func (mis MachineIssues) Get(id string) *MachineWithIssues { @@ -161,7 +165,7 @@ func (c *IssueConfig) includeIssue(t IssueType) bool { return true } -func (mim machineIssueMap) add(m metal.Machine, issue Issue) { +func (mim MachineIssuesMap) add(m metal.Machine, issue Issue) { issues, ok := mim[&m] if !ok { issues = Issues{} @@ -170,7 +174,7 @@ func (mim machineIssueMap) add(m metal.Machine, issue Issue) { mim[&m] = issues } -func (mim machineIssueMap) toList() MachineIssues { +func (mim MachineIssuesMap) ToList() MachineIssues { var res MachineIssues for m, issues := range mim { diff --git a/cmd/metal-api/internal/issues/issues_test.go b/cmd/metal-api/internal/issues/issues_test.go index 1057669c6..02f6fd5ac 100644 --- a/cmd/metal-api/internal/issues/issues_test.go +++ b/cmd/metal-api/internal/issues/issues_test.go @@ -509,7 +509,7 @@ func TestFindIssues(t *testing.T) { want = tt.want(ms) } - if diff := cmp.Diff(want, got, cmp.AllowUnexported(IssueLastEventError{}, IssueASNUniqueness{}, IssueNonDistinctBMCIP{})); diff != "" { + if diff := cmp.Diff(want, got.ToList(), cmp.AllowUnexported(IssueLastEventError{}, IssueASNUniqueness{}, IssueNonDistinctBMCIP{})); diff != "" { t.Errorf("diff (+got -want):\n %s", diff) } }) diff --git a/cmd/metal-api/internal/issues/last-event-error.go b/cmd/metal-api/internal/issues/last-event-error.go index 09ec35999..694bd0832 100644 --- a/cmd/metal-api/internal/issues/last-event-error.go +++ b/cmd/metal-api/internal/issues/last-event-error.go @@ -21,8 +21,8 @@ func DefaultLastErrorThreshold() time.Duration { return 7 * 24 * time.Hour } -func (i *IssueLastEventError) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueLastEventError) Spec() *spec { + return &spec{ Type: IssueTypeLastEventError, Severity: IssueSeverityMinor, Description: "the machine had an error during the provisioning lifecycle", diff --git a/cmd/metal-api/internal/issues/liveliness-dead.go b/cmd/metal-api/internal/issues/liveliness-dead.go index 88b9a5a2b..e9620e3be 100644 --- a/cmd/metal-api/internal/issues/liveliness-dead.go +++ b/cmd/metal-api/internal/issues/liveliness-dead.go @@ -10,8 +10,8 @@ type ( IssueLivelinessDead struct{} ) -func (i *IssueLivelinessDead) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueLivelinessDead) Spec() *spec { + return &spec{ Type: IssueTypeLivelinessDead, Severity: IssueSeverityMajor, Description: "the machine is not sending events anymore", diff --git a/cmd/metal-api/internal/issues/liveliness-not-available.go b/cmd/metal-api/internal/issues/liveliness-not-available.go index 11d7af720..44f51cfab 100644 --- a/cmd/metal-api/internal/issues/liveliness-not-available.go +++ b/cmd/metal-api/internal/issues/liveliness-not-available.go @@ -10,8 +10,8 @@ type ( IssueLivelinessNotAvailable struct{} ) -func (i *IssueLivelinessNotAvailable) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueLivelinessNotAvailable) Spec() *spec { + return &spec{ Type: IssueTypeLivelinessNotAvailable, Severity: IssueSeverityMinor, Description: "the machine liveliness is not available", diff --git a/cmd/metal-api/internal/issues/liveliness-unknown.go b/cmd/metal-api/internal/issues/liveliness-unknown.go index b306f82b8..3e072ee18 100644 --- a/cmd/metal-api/internal/issues/liveliness-unknown.go +++ b/cmd/metal-api/internal/issues/liveliness-unknown.go @@ -10,8 +10,8 @@ type ( IssueLivelinessUnknown struct{} ) -func (i *IssueLivelinessUnknown) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueLivelinessUnknown) Spec() *spec { + return &spec{ Type: IssueTypeLivelinessUnknown, Severity: IssueSeverityMajor, Description: "the machine is not sending LLDP alive messages anymore", diff --git a/cmd/metal-api/internal/issues/no-event-container.go b/cmd/metal-api/internal/issues/no-event-container.go index 314510348..2c260c30d 100644 --- a/cmd/metal-api/internal/issues/no-event-container.go +++ b/cmd/metal-api/internal/issues/no-event-container.go @@ -12,8 +12,8 @@ type ( IssueNoEventContainer struct{} ) -func (i *IssueNoEventContainer) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueNoEventContainer) Spec() *spec { + return &spec{ Type: IssueTypeNoEventContainer, Severity: IssueSeverityMajor, Description: "machine has no event container", diff --git a/cmd/metal-api/internal/issues/no-partition.go b/cmd/metal-api/internal/issues/no-partition.go index 85e4e6460..276237c54 100644 --- a/cmd/metal-api/internal/issues/no-partition.go +++ b/cmd/metal-api/internal/issues/no-partition.go @@ -10,8 +10,8 @@ type ( IssueNoPartition struct{} ) -func (i *IssueNoPartition) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueNoPartition) Spec() *spec { + return &spec{ Type: IssueTypeNoPartition, Severity: IssueSeverityMajor, Description: "machine with no partition", diff --git a/cmd/metal-api/internal/issues/non-distinct-bmc-ip.go b/cmd/metal-api/internal/issues/non-distinct-bmc-ip.go index 29a35023d..1fd3a68d6 100644 --- a/cmd/metal-api/internal/issues/non-distinct-bmc-ip.go +++ b/cmd/metal-api/internal/issues/non-distinct-bmc-ip.go @@ -16,8 +16,8 @@ type ( } ) -func (i *IssueNonDistinctBMCIP) Spec() *issueSpec { - return &issueSpec{ +func (i *IssueNonDistinctBMCIP) Spec() *spec { + return &spec{ Type: IssueTypeNonDistinctBMCIP, Description: "BMC IP address is not distinct", RefURL: "https://docs.metal-stack.io/stable/installation/troubleshoot/#bmc-no-distinct-ip", diff --git a/cmd/metal-api/internal/issues/types.go b/cmd/metal-api/internal/issues/types.go index 6362c4352..29f9b133d 100644 --- a/cmd/metal-api/internal/issues/types.go +++ b/cmd/metal-api/internal/issues/types.go @@ -24,7 +24,7 @@ func AllIssueTypes() []IssueType { } } -func NewIssueFromType(t IssueType) (issueImpl, error) { +func NewIssueFromType(t IssueType) (issue, error) { switch t { case IssueTypeNoPartition: return &IssueNoPartition{}, nil diff --git a/cmd/metal-api/internal/metal/provisioning.go b/cmd/metal-api/internal/metal/provisioning.go index 12503d7f5..207ba286a 100644 --- a/cmd/metal-api/internal/metal/provisioning.go +++ b/cmd/metal-api/internal/metal/provisioning.go @@ -48,8 +48,8 @@ var ( type ProvisioningEvents []ProvisioningEvent // Is return true if given event is equal to specific EventType -func (p ProvisioningEventType) Is(event string) bool { - return string(p) == event +func (p ProvisioningEventType) Is(event ProvisioningEventType) bool { + return p == event } // TrimEvents trim the events to maxCount diff --git a/cmd/metal-api/internal/metal/provisioning_test.go b/cmd/metal-api/internal/metal/provisioning_test.go index ac8b83ba6..df76495b8 100644 --- a/cmd/metal-api/internal/metal/provisioning_test.go +++ b/cmd/metal-api/internal/metal/provisioning_test.go @@ -8,7 +8,7 @@ import ( func TestProvisioningEventType_Is(t *testing.T) { tests := []struct { name string - event string + event ProvisioningEventType p ProvisioningEventType want bool }{ diff --git a/cmd/metal-api/internal/service/machine-service.go b/cmd/metal-api/internal/service/machine-service.go index 3f92935ce..b66f0c706 100644 --- a/cmd/metal-api/internal/service/machine-service.go +++ b/cmd/metal-api/internal/service/machine-service.go @@ -593,7 +593,7 @@ func (r *machineResource) issues(request *restful.Request, response *restful.Res return } - issues, err := issues.FindIssues(&issues.IssueConfig{ + machinesWithIssues, err := issues.FindIssues(&issues.IssueConfig{ Machines: ms, EventContainers: ecs, Severity: severity, @@ -607,7 +607,7 @@ func (r *machineResource) issues(request *restful.Request, response *restful.Res } var issueResponse []*v1.MachineIssueResponse - for _, machineWithIssues := range issues { + for _, machineWithIssues := range machinesWithIssues.ToList() { machineWithIssues := machineWithIssues entry := &v1.MachineIssueResponse{ @@ -1939,7 +1939,7 @@ func evaluateMachineLiveliness(ds *datastore.RethinkStore, m metal.Machine) (met provisioningEvents, err := ds.FindProvisioningEventContainer(m.ID) if err != nil { // we have no provisioning events... we cannot tell - return metal.MachineLivelinessUnknown, fmt.Errorf("no provisioningEvents found for ID: %s", m.ID) + return metal.MachineLivelinessUnknown, fmt.Errorf("no provisioning event container found for machine: %s", m.ID) } old := *provisioningEvents @@ -1956,6 +1956,7 @@ func evaluateMachineLiveliness(ds *datastore.RethinkStore, m metal.Machine) (met } else { provisioningEvents.Liveliness = metal.MachineLivelinessAlive } + err = ds.UpdateProvisioningEventContainer(&old, provisioningEvents) if err != nil { return provisioningEvents.Liveliness, err @@ -2013,7 +2014,6 @@ func ResurrectMachines(ctx context.Context, ds *datastore.RethinkStore, publishe } continue } - } logger.Info("finished machine resurrection") @@ -2238,27 +2238,6 @@ func publishMachineCmd(logger *zap.SugaredLogger, m *metal.Machine, publisher bu return nil } -func machineHasIssues(m *v1.MachineResponse) bool { - if m.Partition == nil { - return true - } - if !metal.MachineLivelinessAlive.Is(m.Liveliness) { - return true - } - if m.Allocation == nil && len(m.RecentProvisioningEvents.Events) > 0 && metal.ProvisioningEventPhonedHome.Is(m.RecentProvisioningEvents.Events[0].Event) { - // not allocated, but phones home - return true - } - if m.RecentProvisioningEvents.CrashLoop || m.RecentProvisioningEvents.FailedMachineReclaim { - // Machines in crash loop but in "Waiting" state are considered available - if len(m.RecentProvisioningEvents.Events) > 0 && !metal.ProvisioningEventWaiting.Is(m.RecentProvisioningEvents.Events[0].Event) { - return true - } - } - - return false -} - func makeMachineResponse(m *metal.Machine, ds *datastore.RethinkStore) (*v1.MachineResponse, error) { s, p, i, ec, err := findMachineReferencedEntities(m, ds) if err != nil { diff --git a/cmd/metal-api/internal/service/partition-service.go b/cmd/metal-api/internal/service/partition-service.go index 4075a119e..25936fdc9 100644 --- a/cmd/metal-api/internal/service/partition-service.go +++ b/cmd/metal-api/internal/service/partition-service.go @@ -2,11 +2,14 @@ package service import ( "errors" + "fmt" "net/http" "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" + "github.com/metal-stack/metal-api/cmd/metal-api/internal/issues" "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" "github.com/metal-stack/metal-lib/auditing" + "github.com/metal-stack/metal-lib/pkg/pointer" "go.uber.org/zap" v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" @@ -97,18 +100,6 @@ func (r *partitionResource) webService() *restful.WebService { Returns(http.StatusConflict, "Conflict", httperrors.HTTPErrorResponse{}). DefaultReturns("Error", httperrors.HTTPErrorResponse{})) - // Deprecated, can be removed in the future - ws.Route(ws.GET("/capacity"). - To(r.partitionCapacityCompat). - Operation("partitionCapacityCompat"). - Doc("get partition capacity"). - Metadata(restfulspec.KeyOpenAPITags, tags). - Metadata(auditing.Exclude, true). - Writes([]v1.PartitionCapacity{}). - Returns(http.StatusOK, "OK", []v1.PartitionCapacity{}). - DefaultReturns("Error", httperrors.HTTPErrorResponse{}). - Deprecate()) - ws.Route(ws.POST("/capacity"). To(r.partitionCapacity). Operation("partitionCapacity"). @@ -314,16 +305,6 @@ func (r *partitionResource) updatePartition(request *restful.Request, response * r.send(request, response, http.StatusOK, v1.NewPartitionResponse(&newPartition)) } -func (r *partitionResource) partitionCapacityCompat(request *restful.Request, response *restful.Response) { - partitionCapacities, err := r.calcPartitionCapacity(nil) - if err != nil { - r.sendError(request, response, httperrors.BadRequest(err)) - return - } - - r.send(request, response, http.StatusOK, partitionCapacities) -} - func (r *partitionResource) partitionCapacity(request *restful.Request, response *restful.Response) { var requestPayload v1.PartitionCapacityRequest err := request.ReadEntity(&requestPayload) @@ -342,15 +323,13 @@ func (r *partitionResource) partitionCapacity(request *restful.Request, response } func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityRequest) ([]v1.PartitionCapacity, error) { - // FIXME bad workaround to be able to run make spec - if r.ds == nil { - return nil, nil - } - var ( - ps metal.Partitions - ms metal.Machines - err error + ps metal.Partitions + ms metal.Machines + + pcs = map[string]*v1.PartitionCapacity{} + + machineQuery = datastore.MachineSearchQuery{} ) if pcr != nil && pcr.ID != nil { @@ -359,96 +338,118 @@ func (r *partitionResource) calcPartitionCapacity(pcr *v1.PartitionCapacityReque return nil, err } ps = metal.Partitions{*p} + + machineQuery.PartitionID = pcr.ID } else { + var err error ps, err = r.ds.ListPartitions() if err != nil { return nil, err } } - msq := datastore.MachineSearchQuery{} if pcr != nil && pcr.Size != nil { - msq.SizeID = pcr.Size + machineQuery.SizeID = pcr.Size } - err = r.ds.SearchMachines(&msq, &ms) + err := r.ds.SearchMachines(&machineQuery, &ms) if err != nil { return nil, err } - machines, err := makeMachineResponseList(ms, r.ds) + + ecs, err := r.ds.ListProvisioningEventContainers() if err != nil { - return nil, err + return nil, fmt.Errorf("unable to fetch provisioning event containers: %w", err) + } + + machinesWithIssues, err := issues.FindIssues(&issues.IssueConfig{ + Machines: ms, + EventContainers: ecs, + Only: []issues.IssueType{ + issues.IssueTypeLivelinessDead, + issues.IssueTypeLivelinessUnknown, + issues.IssueTypeLivelinessNotAvailable, + issues.IssueTypeFailedMachineReclaim, + issues.IssueTypeCrashLoop, + }, + }) + if err != nil { + return nil, fmt.Errorf("unable to calculate machine issues: %w", err) } - partitionCapacities := []v1.PartitionCapacity{} - for _, p := range ps { - p := p - capacities := make(map[string]*v1.ServerCapacity) - for _, m := range machines { - m := m - if m.Partition == nil { - continue - } - if m.Partition.ID != p.ID { - continue - } + partitionsByID := ps.ByID() + ecsByID := ecs.ByID() - size := metal.UnknownSize.ID - if m.Size != nil { - size = m.Size.ID - } + for _, m := range ms { + m := m - available := false - if m.State.Value == string(metal.AvailableState) && len(m.RecentProvisioningEvents.Events) > 0 { - events := m.RecentProvisioningEvents.Events - if metal.ProvisioningEventWaiting.Is(events[0].Event) && metal.ProvisioningEventAlive.Is(m.Liveliness) { - available = true - } - } + ec, ok := ecsByID[m.ID] + if !ok { + continue + } - cap, ok := capacities[size] - if !ok { - cap = &v1.ServerCapacity{Size: size} - capacities[size] = cap - } + p, ok := partitionsByID[m.PartitionID] + if !ok { + continue + } - if m.Allocation != nil { - cap.Allocated++ - } else if machineHasIssues(m) { - cap.Faulty++ - cap.FaultyMachines = append(cap.FaultyMachines, m.ID) - } else if available { - cap.Free++ - } else { - cap.Other++ - cap.OtherMachines = append(cap.OtherMachines, m.ID) + pc, ok := pcs[m.PartitionID] + if !ok { + pc = &v1.PartitionCapacity{ + Common: v1.Common{ + Identifiable: v1.Identifiable{ + ID: p.ID, + }, + Describable: v1.Describable{ + Name: &p.Name, + Description: &p.Description, + }, + }, + ServerCapacities: v1.ServerCapacities{}, } + } + pcs[m.PartitionID] = pc - cap.Total++ + size := metal.UnknownSize.ID + if m.SizeID != "" { + size = m.SizeID } - sc := []v1.ServerCapacity{} - for i := range capacities { - if capacities[i] == nil { - continue + + cap := pc.ServerCapacities.FindBySize(size) + if cap == nil { + cap = &v1.ServerCapacity{ + Size: size, } - sc = append(sc, *capacities[i]) + pc.ServerCapacities = append(pc.ServerCapacities, cap) } - pc := v1.PartitionCapacity{ - Common: v1.Common{ - Identifiable: v1.Identifiable{ - ID: p.ID, - }, - Describable: v1.Describable{ - Name: &p.Name, - Description: &p.Description, - }, - }, - ServerCapacities: sc, + cap.Total++ + + if m.Allocation != nil { + cap.Allocated++ + continue + } + + if _, ok := machinesWithIssues[&m]; ok { + cap.Faulty++ + cap.FaultyMachines = append(cap.FaultyMachines, m.ID) + continue + } + + if m.State.Value == metal.AvailableState && metal.ProvisioningEventWaiting.Is(pointer.FirstOrZero(ec.Events).Event) { + cap.Free++ + continue } - partitionCapacities = append(partitionCapacities, pc) + cap.Other++ + cap.OtherMachines = append(cap.OtherMachines, m.ID) + } + + res := []v1.PartitionCapacity{} + for _, pc := range pcs { + pc := pc + res = append(res, *pc) } - return partitionCapacities, err + return res, nil } diff --git a/cmd/metal-api/internal/service/partition-service_test.go b/cmd/metal-api/internal/service/partition-service_test.go index 503c5dce7..0606015ab 100644 --- a/cmd/metal-api/internal/service/partition-service_test.go +++ b/cmd/metal-api/internal/service/partition-service_test.go @@ -10,9 +10,11 @@ import ( "github.com/stretchr/testify/assert" "go.uber.org/zap/zaptest" + r "gopkg.in/rethinkdb/rethinkdb-go.v6" restful "github.com/emicklei/go-restful/v3" "github.com/metal-stack/metal-api/cmd/metal-api/internal/datastore" + "github.com/metal-stack/metal-api/cmd/metal-api/internal/metal" v1 "github.com/metal-stack/metal-api/cmd/metal-api/internal/service/v1" "github.com/metal-stack/metal-api/cmd/metal-api/internal/testdata" "github.com/metal-stack/metal-lib/httperrors" @@ -244,13 +246,28 @@ func TestUpdatePartition(t *testing.T) { func TestPartitionCapacity(t *testing.T) { ds, mock := datastore.InitMockDB(t) + + ecs := []metal.ProvisioningEventContainer{} + for _, m := range testdata.TestMachines { + m := m + ecs = append(ecs, metal.ProvisioningEventContainer{ + Base: m.Base, + }) + } + mock.On(r.DB("mockdb").Table("event")).Return(ecs, nil) + testdata.InitMockDBData(mock) log := zaptest.NewLogger(t).Sugar() service := NewPartition(log, ds, &nopTopicCreater{}) container := restful.NewContainer().Add(service) - req := httptest.NewRequest("GET", "/v1/partition/capacity", nil) + pcRequest := &v1.PartitionCapacityRequest{} + js, err := json.Marshal(pcRequest) + require.NoError(t, err) + body := bytes.NewBuffer(js) + + req := httptest.NewRequest("POST", "/v1/partition/capacity", body) req.Header.Add("Content-Type", "application/json") container = injectAdmin(log, container, req) w := httptest.NewRecorder() @@ -260,12 +277,13 @@ func TestPartitionCapacity(t *testing.T) { defer resp.Body.Close() require.Equal(t, http.StatusOK, resp.StatusCode, w.Body.String()) var result []v1.PartitionCapacity - err := json.NewDecoder(resp.Body).Decode(&result) + err = json.NewDecoder(resp.Body).Decode(&result) require.NoError(t, err) + require.Len(t, result, 1) require.Equal(t, testdata.Partition1.ID, result[0].ID) require.NotNil(t, result[0].ServerCapacities) - require.Equal(t, 1, len(result[0].ServerCapacities)) + require.Len(t, result[0].ServerCapacities, 1) c := result[0].ServerCapacities[0] require.Equal(t, "1", c.Size) require.Equal(t, 5, c.Total) diff --git a/cmd/metal-api/internal/service/v1/partition.go b/cmd/metal-api/internal/service/v1/partition.go index 49f1bbff5..522075f31 100644 --- a/cmd/metal-api/internal/service/v1/partition.go +++ b/cmd/metal-api/internal/service/v1/partition.go @@ -39,9 +39,11 @@ type PartitionCapacityRequest struct { Size *string `json:"sizeid" description:"the size to filter for" optional:"true"` } +type ServerCapacities []*ServerCapacity + type PartitionCapacity struct { Common - ServerCapacities []ServerCapacity `json:"servers" description:"servers available in this partition"` + ServerCapacities ServerCapacities `json:"servers" description:"servers available in this partition"` } type ServerCapacity struct { @@ -85,3 +87,14 @@ func NewPartitionResponse(p *metal.Partition) *PartitionResponse { }, } } + +func (s ServerCapacities) FindBySize(size string) *ServerCapacity { + for _, sc := range s { + sc := sc + if sc.Size == size { + return sc + } + } + + return nil +} diff --git a/spec/metal-api.json b/spec/metal-api.json index 1b61ed960..a33c1f3ff 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -8048,37 +8048,6 @@ } }, "/v1/partition/capacity": { - "get": { - "consumes": [ - "application/json" - ], - "deprecated": true, - "operationId": "partitionCapacityCompat", - "produces": [ - "application/json" - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "items": { - "$ref": "#/definitions/v1.PartitionCapacity" - }, - "type": "array" - } - }, - "default": { - "description": "Error", - "schema": { - "$ref": "#/definitions/httperrors.HTTPErrorResponse" - } - } - }, - "summary": "get partition capacity", - "tags": [ - "Partition" - ] - }, "post": { "consumes": [ "application/json"