From 66cc986f5fd135159fecbfd1aed757f4f6ce0a15 Mon Sep 17 00:00:00 2001 From: Mike Peter Date: Thu, 12 Oct 2023 15:05:33 +0200 Subject: [PATCH] Size suggestion from machines (#469) --- .../internal/service/size-service.go | 49 +++++++++++++++++ .../internal/service/size-service_test.go | 48 +++++++++++++++++ cmd/metal-api/internal/service/v1/size.go | 4 ++ cmd/metal-api/internal/testdata/testdata.go | 16 ++++++ spec/metal-api.json | 53 +++++++++++++++++++ 5 files changed, 170 insertions(+) diff --git a/cmd/metal-api/internal/service/size-service.go b/cmd/metal-api/internal/service/size-service.go index 184fb4c05..ffff100e1 100644 --- a/cmd/metal-api/internal/service/size-service.go +++ b/cmd/metal-api/internal/service/size-service.go @@ -59,6 +59,16 @@ func (r *sizeResource) webService() *restful.WebService { Returns(http.StatusOK, "OK", []v1.SizeResponse{}). DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + ws.Route(ws.POST("/suggest"). + To(r.suggestSize). + Operation("suggest"). + Doc("from a given machine id returns the appropriate size"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Metadata(auditing.Exclude, true). + Reads(v1.SizeSuggestRequest{}). + Returns(http.StatusOK, "OK", []v1.SizeConstraint{}). + DefaultReturns("Error", httperrors.HTTPErrorResponse{})) + ws.Route(ws.DELETE("/{id}"). To(admin(r.deleteSize)). Operation("deleteSize"). @@ -114,6 +124,45 @@ func (r *sizeResource) findSize(request *restful.Request, response *restful.Resp r.send(request, response, http.StatusOK, v1.NewSizeResponse(s)) } +func (r *sizeResource) suggestSize(request *restful.Request, response *restful.Response) { + var requestPayload v1.SizeSuggestRequest + err := request.ReadEntity(&requestPayload) + if err != nil { + r.sendError(request, response, httperrors.BadRequest(err)) + return + } + + if requestPayload.MachineID == "" { + r.sendError(request, response, httperrors.BadRequest(fmt.Errorf("machineID must be given"))) + return + } + + m, err := r.ds.FindMachineByID(requestPayload.MachineID) + if err != nil { + r.sendError(request, response, defaultError(err)) + return + } + + r.send(request, response, http.StatusOK, []v1.SizeConstraint{ + { + Type: metal.CoreConstraint, + Min: uint64(m.Hardware.CPUCores), + Max: uint64(m.Hardware.CPUCores), + }, + { + Type: metal.MemoryConstraint, + Min: m.Hardware.Memory, + Max: m.Hardware.Memory, + }, + { + Type: metal.StorageConstraint, + Min: m.Hardware.DiskCapacity(), + Max: m.Hardware.DiskCapacity(), + }, + }) + +} + func (r *sizeResource) listSizes(request *restful.Request, response *restful.Response) { ss, err := r.ds.ListSizes() if err != nil { diff --git a/cmd/metal-api/internal/service/size-service_test.go b/cmd/metal-api/internal/service/size-service_test.go index b3d198feb..9e0a02345 100644 --- a/cmd/metal-api/internal/service/size-service_test.go +++ b/cmd/metal-api/internal/service/size-service_test.go @@ -12,6 +12,7 @@ import ( 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" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" @@ -70,6 +71,53 @@ func TestGetSize(t *testing.T) { require.Equal(t, len(testdata.Sz1.Constraints), len(result.SizeConstraints)) } +func TestSuggest(t *testing.T) { + ds, mock := datastore.InitMockDB(t) + testdata.InitMockDBData(mock) + + createRequest := v1.SizeSuggestRequest{ + MachineID: "1", + } + js, err := json.Marshal(createRequest) + require.NoError(t, err) + body := bytes.NewBuffer(js) + + sizeservice := NewSize(zaptest.NewLogger(t).Sugar(), ds) + container := restful.NewContainer().Add(sizeservice) + req := httptest.NewRequest("POST", "/v1/size/suggest", body) + req.Header.Add("Content-Type", "application/json") + w := httptest.NewRecorder() + container.ServeHTTP(w, req) + + resp := w.Result() + defer resp.Body.Close() + require.Equal(t, http.StatusOK, resp.StatusCode, w.Body.String()) + var result []v1.SizeConstraint + err = json.NewDecoder(resp.Body).Decode(&result) + require.NoError(t, err) + + require.Len(t, result, 3) + + assert.Contains(t, result, v1.SizeConstraint{ + Type: metal.MemoryConstraint, + Min: 1 << 30, + Max: 1 << 30, + }) + + assert.Contains(t, result, v1.SizeConstraint{ + Type: metal.CoreConstraint, + Min: 8, + Max: 8, + }) + + assert.Contains(t, result, v1.SizeConstraint{ + Type: metal.StorageConstraint, + Min: 3000, + Max: 3000, + }) + +} + func TestGetSizeNotFound(t *testing.T) { ds, mock := datastore.InitMockDB(t) testdata.InitMockDBData(mock) diff --git a/cmd/metal-api/internal/service/v1/size.go b/cmd/metal-api/internal/service/v1/size.go index db31e5271..9c875d778 100644 --- a/cmd/metal-api/internal/service/v1/size.go +++ b/cmd/metal-api/internal/service/v1/size.go @@ -26,6 +26,10 @@ type SizeResponse struct { Timestamps } +type SizeSuggestRequest struct { + MachineID string `json:"machineID" description:"machineID to retrieve size suggestion for"` +} + type SizeConstraintMatchingLog struct { Constraint SizeConstraint `json:"constraint" description:"the size constraint to which this log relates to"` Match bool `json:"match" description:"indicates whether the constraint matched or not"` diff --git a/cmd/metal-api/internal/testdata/testdata.go b/cmd/metal-api/internal/testdata/testdata.go index 41db4dcc2..3ca14f7d0 100644 --- a/cmd/metal-api/internal/testdata/testdata.go +++ b/cmd/metal-api/internal/testdata/testdata.go @@ -43,6 +43,22 @@ var ( }, }, }, + Hardware: metal.MachineHardware{ + CPUCores: 8, + Memory: 1 << 30, + Disks: []metal.BlockDevice{ + { + Size: 1000, + }, + { + Size: 1000, + }, + { + Size: 1000, + }, + }, + }, + IPMI: IPMI1, Tags: []string{"1"}, } diff --git a/spec/metal-api.json b/spec/metal-api.json index 624aec38a..3ff424fe5 100644 --- a/spec/metal-api.json +++ b/spec/metal-api.json @@ -4509,6 +4509,17 @@ "id" ] }, + "v1.SizeSuggestRequest": { + "properties": { + "machineID": { + "description": "machineID to retrieve size suggestion for", + "type": "string" + } + }, + "required": [ + "machineID" + ] + }, "v1.SizeUpdateRequest": { "properties": { "constraints": { @@ -8785,6 +8796,48 @@ ] } }, + "/v1/size/suggest": { + "post": { + "consumes": [ + "application/json" + ], + "operationId": "suggest", + "parameters": [ + { + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1.SizeSuggestRequest" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/v1.SizeConstraint" + }, + "type": "array" + } + }, + "default": { + "description": "Error", + "schema": { + "$ref": "#/definitions/httperrors.HTTPErrorResponse" + } + } + }, + "summary": "from a given machine id returns the appropriate size", + "tags": [ + "size" + ] + } + }, "/v1/size/{id}": { "delete": { "consumes": [