From a9eb1baad0e8e895c696edd547dd2b35c2ab062b Mon Sep 17 00:00:00 2001 From: Cosmin Tupangiu Date: Wed, 27 Nov 2024 11:03:00 +0100 Subject: [PATCH] multi-source: Implement logic on planner This commit implements the multi-source model in the planner. Signed-off-by: Cosmin Tupangiu --- api/v1alpha1/agent/openapi.yaml | 31 ++- api/v1alpha1/agent/spec.gen.go | 43 +++-- api/v1alpha1/agent/types.gen.go | 7 +- api/v1alpha1/common.go | 17 -- api/v1alpha1/openapi.yaml | 98 +++++++--- api/v1alpha1/spec.gen.go | 60 +++--- api/v1alpha1/types.gen.go | 10 +- internal/api/client/agent/client.gen.go | 24 +++ internal/api/client/client.gen.go | 197 +++++++++---------- internal/api/server/agent/server.gen.go | 27 +++ internal/api/server/server.gen.go | 195 +++++++++++-------- internal/service/agent.go | 32 +++- internal/service/agent/agent_suite_test.go | 13 ++ internal/service/agent/handler.go | 91 ++++++++- internal/service/agent/handler_test.go | 213 +++++++++++++++++++++ internal/service/agent_test.go | 105 ++++++++++ internal/service/service_suite_test.go | 13 ++ internal/service/source.go | 31 ++- internal/service/source_test.go | 147 ++++++++++++++ internal/store/agent.go | 9 +- internal/store/agent_test.go | 19 +- internal/store/model/agent.go | 6 + internal/store/model/source.go | 42 ++-- internal/store/source.go | 32 ++-- internal/store/source_test.go | 36 ++-- internal/store/store_test.go | 8 +- 26 files changed, 1119 insertions(+), 387 deletions(-) create mode 100644 internal/service/agent/agent_suite_test.go create mode 100644 internal/service/agent/handler_test.go create mode 100644 internal/service/agent_test.go create mode 100644 internal/service/service_suite_test.go create mode 100644 internal/service/source_test.go diff --git a/api/v1alpha1/agent/openapi.yaml b/api/v1alpha1/agent/openapi.yaml index f8254b3..767963c 100644 --- a/api/v1alpha1/agent/openapi.yaml +++ b/api/v1alpha1/agent/openapi.yaml @@ -53,6 +53,12 @@ paths: application/json: schema: $ref: '../openapi.yaml#/components/schemas/Error' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '../openapi.yaml#/components/schemas/Error' /api/v1/agents/{id}/status: put: tags: @@ -77,6 +83,12 @@ paths: description: OK "201": description: OK + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '../openapi.yaml#/components/schemas/Error' "401": description: Unauthorized content: @@ -89,6 +101,12 @@ paths: application/json: schema: $ref: '../openapi.yaml#/components/schemas/Error' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '../openapi.yaml#/components/schemas/Error' /health: get: tags: @@ -104,18 +122,15 @@ components: SourceStatusUpdate: type: object properties: - status: - type: string - statusInfo: + agentId: type: string + format: uuid inventory: $ref: '../openapi.yaml#/components/schemas/Inventory' - credentialUrl: - type: string required: - - status - - statusInfo - - credentialUrl + - inventory + - agentId + AgentStatusUpdate: type: object properties: diff --git a/api/v1alpha1/agent/spec.gen.go b/api/v1alpha1/agent/spec.gen.go index 82a57c1..faa848f 100644 --- a/api/v1alpha1/agent/spec.gen.go +++ b/api/v1alpha1/agent/spec.gen.go @@ -19,27 +19,28 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v1alpha1/agent/types.gen.go b/api/v1alpha1/agent/types.gen.go index c9008b6..7d20332 100644 --- a/api/v1alpha1/agent/types.gen.go +++ b/api/v1alpha1/agent/types.gen.go @@ -5,6 +5,7 @@ package v1alpha1 import ( externalRef0 "github.com/kubev2v/migration-planner/api/v1alpha1" + openapi_types "github.com/oapi-codegen/runtime/types" ) // AgentStatusUpdate defines model for AgentStatusUpdate. @@ -18,10 +19,8 @@ type AgentStatusUpdate struct { // SourceStatusUpdate defines model for SourceStatusUpdate. type SourceStatusUpdate struct { - CredentialUrl string `json:"credentialUrl"` - Inventory *externalRef0.Inventory `json:"inventory,omitempty"` - Status string `json:"status"` - StatusInfo string `json:"statusInfo"` + AgentId openapi_types.UUID `json:"agentId"` + Inventory externalRef0.Inventory `json:"inventory"` } // UpdateAgentStatusJSONRequestBody defines body for UpdateAgentStatus for application/json ContentType. diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go index cd7f0f1..dd2d2de 100644 --- a/api/v1alpha1/common.go +++ b/api/v1alpha1/common.go @@ -1,22 +1,5 @@ package v1alpha1 -func StringToSourceStatus(s string) SourceStatus { - switch s { - case string(SourceStatusError): - return SourceStatusError - case string(SourceStatusGatheringInitialInventory): - return SourceStatusGatheringInitialInventory - case string(SourceStatusUpToDate): - return SourceStatusUpToDate - case string(SourceStatusWaitingForCredentials): - return SourceStatusWaitingForCredentials - case string(SourceStatusNotConnected): - return SourceStatusNotConnected - default: - return SourceStatusNotConnected - } -} - func StringToAgentStatus(s string) AgentStatus { switch s { case string(AgentStatusError): diff --git a/api/v1alpha1/openapi.yaml b/api/v1alpha1/openapi.yaml index 68df85c..2208cac 100644 --- a/api/v1alpha1/openapi.yaml +++ b/api/v1alpha1/openapi.yaml @@ -27,36 +27,6 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' - post: - tags: - - source - description: create a source - operationId: createSource - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/SourceCreate' - required: true - responses: - "201": - description: Created - content: - application/json: - schema: - $ref: '#/components/schemas/Source' - "400": - description: Bad Request - content: - application/json: - schema: - $ref: '#/components/schemas/Error' - "401": - description: Unauthorized - content: - application/json: - schema: - $ref: '#/components/schemas/Error' delete: tags: - source @@ -243,6 +213,58 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /api/v1/agents/{id}: + delete: + tags: + - agent + description: delete an agent + operationId: deleteAgent + parameters: + - name: id + in: path + description: ID of the agent + required: true + schema: + type: string + format: uuid + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/Agent" + "400": + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + "401": + description: Unauthorized + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + "404": + description: NotFound + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /health: get: tags: @@ -280,6 +302,10 @@ components: format: date-time sshKey: type: string + agents: + type: array + items: + $ref: '#/components/schemas/SourceAgentItem' required: - id - name @@ -303,6 +329,18 @@ components: items: $ref: '#/components/schemas/Source' + SourceAgentItem: + type: object + properties: + id: + type: string + format: uuid + associated: + type: boolean + required: + - id + - associated + Error: properties: message: diff --git a/api/v1alpha1/spec.gen.go b/api/v1alpha1/spec.gen.go index e1d8fd0..8282b3f 100644 --- a/api/v1alpha1/spec.gen.go +++ b/api/v1alpha1/spec.gen.go @@ -18,36 +18,36 @@ import ( // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v1alpha1/types.gen.go b/api/v1alpha1/types.gen.go index 591d59b..0f98afa 100644 --- a/api/v1alpha1/types.gen.go +++ b/api/v1alpha1/types.gen.go @@ -96,6 +96,7 @@ type MigrationIssues = []struct { // Source defines model for Source. type Source struct { + Agents *[]SourceAgentItem `json:"agents,omitempty"` CreatedAt time.Time `json:"createdAt"` CredentialUrl *string `json:"credentialUrl,omitempty"` Id openapi_types.UUID `json:"id"` @@ -110,6 +111,12 @@ type Source struct { // SourceStatus defines model for Source.Status. type SourceStatus string +// SourceAgentItem defines model for SourceAgentItem. +type SourceAgentItem struct { + Associated bool `json:"associated"` + Id openapi_types.UUID `json:"id"` +} + // SourceCreate defines model for SourceCreate. type SourceCreate struct { Name string `json:"name"` @@ -163,6 +170,3 @@ type VMs struct { TotalMigratable int `json:"totalMigratable"` TotalMigratableWithWarnings *int `json:"totalMigratableWithWarnings,omitempty"` } - -// CreateSourceJSONRequestBody defines body for CreateSource for application/json ContentType. -type CreateSourceJSONRequestBody = SourceCreate diff --git a/internal/api/client/agent/client.gen.go b/internal/api/client/agent/client.gen.go index 10ce2eb..eccc1d2 100644 --- a/internal/api/client/agent/client.gen.go +++ b/internal/api/client/agent/client.gen.go @@ -347,8 +347,10 @@ type ClientWithResponsesInterface interface { type UpdateAgentStatusResponse struct { Body []byte HTTPResponse *http.Response + JSON400 *externalRef0.Error JSON401 *externalRef0.Error JSON410 *externalRef0.Error + JSON500 *externalRef0.Error } // Status returns HTTPResponse.Status @@ -374,6 +376,7 @@ type ReplaceSourceStatusResponse struct { JSON400 *externalRef0.Error JSON401 *externalRef0.Error JSON404 *externalRef0.Error + JSON500 *externalRef0.Error } // Status returns HTTPResponse.Status @@ -470,6 +473,13 @@ func ParseUpdateAgentStatusResponse(rsp *http.Response) (*UpdateAgentStatusRespo } switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest externalRef0.Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest externalRef0.Error if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -484,6 +494,13 @@ func ParseUpdateAgentStatusResponse(rsp *http.Response) (*UpdateAgentStatusRespo } response.JSON410 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest externalRef0.Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil @@ -531,6 +548,13 @@ func ParseReplaceSourceStatusResponse(rsp *http.Response) (*ReplaceSourceStatusR } response.JSON404 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest externalRef0.Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil diff --git a/internal/api/client/client.gen.go b/internal/api/client/client.gen.go index 5196e6b..00ccf73 100644 --- a/internal/api/client/client.gen.go +++ b/internal/api/client/client.gen.go @@ -4,7 +4,6 @@ package client import ( - "bytes" "context" "encoding/json" "fmt" @@ -94,17 +93,15 @@ type ClientInterface interface { // ListAgents request ListAgents(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteAgent request + DeleteAgent(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) + // DeleteSources request DeleteSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) // ListSources request ListSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) - // CreateSourceWithBody request with any body - CreateSourceWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) - - CreateSource(ctx context.Context, body CreateSourceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // DeleteSource request DeleteSource(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -133,20 +130,8 @@ func (c *Client) ListAgents(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) DeleteSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewDeleteSourcesRequest(c.Server) - if err != nil { - return nil, err - } - req = req.WithContext(ctx) - if err := c.applyEditors(ctx, req, reqEditors); err != nil { - return nil, err - } - return c.Client.Do(req) -} - -func (c *Client) ListSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewListSourcesRequest(c.Server) +func (c *Client) DeleteAgent(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteAgentRequest(c.Server, id) if err != nil { return nil, err } @@ -157,8 +142,8 @@ func (c *Client) ListSources(ctx context.Context, reqEditors ...RequestEditorFn) return c.Client.Do(req) } -func (c *Client) CreateSourceWithBody(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateSourceRequestWithBody(c.Server, contentType, body) +func (c *Client) DeleteSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteSourcesRequest(c.Server) if err != nil { return nil, err } @@ -169,8 +154,8 @@ func (c *Client) CreateSourceWithBody(ctx context.Context, contentType string, b return c.Client.Do(req) } -func (c *Client) CreateSource(ctx context.Context, body CreateSourceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewCreateSourceRequest(c.Server, body) +func (c *Client) ListSources(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewListSourcesRequest(c.Server) if err != nil { return nil, err } @@ -268,16 +253,23 @@ func NewListAgentsRequest(server string) (*http.Request, error) { return req, nil } -// NewDeleteSourcesRequest generates requests for DeleteSources -func NewDeleteSourcesRequest(server string) (*http.Request, error) { +// NewDeleteAgentRequest generates requests for DeleteAgent +func NewDeleteAgentRequest(server string, id openapi_types.UUID) (*http.Request, error) { var err error + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "id", runtime.ParamLocationPath, id) + if err != nil { + return nil, err + } + serverURL, err := url.Parse(server) if err != nil { return nil, err } - operationPath := fmt.Sprintf("/api/v1/sources") + operationPath := fmt.Sprintf("/api/v1/agents/%s", pathParam0) if operationPath[0] == '/' { operationPath = "." + operationPath } @@ -295,8 +287,8 @@ func NewDeleteSourcesRequest(server string) (*http.Request, error) { return req, nil } -// NewListSourcesRequest generates requests for ListSources -func NewListSourcesRequest(server string) (*http.Request, error) { +// NewDeleteSourcesRequest generates requests for DeleteSources +func NewDeleteSourcesRequest(server string) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -314,7 +306,7 @@ func NewListSourcesRequest(server string) (*http.Request, error) { return nil, err } - req, err := http.NewRequest("GET", queryURL.String(), nil) + req, err := http.NewRequest("DELETE", queryURL.String(), nil) if err != nil { return nil, err } @@ -322,19 +314,8 @@ func NewListSourcesRequest(server string) (*http.Request, error) { return req, nil } -// NewCreateSourceRequest calls the generic CreateSource builder with application/json body -func NewCreateSourceRequest(server string, body CreateSourceJSONRequestBody) (*http.Request, error) { - var bodyReader io.Reader - buf, err := json.Marshal(body) - if err != nil { - return nil, err - } - bodyReader = bytes.NewReader(buf) - return NewCreateSourceRequestWithBody(server, "application/json", bodyReader) -} - -// NewCreateSourceRequestWithBody generates requests for CreateSource with any type of body -func NewCreateSourceRequestWithBody(server string, contentType string, body io.Reader) (*http.Request, error) { +// NewListSourcesRequest generates requests for ListSources +func NewListSourcesRequest(server string) (*http.Request, error) { var err error serverURL, err := url.Parse(server) @@ -352,13 +333,11 @@ func NewCreateSourceRequestWithBody(server string, contentType string, body io.R return nil, err } - req, err := http.NewRequest("POST", queryURL.String(), body) + req, err := http.NewRequest("GET", queryURL.String(), nil) if err != nil { return nil, err } - req.Header.Add("Content-Type", contentType) - return req, nil } @@ -571,17 +550,15 @@ type ClientWithResponsesInterface interface { // ListAgentsWithResponse request ListAgentsWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListAgentsResponse, error) + // DeleteAgentWithResponse request + DeleteAgentWithResponse(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*DeleteAgentResponse, error) + // DeleteSourcesWithResponse request DeleteSourcesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*DeleteSourcesResponse, error) // ListSourcesWithResponse request ListSourcesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*ListSourcesResponse, error) - // CreateSourceWithBodyWithResponse request with any body - CreateSourceWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateSourceResponse, error) - - CreateSourceWithResponse(ctx context.Context, body CreateSourceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateSourceResponse, error) - // DeleteSourceWithResponse request DeleteSourceWithResponse(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*DeleteSourceResponse, error) @@ -603,6 +580,7 @@ type ListAgentsResponse struct { HTTPResponse *http.Response JSON200 *AgentList JSON401 *Error + JSON500 *Error } // Status returns HTTPResponse.Status @@ -621,15 +599,18 @@ func (r ListAgentsResponse) StatusCode() int { return 0 } -type DeleteSourcesResponse struct { +type DeleteAgentResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *Status + JSON200 *Agent + JSON400 *Error JSON401 *Error + JSON404 *Error + JSON500 *Error } // Status returns HTTPResponse.Status -func (r DeleteSourcesResponse) Status() string { +func (r DeleteAgentResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -637,22 +618,22 @@ func (r DeleteSourcesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r DeleteSourcesResponse) StatusCode() int { +func (r DeleteAgentResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type ListSourcesResponse struct { +type DeleteSourcesResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *SourceList + JSON200 *Status JSON401 *Error } // Status returns HTTPResponse.Status -func (r ListSourcesResponse) Status() string { +func (r DeleteSourcesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -660,23 +641,22 @@ func (r ListSourcesResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r ListSourcesResponse) StatusCode() int { +func (r DeleteSourcesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } return 0 } -type CreateSourceResponse struct { +type ListSourcesResponse struct { Body []byte HTTPResponse *http.Response - JSON201 *Source - JSON400 *Error + JSON200 *SourceList JSON401 *Error } // Status returns HTTPResponse.Status -func (r CreateSourceResponse) Status() string { +func (r ListSourcesResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -684,7 +664,7 @@ func (r CreateSourceResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r CreateSourceResponse) StatusCode() int { +func (r ListSourcesResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -817,6 +797,15 @@ func (c *ClientWithResponses) ListAgentsWithResponse(ctx context.Context, reqEdi return ParseListAgentsResponse(rsp) } +// DeleteAgentWithResponse request returning *DeleteAgentResponse +func (c *ClientWithResponses) DeleteAgentWithResponse(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*DeleteAgentResponse, error) { + rsp, err := c.DeleteAgent(ctx, id, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteAgentResponse(rsp) +} + // DeleteSourcesWithResponse request returning *DeleteSourcesResponse func (c *ClientWithResponses) DeleteSourcesWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*DeleteSourcesResponse, error) { rsp, err := c.DeleteSources(ctx, reqEditors...) @@ -835,23 +824,6 @@ func (c *ClientWithResponses) ListSourcesWithResponse(ctx context.Context, reqEd return ParseListSourcesResponse(rsp) } -// CreateSourceWithBodyWithResponse request with arbitrary body returning *CreateSourceResponse -func (c *ClientWithResponses) CreateSourceWithBodyWithResponse(ctx context.Context, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateSourceResponse, error) { - rsp, err := c.CreateSourceWithBody(ctx, contentType, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateSourceResponse(rsp) -} - -func (c *ClientWithResponses) CreateSourceWithResponse(ctx context.Context, body CreateSourceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateSourceResponse, error) { - rsp, err := c.CreateSource(ctx, body, reqEditors...) - if err != nil { - return nil, err - } - return ParseCreateSourceResponse(rsp) -} - // DeleteSourceWithResponse request returning *DeleteSourceResponse func (c *ClientWithResponses) DeleteSourceWithResponse(ctx context.Context, id openapi_types.UUID, reqEditors ...RequestEditorFn) (*DeleteSourceResponse, error) { rsp, err := c.DeleteSource(ctx, id, reqEditors...) @@ -925,32 +897,46 @@ func ParseListAgentsResponse(rsp *http.Response) (*ListAgentsResponse, error) { } response.JSON401 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil } -// ParseDeleteSourcesResponse parses an HTTP response from a DeleteSourcesWithResponse call -func ParseDeleteSourcesResponse(rsp *http.Response) (*DeleteSourcesResponse, error) { +// ParseDeleteAgentResponse parses an HTTP response from a DeleteAgentWithResponse call +func ParseDeleteAgentResponse(rsp *http.Response) (*DeleteAgentResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &DeleteSourcesResponse{ + response := &DeleteAgentResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest Status + var dest Agent if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } response.JSON200 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest Error if err := json.Unmarshal(bodyBytes, &dest); err != nil { @@ -958,27 +944,41 @@ func ParseDeleteSourcesResponse(rsp *http.Response) (*DeleteSourcesResponse, err } response.JSON401 = &dest + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 404: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON404 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 500: + var dest Error + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON500 = &dest + } return response, nil } -// ParseListSourcesResponse parses an HTTP response from a ListSourcesWithResponse call -func ParseListSourcesResponse(rsp *http.Response) (*ListSourcesResponse, error) { +// ParseDeleteSourcesResponse parses an HTTP response from a DeleteSourcesWithResponse call +func ParseDeleteSourcesResponse(rsp *http.Response) (*DeleteSourcesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &ListSourcesResponse{ + response := &DeleteSourcesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest SourceList + var dest Status if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } @@ -996,33 +996,26 @@ func ParseListSourcesResponse(rsp *http.Response) (*ListSourcesResponse, error) return response, nil } -// ParseCreateSourceResponse parses an HTTP response from a CreateSourceWithResponse call -func ParseCreateSourceResponse(rsp *http.Response) (*CreateSourceResponse, error) { +// ParseListSourcesResponse parses an HTTP response from a ListSourcesWithResponse call +func ParseListSourcesResponse(rsp *http.Response) (*ListSourcesResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) defer func() { _ = rsp.Body.Close() }() if err != nil { return nil, err } - response := &CreateSourceResponse{ + response := &ListSourcesResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: - var dest Source - if err := json.Unmarshal(bodyBytes, &dest); err != nil { - return nil, err - } - response.JSON201 = &dest - - case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: - var dest Error + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest SourceList if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } - response.JSON400 = &dest + response.JSON200 = &dest case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 401: var dest Error diff --git a/internal/api/server/agent/server.gen.go b/internal/api/server/agent/server.gen.go index 5ff2ed4..aabb0e8 100644 --- a/internal/api/server/agent/server.gen.go +++ b/internal/api/server/agent/server.gen.go @@ -276,6 +276,15 @@ func (response UpdateAgentStatus201Response) VisitUpdateAgentStatusResponse(w ht return nil } +type UpdateAgentStatus400JSONResponse externalRef0.Error + +func (response UpdateAgentStatus400JSONResponse) VisitUpdateAgentStatusResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + type UpdateAgentStatus401JSONResponse externalRef0.Error func (response UpdateAgentStatus401JSONResponse) VisitUpdateAgentStatusResponse(w http.ResponseWriter) error { @@ -294,6 +303,15 @@ func (response UpdateAgentStatus410JSONResponse) VisitUpdateAgentStatusResponse( return json.NewEncoder(w).Encode(response) } +type UpdateAgentStatus500JSONResponse externalRef0.Error + +func (response UpdateAgentStatus500JSONResponse) VisitUpdateAgentStatusResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type ReplaceSourceStatusRequestObject struct { Id openapi_types.UUID `json:"id"` Body *ReplaceSourceStatusJSONRequestBody @@ -339,6 +357,15 @@ func (response ReplaceSourceStatus404JSONResponse) VisitReplaceSourceStatusRespo return json.NewEncoder(w).Encode(response) } +type ReplaceSourceStatus500JSONResponse externalRef0.Error + +func (response ReplaceSourceStatus500JSONResponse) VisitReplaceSourceStatusResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) +} + type HealthRequestObject struct { } diff --git a/internal/api/server/server.gen.go b/internal/api/server/server.gen.go index ee98fbf..102ce63 100644 --- a/internal/api/server/server.gen.go +++ b/internal/api/server/server.gen.go @@ -23,15 +23,15 @@ type ServerInterface interface { // (GET /api/v1/agents) ListAgents(w http.ResponseWriter, r *http.Request) + // (DELETE /api/v1/agents/{id}) + DeleteAgent(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) + // (DELETE /api/v1/sources) DeleteSources(w http.ResponseWriter, r *http.Request) // (GET /api/v1/sources) ListSources(w http.ResponseWriter, r *http.Request) - // (POST /api/v1/sources) - CreateSource(w http.ResponseWriter, r *http.Request) - // (DELETE /api/v1/sources/{id}) DeleteSource(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) @@ -57,6 +57,11 @@ func (_ Unimplemented) ListAgents(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } +// (DELETE /api/v1/agents/{id}) +func (_ Unimplemented) DeleteAgent(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { + w.WriteHeader(http.StatusNotImplemented) +} + // (DELETE /api/v1/sources) func (_ Unimplemented) DeleteSources(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) @@ -67,11 +72,6 @@ func (_ Unimplemented) ListSources(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotImplemented) } -// (POST /api/v1/sources) -func (_ Unimplemented) CreateSource(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusNotImplemented) -} - // (DELETE /api/v1/sources/{id}) func (_ Unimplemented) DeleteSource(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { w.WriteHeader(http.StatusNotImplemented) @@ -121,12 +121,23 @@ func (siw *ServerInterfaceWrapper) ListAgents(w http.ResponseWriter, r *http.Req handler.ServeHTTP(w, r.WithContext(ctx)) } -// DeleteSources operation middleware -func (siw *ServerInterfaceWrapper) DeleteSources(w http.ResponseWriter, r *http.Request) { +// DeleteAgent operation middleware +func (siw *ServerInterfaceWrapper) DeleteAgent(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + var err error + + // ------------- Path parameter "id" ------------- + var id openapi_types.UUID + + err = runtime.BindStyledParameterWithOptions("simple", "id", chi.URLParam(r, "id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "id", Err: err}) + return + } + handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.DeleteSources(w, r) + siw.Handler.DeleteAgent(w, r, id) })) for _, middleware := range siw.HandlerMiddlewares { @@ -136,12 +147,12 @@ func (siw *ServerInterfaceWrapper) DeleteSources(w http.ResponseWriter, r *http. handler.ServeHTTP(w, r.WithContext(ctx)) } -// ListSources operation middleware -func (siw *ServerInterfaceWrapper) ListSources(w http.ResponseWriter, r *http.Request) { +// DeleteSources operation middleware +func (siw *ServerInterfaceWrapper) DeleteSources(w http.ResponseWriter, r *http.Request) { ctx := r.Context() handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.ListSources(w, r) + siw.Handler.DeleteSources(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -151,12 +162,12 @@ func (siw *ServerInterfaceWrapper) ListSources(w http.ResponseWriter, r *http.Re handler.ServeHTTP(w, r.WithContext(ctx)) } -// CreateSource operation middleware -func (siw *ServerInterfaceWrapper) CreateSource(w http.ResponseWriter, r *http.Request) { +// ListSources operation middleware +func (siw *ServerInterfaceWrapper) ListSources(w http.ResponseWriter, r *http.Request) { ctx := r.Context() handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - siw.Handler.CreateSource(w, r) + siw.Handler.ListSources(w, r) })) for _, middleware := range siw.HandlerMiddlewares { @@ -402,13 +413,13 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl r.Get(options.BaseURL+"/api/v1/agents", wrapper.ListAgents) }) r.Group(func(r chi.Router) { - r.Delete(options.BaseURL+"/api/v1/sources", wrapper.DeleteSources) + r.Delete(options.BaseURL+"/api/v1/agents/{id}", wrapper.DeleteAgent) }) r.Group(func(r chi.Router) { - r.Get(options.BaseURL+"/api/v1/sources", wrapper.ListSources) + r.Delete(options.BaseURL+"/api/v1/sources", wrapper.DeleteSources) }) r.Group(func(r chi.Router) { - r.Post(options.BaseURL+"/api/v1/sources", wrapper.CreateSource) + r.Get(options.BaseURL+"/api/v1/sources", wrapper.ListSources) }) r.Group(func(r chi.Router) { r.Delete(options.BaseURL+"/api/v1/sources/{id}", wrapper.DeleteSource) @@ -454,85 +465,112 @@ func (response ListAgents401JSONResponse) VisitListAgentsResponse(w http.Respons return json.NewEncoder(w).Encode(response) } -type DeleteSourcesRequestObject struct { +type ListAgents500JSONResponse Error + +func (response ListAgents500JSONResponse) VisitListAgentsResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(500) + + return json.NewEncoder(w).Encode(response) } -type DeleteSourcesResponseObject interface { - VisitDeleteSourcesResponse(w http.ResponseWriter) error +type DeleteAgentRequestObject struct { + Id openapi_types.UUID `json:"id"` } -type DeleteSources200JSONResponse Status +type DeleteAgentResponseObject interface { + VisitDeleteAgentResponse(w http.ResponseWriter) error +} -func (response DeleteSources200JSONResponse) VisitDeleteSourcesResponse(w http.ResponseWriter) error { +type DeleteAgent200JSONResponse Agent + +func (response DeleteAgent200JSONResponse) VisitDeleteAgentResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type DeleteSources401JSONResponse Error +type DeleteAgent400JSONResponse Error -func (response DeleteSources401JSONResponse) VisitDeleteSourcesResponse(w http.ResponseWriter) error { +func (response DeleteAgent400JSONResponse) VisitDeleteAgentResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(401) + w.WriteHeader(400) return json.NewEncoder(w).Encode(response) } -type ListSourcesRequestObject struct { -} +type DeleteAgent401JSONResponse Error -type ListSourcesResponseObject interface { - VisitListSourcesResponse(w http.ResponseWriter) error +func (response DeleteAgent401JSONResponse) VisitDeleteAgentResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(401) + + return json.NewEncoder(w).Encode(response) } -type ListSources200JSONResponse SourceList +type DeleteAgent404JSONResponse Error -func (response ListSources200JSONResponse) VisitListSourcesResponse(w http.ResponseWriter) error { +func (response DeleteAgent404JSONResponse) VisitDeleteAgentResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) + w.WriteHeader(404) return json.NewEncoder(w).Encode(response) } -type ListSources401JSONResponse Error +type DeleteAgent500JSONResponse Error -func (response ListSources401JSONResponse) VisitListSourcesResponse(w http.ResponseWriter) error { +func (response DeleteAgent500JSONResponse) VisitDeleteAgentResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(401) + w.WriteHeader(500) return json.NewEncoder(w).Encode(response) } -type CreateSourceRequestObject struct { - Body *CreateSourceJSONRequestBody +type DeleteSourcesRequestObject struct { } -type CreateSourceResponseObject interface { - VisitCreateSourceResponse(w http.ResponseWriter) error +type DeleteSourcesResponseObject interface { + VisitDeleteSourcesResponse(w http.ResponseWriter) error } -type CreateSource201JSONResponse Source +type DeleteSources200JSONResponse Status -func (response CreateSource201JSONResponse) VisitCreateSourceResponse(w http.ResponseWriter) error { +func (response DeleteSources200JSONResponse) VisitDeleteSourcesResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(201) + w.WriteHeader(200) return json.NewEncoder(w).Encode(response) } -type CreateSource400JSONResponse Error +type DeleteSources401JSONResponse Error -func (response CreateSource400JSONResponse) VisitCreateSourceResponse(w http.ResponseWriter) error { +func (response DeleteSources401JSONResponse) VisitDeleteSourcesResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") - w.WriteHeader(400) + w.WriteHeader(401) return json.NewEncoder(w).Encode(response) } -type CreateSource401JSONResponse Error +type ListSourcesRequestObject struct { +} -func (response CreateSource401JSONResponse) VisitCreateSourceResponse(w http.ResponseWriter) error { +type ListSourcesResponseObject interface { + VisitListSourcesResponse(w http.ResponseWriter) error +} + +type ListSources200JSONResponse SourceList + +func (response ListSources200JSONResponse) VisitListSourcesResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type ListSources401JSONResponse Error + +func (response ListSources401JSONResponse) VisitListSourcesResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(401) @@ -759,15 +797,15 @@ type StrictServerInterface interface { // (GET /api/v1/agents) ListAgents(ctx context.Context, request ListAgentsRequestObject) (ListAgentsResponseObject, error) + // (DELETE /api/v1/agents/{id}) + DeleteAgent(ctx context.Context, request DeleteAgentRequestObject) (DeleteAgentResponseObject, error) + // (DELETE /api/v1/sources) DeleteSources(ctx context.Context, request DeleteSourcesRequestObject) (DeleteSourcesResponseObject, error) // (GET /api/v1/sources) ListSources(ctx context.Context, request ListSourcesRequestObject) (ListSourcesResponseObject, error) - // (POST /api/v1/sources) - CreateSource(ctx context.Context, request CreateSourceRequestObject) (CreateSourceResponseObject, error) - // (DELETE /api/v1/sources/{id}) DeleteSource(ctx context.Context, request DeleteSourceRequestObject) (DeleteSourceResponseObject, error) @@ -837,23 +875,25 @@ func (sh *strictHandler) ListAgents(w http.ResponseWriter, r *http.Request) { } } -// DeleteSources operation middleware -func (sh *strictHandler) DeleteSources(w http.ResponseWriter, r *http.Request) { - var request DeleteSourcesRequestObject +// DeleteAgent operation middleware +func (sh *strictHandler) DeleteAgent(w http.ResponseWriter, r *http.Request, id openapi_types.UUID) { + var request DeleteAgentRequestObject + + request.Id = id handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.DeleteSources(ctx, request.(DeleteSourcesRequestObject)) + return sh.ssi.DeleteAgent(ctx, request.(DeleteAgentRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "DeleteSources") + handler = middleware(handler, "DeleteAgent") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(DeleteSourcesResponseObject); ok { - if err := validResponse.VisitDeleteSourcesResponse(w); err != nil { + } else if validResponse, ok := response.(DeleteAgentResponseObject); ok { + if err := validResponse.VisitDeleteAgentResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -861,23 +901,23 @@ func (sh *strictHandler) DeleteSources(w http.ResponseWriter, r *http.Request) { } } -// ListSources operation middleware -func (sh *strictHandler) ListSources(w http.ResponseWriter, r *http.Request) { - var request ListSourcesRequestObject +// DeleteSources operation middleware +func (sh *strictHandler) DeleteSources(w http.ResponseWriter, r *http.Request) { + var request DeleteSourcesRequestObject handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.ListSources(ctx, request.(ListSourcesRequestObject)) + return sh.ssi.DeleteSources(ctx, request.(DeleteSourcesRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "ListSources") + handler = middleware(handler, "DeleteSources") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(ListSourcesResponseObject); ok { - if err := validResponse.VisitListSourcesResponse(w); err != nil { + } else if validResponse, ok := response.(DeleteSourcesResponseObject); ok { + if err := validResponse.VisitDeleteSourcesResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { @@ -885,30 +925,23 @@ func (sh *strictHandler) ListSources(w http.ResponseWriter, r *http.Request) { } } -// CreateSource operation middleware -func (sh *strictHandler) CreateSource(w http.ResponseWriter, r *http.Request) { - var request CreateSourceRequestObject - - var body CreateSourceJSONRequestBody - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - sh.options.RequestErrorHandlerFunc(w, r, fmt.Errorf("can't decode JSON body: %w", err)) - return - } - request.Body = &body +// ListSources operation middleware +func (sh *strictHandler) ListSources(w http.ResponseWriter, r *http.Request) { + var request ListSourcesRequestObject handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) { - return sh.ssi.CreateSource(ctx, request.(CreateSourceRequestObject)) + return sh.ssi.ListSources(ctx, request.(ListSourcesRequestObject)) } for _, middleware := range sh.middlewares { - handler = middleware(handler, "CreateSource") + handler = middleware(handler, "ListSources") } response, err := handler(r.Context(), w, r, request) if err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) - } else if validResponse, ok := response.(CreateSourceResponseObject); ok { - if err := validResponse.VisitCreateSourceResponse(w); err != nil { + } else if validResponse, ok := response.(ListSourcesResponseObject); ok { + if err := validResponse.VisitListSourcesResponse(w); err != nil { sh.options.ResponseErrorHandlerFunc(w, r, err) } } else if response != nil { diff --git a/internal/service/agent.go b/internal/service/agent.go index f79c29a..16e73ee 100644 --- a/internal/service/agent.go +++ b/internal/service/agent.go @@ -2,10 +2,40 @@ package service import ( "context" + "errors" "github.com/kubev2v/migration-planner/internal/api/server" + "github.com/kubev2v/migration-planner/internal/store" ) func (h *ServiceHandler) ListAgents(ctx context.Context, request server.ListAgentsRequestObject) (server.ListAgentsResponseObject, error) { - return nil, nil + result, err := h.store.Agent().List(ctx, store.NewAgentQueryFilter(), store.NewAgentQueryOptions().WithIncludeSoftDeleted(true)) + if err != nil { + return nil, err + } + return server.ListAgents200JSONResponse(result), nil +} + +func (h *ServiceHandler) DeleteAgent(ctx context.Context, request server.DeleteAgentRequestObject) (server.DeleteAgentResponseObject, error) { + agent, err := h.store.Agent().Get(ctx, request.Id.String()) + if err != nil { + if errors.Is(err, store.ErrRecordNotFound) { + return server.DeleteAgent404JSONResponse{Message: "agent not found"}, nil + } + return server.DeleteAgent500JSONResponse{}, nil + } + if agent.DeletedAt != nil { + return server.DeleteAgent200JSONResponse(*agent), nil + } + if agent.Associated { + return server.DeleteAgent400JSONResponse{Message: "cannot delete an associated agent"}, nil + } + + // remove the agent + softDeletion := true + if err := h.store.Agent().Delete(ctx, request.Id.String(), softDeletion); err != nil { + return server.DeleteAgent500JSONResponse{}, nil + } + + return server.DeleteAgent200JSONResponse(*agent), nil } diff --git a/internal/service/agent/agent_suite_test.go b/internal/service/agent/agent_suite_test.go new file mode 100644 index 0000000..dba4ae7 --- /dev/null +++ b/internal/service/agent/agent_suite_test.go @@ -0,0 +1,13 @@ +package service_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestAgent(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Agent Suite") +} diff --git a/internal/service/agent/handler.go b/internal/service/agent/handler.go index a61d7a4..ca6c4ee 100644 --- a/internal/service/agent/handler.go +++ b/internal/service/agent/handler.go @@ -2,6 +2,7 @@ package service import ( "context" + "errors" agentServer "github.com/kubev2v/migration-planner/internal/api/server/agent" "github.com/kubev2v/migration-planner/internal/store" @@ -24,10 +25,64 @@ func NewAgentServiceHandler(store store.Store, log logrus.FieldLogger) *AgentSer } func (h *AgentServiceHandler) ReplaceSourceStatus(ctx context.Context, request agentServer.ReplaceSourceStatusRequestObject) (agentServer.ReplaceSourceStatusResponseObject, error) { - result, err := h.store.Source().Update(ctx, request.Id, &request.Body.Status, &request.Body.StatusInfo, &request.Body.CredentialUrl, request.Body.Inventory) + // start new transaction + ctx, err := h.store.NewTransactionContext(ctx) if err != nil { + return agentServer.ReplaceSourceStatus500JSONResponse{}, nil + } + + agent, err := h.store.Agent().Get(ctx, request.Body.AgentId.String()) + if err != nil && !errors.Is(err, store.ErrRecordNotFound) { + return agentServer.ReplaceSourceStatus400JSONResponse{}, nil + } + + if agent == nil { + return agentServer.ReplaceSourceStatus400JSONResponse{}, nil + } + + source, err := h.store.Source().Get(ctx, request.Id) + if err != nil && !errors.Is(err, store.ErrRecordNotFound) { + return agentServer.ReplaceSourceStatus400JSONResponse{}, nil + } + + associated := false + if source == nil { + source, err = h.store.Source().Create(ctx, request.Id) + if err != nil { + return agentServer.ReplaceSourceStatus400JSONResponse{}, nil + } + associated = true + } + + // connect the agent to the source + // If agent is already connected to a source but the source is different from the current one, connect it anyway. + // An agent is allowed to change sources. + if agent.SourceId == nil || *agent.SourceId != source.Id.String() { + if agent, err = h.store.Agent().UpdateSourceID(ctx, agent.Id, request.Id.String(), associated); err != nil { + store.Rollback(ctx) + return agentServer.ReplaceSourceStatus400JSONResponse{}, nil + } + } + + // We are not allowing updates from agents not associated with the source ("first come first serve"). + if !agent.Associated { + h.log.Errorf("Failed to update status of source %s from agent %s. Agent is not the associated with the source", source.Id, agent.Id) + if _, err := store.Commit(ctx); err != nil { + return agentServer.ReplaceSourceStatus500JSONResponse{}, nil + } + return agentServer.ReplaceSourceStatus400JSONResponse{}, nil + } + + result, err := h.store.Source().Update(ctx, request.Id, &request.Body.Inventory) + if err != nil { + store.Rollback(ctx) return agentServer.ReplaceSourceStatus400JSONResponse{}, nil } + + if _, err := store.Commit(ctx); err != nil { + return agentServer.ReplaceSourceStatus500JSONResponse{}, nil + } + return agentServer.ReplaceSourceStatus200JSONResponse(*result), nil } @@ -37,5 +92,37 @@ func (h *AgentServiceHandler) Health(ctx context.Context, request agentServer.He } func (h *AgentServiceHandler) UpdateAgentStatus(ctx context.Context, request agentServer.UpdateAgentStatusRequestObject) (agentServer.UpdateAgentStatusResponseObject, error) { - return nil, nil + ctx, err := h.store.NewTransactionContext(ctx) + if err != nil { + return agentServer.UpdateAgentStatus500JSONResponse{}, nil + } + agent, err := h.store.Agent().Get(ctx, request.Id.String()) + if err != nil && !errors.Is(err, store.ErrRecordNotFound) { + return agentServer.UpdateAgentStatus400JSONResponse{}, nil + } + + if agent == nil { + if _, err := h.store.Agent().Create(ctx, *request.Body); err != nil { + return agentServer.UpdateAgentStatus400JSONResponse{}, nil + } + if _, err := store.Commit(ctx); err != nil { + return agentServer.UpdateAgentStatus500JSONResponse{}, nil + } + return agentServer.UpdateAgentStatus201Response{}, nil + } + + // check if agent is marked for deletion + if agent.DeletedAt != nil { + return agentServer.UpdateAgentStatus410JSONResponse{}, nil + } + + if _, err := h.store.Agent().Update(ctx, *request.Body); err != nil { + store.Rollback(ctx) + return agentServer.UpdateAgentStatus400JSONResponse{}, nil + } + + if _, err := store.Commit(ctx); err != nil { + return agentServer.UpdateAgentStatus500JSONResponse{}, nil + } + return agentServer.UpdateAgentStatus200Response{}, nil } diff --git a/internal/service/agent/handler_test.go b/internal/service/agent/handler_test.go new file mode 100644 index 0000000..1dcd8b9 --- /dev/null +++ b/internal/service/agent/handler_test.go @@ -0,0 +1,213 @@ +package service_test + +import ( + "context" + "fmt" + "reflect" + "time" + + "github.com/google/uuid" + v1alpha1 "github.com/kubev2v/migration-planner/api/v1alpha1" + apiAgent "github.com/kubev2v/migration-planner/api/v1alpha1/agent" + server "github.com/kubev2v/migration-planner/internal/api/server/agent" + "github.com/kubev2v/migration-planner/internal/config" + service "github.com/kubev2v/migration-planner/internal/service/agent" + "github.com/kubev2v/migration-planner/internal/store" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +const ( + insertAgentStm = "INSERT INTO agents (id, status, status_info, cred_url, version) VALUES ('%s', '%s', '%s', '%s', 'version_1');" + insertAgentWithSourceStm = "INSERT INTO agents (id, status, status_info, cred_url, source_id, version) VALUES ('%s', '%s', '%s', '%s', '%s', 'version_1');" + insertAgentWithUpdateAtStm = "INSERT INTO agents (id, status, status_info, cred_url, updated_at, version) VALUES ('%s', '%s', '%s', '%s', '%s', 'version_1');" + insertAgentWithDeletedAtStm = "INSERT INTO agents (id, status, status_info, cred_url,created_at, updated_at, deleted_at, version) VALUES ('%s', '%s', '%s', '%s', '%s','%s','%s', 'version_1');" +) + +var _ = Describe("agent store", Ordered, func() { + var ( + s store.Store + gormdb *gorm.DB + ) + + BeforeAll(func() { + log := logrus.New() + db, err := store.InitDB(config.NewDefault(), log) + Expect(err).To(BeNil()) + + s = store.NewStore(db, log) + gormdb = db + s.InitialMigration() + }) + + AfterAll(func() { + s.Close() + }) + + Context("Update agent status", func() { + It("successfully creates the agent", func() { + agentID := uuid.New() + + srv := service.NewAgentServiceHandler(s, logrus.New()) + resp, err := srv.UpdateAgentStatus(context.TODO(), server.UpdateAgentStatusRequestObject{ + Id: agentID, + Body: &apiAgent.UpdateAgentStatusJSONRequestBody{ + Id: agentID.String(), + Status: string(v1alpha1.AgentStatusWaitingForCredentials), + StatusInfo: "waiting-for-credentials", + CredentialUrl: "creds-url", + Version: "version-1", + }, + }) + Expect(err).To(BeNil()) + Expect(resp).To(Equal(server.UpdateAgentStatus201Response{})) + + count := -1 + tx := gormdb.Raw("SELECT COUNT(*) FROM agents;").Scan(&count) + Expect(count).To(Equal(1)) + + status := "" + tx = gormdb.Raw(fmt.Sprintf("SELECT status from agents WHERE id = '%s';", agentID)).Scan(&status) + Expect(tx.Error).To(BeNil()) + Expect(status).To(Equal("waiting-for-credentials")) + }) + + It("successfully updates the agent", func() { + agentID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertAgentStm, agentID, "not-connected", "status-info-1", "cred_url-1")) + Expect(tx.Error).To(BeNil()) + + srv := service.NewAgentServiceHandler(s, logrus.New()) + resp, err := srv.UpdateAgentStatus(context.TODO(), server.UpdateAgentStatusRequestObject{ + Id: agentID, + Body: &apiAgent.UpdateAgentStatusJSONRequestBody{ + Id: agentID.String(), + Status: string(v1alpha1.AgentStatusWaitingForCredentials), + StatusInfo: "waiting-for-credentials", + CredentialUrl: "creds-url", + Version: "version-1", + }, + }) + Expect(err).To(BeNil()) + Expect(resp).To(Equal(server.UpdateAgentStatus200Response{})) + + count := -1 + tx = gormdb.Raw("SELECT COUNT(*) FROM agents;").Scan(&count) + Expect(count).To(Equal(1)) + + status := "" + tx = gormdb.Raw(fmt.Sprintf("SELECT status from agents WHERE id = '%s';", agentID)).Scan(&status) + Expect(tx.Error).To(BeNil()) + Expect(status).To(Equal("waiting-for-credentials")) + }) + + It("should receive 410 when agent is soft deleted", func() { + agentID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertAgentWithDeletedAtStm, agentID, "not-connected", "status-info-1", "cred_url-1", time.Now().Format(time.RFC3339), time.Now().Format(time.RFC3339), time.Now().Format(time.RFC3339))) + Expect(tx.Error).To(BeNil()) + + srv := service.NewAgentServiceHandler(s, logrus.New()) + resp, err := srv.UpdateAgentStatus(context.TODO(), server.UpdateAgentStatusRequestObject{ + Id: agentID, + Body: &apiAgent.UpdateAgentStatusJSONRequestBody{ + Id: agentID.String(), + Status: string(v1alpha1.AgentStatusWaitingForCredentials), + StatusInfo: "waiting-for-credentials", + CredentialUrl: "creds-url", + Version: "version-1", + }, + }) + Expect(err).To(BeNil()) + Expect(resp).To(Equal(server.UpdateAgentStatus410JSONResponse{})) + }) + + AfterEach(func() { + gormdb.Exec("DELETE FROM agents;") + }) + }) + + Context("Update source", func() { + It("successfully creates the source", func() { + agentID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertAgentStm, agentID, "not-connected", "status-info-1", "cred_url-1")) + Expect(tx.Error).To(BeNil()) + + sourceID := uuid.New() + srv := service.NewAgentServiceHandler(s, logrus.New()) + resp, err := srv.ReplaceSourceStatus(context.TODO(), server.ReplaceSourceStatusRequestObject{ + Id: sourceID, + Body: &apiAgent.SourceStatusUpdate{ + AgentId: agentID, + Inventory: v1alpha1.Inventory{}, + }, + }) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.ReplaceSourceStatus200JSONResponse{}))) + + // according to the multi source model the agent should be associated with the source + agent, err := s.Agent().Get(context.TODO(), agentID.String()) + Expect(err).To(BeNil()) + Expect(agent.Associated).To(BeTrue()) + + // the source should have the agent associated + source, err := s.Source().Get(context.TODO(), sourceID) + Expect(err).To(BeNil()) + Expect(source.Agents).ToNot(BeNil()) + Expect(*source.Agents).To(HaveLen(1)) + Expect((*source.Agents)[0].Id).To(Equal(agentID)) + }) + + It("agents not associated with the source are not allowed to update inventory", func() { + agentID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertAgentStm, agentID, "not-connected", "status-info-1", "cred_url-1")) + Expect(tx.Error).To(BeNil()) + + sourceID := uuid.New() + srv := service.NewAgentServiceHandler(s, logrus.New()) + resp, err := srv.ReplaceSourceStatus(context.TODO(), server.ReplaceSourceStatusRequestObject{ + Id: sourceID, + Body: &apiAgent.SourceStatusUpdate{ + AgentId: agentID, + Inventory: v1alpha1.Inventory{}, + }, + }) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.ReplaceSourceStatus200JSONResponse{}))) + + // according to the multi source model the agent should be associated with the source + agent, err := s.Agent().Get(context.TODO(), agentID.String()) + Expect(err).To(BeNil()) + Expect(agent.Associated).To(BeTrue()) + + // the source should have the agent associated + source, err := s.Source().Get(context.TODO(), sourceID) + Expect(err).To(BeNil()) + Expect(source.Agents).ToNot(BeNil()) + Expect(*source.Agents).To(HaveLen(1)) + Expect((*source.Agents)[0].Id).To(Equal(agentID)) + + // make another request from another agent + secondAgentID := uuid.New() + tx = gormdb.Exec(fmt.Sprintf(insertAgentStm, secondAgentID, "not-connected", "status-info-1", "cred_url-1")) + Expect(tx.Error).To(BeNil()) + + resp, err = srv.ReplaceSourceStatus(context.TODO(), server.ReplaceSourceStatusRequestObject{ + Id: sourceID, + Body: &apiAgent.SourceStatusUpdate{ + AgentId: secondAgentID, + Inventory: v1alpha1.Inventory{}, + }, + }) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.ReplaceSourceStatus400JSONResponse{}))) + }) + + AfterEach(func() { + gormdb.Exec("DELETE FROM agents;") + gormdb.Exec("DELETE FROM sources;") + }) + }) + +}) diff --git a/internal/service/agent_test.go b/internal/service/agent_test.go new file mode 100644 index 0000000..9d8cde5 --- /dev/null +++ b/internal/service/agent_test.go @@ -0,0 +1,105 @@ +package service_test + +import ( + "context" + "fmt" + "reflect" + + "github.com/google/uuid" + "github.com/kubev2v/migration-planner/internal/api/server" + "github.com/kubev2v/migration-planner/internal/config" + "github.com/kubev2v/migration-planner/internal/service" + "github.com/kubev2v/migration-planner/internal/store" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +const ( + insertAgentStm = "INSERT INTO agents (id, status, status_info, cred_url, version) VALUES ('%s', '%s', '%s', '%s', 'version_1');" + insertAssociatedAgentStm = "INSERT INTO agents (id, associated) VALUES ('%s', TRUE);" +) + +var _ = Describe("agent handler", Ordered, func() { + var ( + s store.Store + gormdb *gorm.DB + ) + + BeforeAll(func() { + log := logrus.New() + db, err := store.InitDB(config.NewDefault(), log) + Expect(err).To(BeNil()) + + s = store.NewStore(db, log) + gormdb = db + s.InitialMigration() + }) + + AfterAll(func() { + s.Close() + }) + + Context("list", func() { + It("successfully list all the agents", func() { + tx := gormdb.Exec(fmt.Sprintf(insertAgentStm, "agent-1", "not-connected", "status-info-1", "cred_url-1")) + Expect(tx.Error).To(BeNil()) + tx = gormdb.Exec(fmt.Sprintf(insertAgentStm, "agent-2", "not-connected", "status-info-2", "cred_url-2")) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + resp, err := srv.ListAgents(context.TODO(), server.ListAgentsRequestObject{}) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.ListAgents200JSONResponse{}))) + Expect(resp).To(HaveLen(2)) + }) + + AfterEach(func() { + gormdb.Exec("DELETE FROM agents;") + }) + }) + + Context("delete", func() { + It("successfully deletes an unassociated agents", func() { + agentID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertAgentStm, agentID, "not-connected", "status-info-1", "cred_url-1")) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + resp, err := srv.DeleteAgent(context.TODO(), server.DeleteAgentRequestObject{Id: agentID}) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.DeleteAgent200JSONResponse{}))) + + myAgent, err := s.Agent().Get(context.TODO(), agentID.String()) + Expect(err).To(BeNil()) + Expect(myAgent.DeletedAt).NotTo(BeNil()) + }) + + It("fails to delete agent -- 404", func() { + agentID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertAgentStm, agentID, "not-connected", "status-info-1", "cred_url-1")) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + resp, err := srv.DeleteAgent(context.TODO(), server.DeleteAgentRequestObject{Id: uuid.New()}) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.DeleteAgent404JSONResponse{}))) + }) + + It("fails to delete an associated agent", func() { + agentID := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertAssociatedAgentStm, agentID)) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + resp, err := srv.DeleteAgent(context.TODO(), server.DeleteAgentRequestObject{Id: agentID}) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.DeleteAgent400JSONResponse{}))) + }) + + AfterEach(func() { + gormdb.Exec("DELETE FROM agents;") + }) + }) +}) diff --git a/internal/service/service_suite_test.go b/internal/service/service_suite_test.go new file mode 100644 index 0000000..b3d922b --- /dev/null +++ b/internal/service/service_suite_test.go @@ -0,0 +1,13 @@ +package service_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestService(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Service Suite") +} diff --git a/internal/service/source.go b/internal/service/source.go index fe798ff..61472e6 100644 --- a/internal/service/source.go +++ b/internal/service/source.go @@ -4,6 +4,7 @@ import ( "context" "github.com/kubev2v/migration-planner/internal/api/server" + "github.com/kubev2v/migration-planner/internal/store" ) func (h *ServiceHandler) ListSources(ctx context.Context, request server.ListSourcesRequestObject) (server.ListSourcesResponseObject, error) { @@ -14,14 +15,6 @@ func (h *ServiceHandler) ListSources(ctx context.Context, request server.ListSou return server.ListSources200JSONResponse(result), nil } -func (h *ServiceHandler) CreateSource(ctx context.Context, request server.CreateSourceRequestObject) (server.CreateSourceResponseObject, error) { - result, err := h.store.Source().Create(ctx, *request.Body) - if err != nil { - return server.CreateSource400JSONResponse{Message: err.Error()}, nil - } - return server.CreateSource201JSONResponse(*result), nil -} - func (h *ServiceHandler) DeleteSources(ctx context.Context, request server.DeleteSourcesRequestObject) (server.DeleteSourcesResponseObject, error) { err := h.store.Source().DeleteAll(ctx) if err != nil { @@ -39,9 +32,29 @@ func (h *ServiceHandler) ReadSource(ctx context.Context, request server.ReadSour } func (h *ServiceHandler) DeleteSource(ctx context.Context, request server.DeleteSourceRequestObject) (server.DeleteSourceResponseObject, error) { - err := h.store.Source().Delete(ctx, request.Id) + // Delete the agents first + ctx, err := h.store.NewTransactionContext(ctx) if err != nil { return server.DeleteSource404JSONResponse{}, nil } + + agents, err := h.store.Agent().List(ctx, store.NewAgentQueryFilter().BySourceID(request.Id.String()), store.NewAgentQueryOptions()) + if err != nil { + return server.DeleteSource400JSONResponse{}, nil + } + + for _, agent := range agents { + if err := h.store.Agent().Delete(ctx, agent.Id, true); err != nil { + store.Rollback(ctx) + return server.DeleteSource400JSONResponse{}, nil + } + } + + if err := h.store.Source().Delete(ctx, request.Id); err != nil { + store.Rollback(ctx) + return server.DeleteSource404JSONResponse{}, nil + } + + store.Commit(ctx) return server.DeleteSource200JSONResponse{}, nil } diff --git a/internal/service/source_test.go b/internal/service/source_test.go new file mode 100644 index 0000000..5e81b74 --- /dev/null +++ b/internal/service/source_test.go @@ -0,0 +1,147 @@ +package service_test + +import ( + "context" + "fmt" + "reflect" + + "github.com/google/uuid" + "github.com/kubev2v/migration-planner/internal/api/server" + "github.com/kubev2v/migration-planner/internal/config" + "github.com/kubev2v/migration-planner/internal/service" + "github.com/kubev2v/migration-planner/internal/store" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +const ( + insertAgentWithSourceStm = "INSERT INTO agents (id, source_id,associated) VALUES ('%s', '%s', TRUE);" + insertSourceStm = "INSERT INTO sources (id) VALUES ('%s');" +) + +var _ = Describe("source handler", Ordered, func() { + var ( + s store.Store + gormdb *gorm.DB + ) + + BeforeAll(func() { + log := logrus.New() + db, err := store.InitDB(config.NewDefault(), log) + Expect(err).To(BeNil()) + + s = store.NewStore(db, log) + gormdb = db + s.InitialMigration() + }) + + AfterAll(func() { + s.Close() + }) + + Context("list", func() { + It("successfully list all the sources", func() { + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) + Expect(tx.Error).To(BeNil()) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + resp, err := srv.ListSources(context.TODO(), server.ListSourcesRequestObject{}) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.ListSources200JSONResponse{}))) + Expect(resp).To(HaveLen(2)) + }) + + AfterEach(func() { + gormdb.Exec("DELETE FROM agents;") + gormdb.Exec("DELETE FROM sources;") + }) + }) + + Context("get", func() { + It("successfully retrieve the source", func() { + firstSource := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, firstSource)) + Expect(tx.Error).To(BeNil()) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + resp, err := srv.ReadSource(context.TODO(), server.ReadSourceRequestObject{Id: firstSource}) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.ReadSource200JSONResponse{}))) + }) + It("failed to get source - 404", func() { + firstSource := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, firstSource)) + Expect(tx.Error).To(BeNil()) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + resp, err := srv.ReadSource(context.TODO(), server.ReadSourceRequestObject{Id: uuid.New()}) + Expect(err).To(BeNil()) + Expect(reflect.TypeOf(resp)).To(Equal(reflect.TypeOf(server.ReadSource404JSONResponse{}))) + }) + AfterEach(func() { + gormdb.Exec("DELETE FROM agents;") + gormdb.Exec("DELETE FROM sources;") + }) + }) + + Context("delete", func() { + It("successfully deletes all the sources", func() { + firstSource := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, firstSource)) + Expect(tx.Error).To(BeNil()) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + _, err := srv.DeleteSources(context.TODO(), server.DeleteSourcesRequestObject{}) + Expect(err).To(BeNil()) + + count := 1 + tx = gormdb.Raw("SELECT COUNT(*) FROM SOURCES;").Scan(&count) + Expect(tx.Error).To(BeNil()) + Expect(count).To(Equal(0)) + + }) + It("successfully deletes a source", func() { + source := uuid.New() + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, source)) + Expect(tx.Error).To(BeNil()) + + agent := uuid.New() + tx = gormdb.Exec(fmt.Sprintf(insertAgentWithSourceStm, agent.String(), source.String())) + Expect(tx.Error).To(BeNil()) + + srv := service.NewServiceHandler(s, logrus.New()) + _, err := srv.DeleteSource(context.TODO(), server.DeleteSourceRequestObject{Id: source}) + Expect(err).To(BeNil()) + + count := 1 + tx = gormdb.Raw("SELECT COUNT(*) FROM SOURCES;").Scan(&count) + Expect(tx.Error).To(BeNil()) + Expect(count).To(Equal(0)) + + // we still have an agent but it's soft deleted + count = 0 + tx = gormdb.Raw("SELECT COUNT(*) FROM AGENTS;").Scan(&count) + Expect(tx.Error).To(BeNil()) + Expect(count).To(Equal(1)) + + myAgent, err := s.Agent().Get(context.TODO(), agent.String()) + Expect(err).To(BeNil()) + Expect(myAgent.DeletedAt).NotTo(BeNil()) + + }) + AfterEach(func() { + gormdb.Exec("DELETE FROM agents;") + gormdb.Exec("DELETE FROM sources;") + }) + }) +}) diff --git a/internal/store/agent.go b/internal/store/agent.go index db9e3b4..b9f6e93 100644 --- a/internal/store/agent.go +++ b/internal/store/agent.go @@ -25,7 +25,7 @@ type Agent interface { List(ctx context.Context, filter *AgentQueryFilter, opts *AgentQueryOptions) (api.AgentList, error) Get(ctx context.Context, id string) (*api.Agent, error) Update(ctx context.Context, agentUpdate apiAgent.AgentStatusUpdate) (*api.Agent, error) - UpdateSourceID(ctx context.Context, agentID string, sourceID string) (*api.Agent, error) + UpdateSourceID(ctx context.Context, agentID string, sourceID string, associated bool) (*api.Agent, error) Create(ctx context.Context, agentUpdate apiAgent.AgentStatusUpdate) (*api.Agent, error) Delete(ctx context.Context, id string, softDeletion bool) error InitialMigration(context.Context) error @@ -100,7 +100,7 @@ func (a *AgentStore) Update(ctx context.Context, agentUpdate apiAgent.AgentStatu // UpdateSourceID updates the sources id field of an agent. // The source must exists. -func (a *AgentStore) UpdateSourceID(ctx context.Context, agentID string, sourceID string) (*api.Agent, error) { +func (a *AgentStore) UpdateSourceID(ctx context.Context, agentID string, sourceID string, associated bool) (*api.Agent, error) { agent := model.NewAgentFromID(agentID) if err := a.getDB(ctx).WithContext(ctx).First(agent).Error; err != nil { @@ -108,6 +108,7 @@ func (a *AgentStore) UpdateSourceID(ctx context.Context, agentID string, sourceI } agent.SourceID = &sourceID + agent.Associated = associated if tx := a.getDB(ctx).WithContext(ctx).Clauses(clause.Returning{}).Updates(&agent); tx.Error != nil { return nil, tx.Error @@ -121,9 +122,9 @@ func (a *AgentStore) UpdateSourceID(ctx context.Context, agentID string, sourceI func (a *AgentStore) Get(ctx context.Context, id string) (*api.Agent, error) { agent := model.NewAgentFromID(id) - if err := a.getDB(ctx).WithContext(ctx).First(&agent).Error; err != nil { + if err := a.getDB(ctx).WithContext(ctx).Unscoped().First(&agent).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { - return nil, nil + return nil, ErrRecordNotFound } return nil, err } diff --git a/internal/store/agent_test.go b/internal/store/agent_test.go index e14925e..e1c36d5 100644 --- a/internal/store/agent_test.go +++ b/internal/store/agent_test.go @@ -103,9 +103,9 @@ var _ = Describe("agent store", Ordered, func() { }) It("successfuly list the agents -- with filter by source-id", func() { - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, "source-1", "source-name-1")) + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, "source-1")) Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, "source-2", "source-name-1")) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, "source-2")) Expect(tx.Error).To(BeNil()) tx = gormdb.Exec(fmt.Sprintf(insertAgentWithSourceStm, "agent-1", "not-connected", "status-info-1", "cred_url-1", "source-1")) Expect(tx.Error).To(BeNil()) @@ -183,9 +183,10 @@ var _ = Describe("agent store", Ordered, func() { }) Context("get", func() { - It("successfuly return nil when agent is not found", func() { + It("successfuly return ErrRecordNotFound when agent is not found", func() { agent, err := s.Agent().Get(context.TODO(), "id") - Expect(err).To(BeNil()) + Expect(err).ToNot(BeNil()) + Expect(err).To(Equal(store.ErrRecordNotFound)) Expect(agent).To(BeNil()) }) @@ -203,7 +204,7 @@ var _ = Describe("agent store", Ordered, func() { }) It("successfuly return the agent connected to a source", func() { - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, "source-1", "source-name-1")) + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, "source-1")) Expect(tx.Error).To(BeNil()) tx = gormdb.Exec(fmt.Sprintf(insertAgentWithSourceStm, "agent-1", "not-connected", "status-info-1", "cred_url-1", "source-1")) Expect(tx.Error).To(BeNil()) @@ -269,15 +270,17 @@ var _ = Describe("agent store", Ordered, func() { }) It("successfuly updates an agent -- source_id field", func() { - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, "source-1", "source-name-1")) + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, "source-1")) Expect(tx.Error).To(BeNil()) tx = gormdb.Exec(fmt.Sprintf(insertAgentStm, "agent-1", "not-connected", "status-info-1", "cred_url-1")) Expect(tx.Error).To(BeNil()) - agent, err := s.Agent().UpdateSourceID(context.TODO(), "agent-1", "source-1") + associated := true + agent, err := s.Agent().UpdateSourceID(context.TODO(), "agent-1", "source-1", associated) Expect(err).To(BeNil()) Expect(agent).NotTo(BeNil()) Expect(*agent.SourceId).To(Equal("source-1")) + Expect(agent.Associated).To(BeTrue()) rawSourceID := "" gormdb.Raw("select source_id from agents where id = 'agent-1';").Scan(&rawSourceID) @@ -288,7 +291,7 @@ var _ = Describe("agent store", Ordered, func() { tx := gormdb.Exec(fmt.Sprintf(insertAgentStm, "agent-1", "not-connected", "status-info-1", "cred_url-1")) Expect(tx.Error).To(BeNil()) - agent, err := s.Agent().UpdateSourceID(context.TODO(), "agent-1", "source-1") + agent, err := s.Agent().UpdateSourceID(context.TODO(), "agent-1", "source-1", true) Expect(err).ToNot(BeNil()) Expect(agent).To(BeNil()) }) diff --git a/internal/store/model/agent.go b/internal/store/model/agent.go index 6c4bb91..cbc154c 100644 --- a/internal/store/model/agent.go +++ b/internal/store/model/agent.go @@ -17,6 +17,7 @@ type Agent struct { SourceID *string Source *Source Version string + Associated bool } type AgentList []Agent @@ -49,6 +50,11 @@ func (a *Agent) ToApiResource() api.Agent { UpdatedAt: a.UpdatedAt, CredentialUrl: a.CredUrl, Version: a.Version, + Associated: a.Associated, + } + + if a.DeletedAt.Valid { + agent.DeletedAt = &a.DeletedAt.Time } if a.SourceID != nil { diff --git a/internal/store/model/source.go b/internal/store/model/source.go index 4cd41c2..1cc6684 100644 --- a/internal/store/model/source.go +++ b/internal/store/model/source.go @@ -11,17 +11,12 @@ import ( ) type Source struct { - ID openapi_types.UUID `json:"id" gorm:"primaryKey"` - CreatedAt time.Time - UpdatedAt time.Time - DeletedAt gorm.DeletedAt `gorm:"index"` - Name string - Status string - StatusInfo string - SshKey *string - Inventory *JSONField[api.Inventory] `gorm:"type:jsonb"` - CredUrl *string - Agents []Agent `gorm:"constraint:OnDelete:SET NULL;"` + ID openapi_types.UUID `json:"id" gorm:"primaryKey"` + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt gorm.DeletedAt `gorm:"index"` + Inventory *JSONField[api.Inventory] `gorm:"type:jsonb"` + Agents []Agent `gorm:"constraint:OnDelete:SET NULL;"` } type SourceList []Source @@ -31,8 +26,8 @@ func (s Source) String() string { return string(val) } -func NewSourceFromApiCreateResource(resource *api.SourceCreate) *Source { - return &Source{ID: uuid.New(), Name: resource.Name, SshKey: resource.SshKey} +func NewSourceFromApiCreateResource(id uuid.UUID) *Source { + return &Source{ID: id} } func NewSourceFromId(id uuid.UUID) *Source { @@ -42,15 +37,18 @@ func NewSourceFromId(id uuid.UUID) *Source { func (s *Source) ToApiResource() api.Source { source := api.Source{ - Id: s.ID, - Name: s.Name, - Status: api.StringToSourceStatus(s.Status), - StatusInfo: s.StatusInfo, - Inventory: nil, - CredentialUrl: s.CredUrl, - CreatedAt: s.CreatedAt, - UpdatedAt: s.UpdatedAt, - SshKey: s.SshKey, + Id: s.ID, + Inventory: nil, + CreatedAt: s.CreatedAt, + UpdatedAt: s.UpdatedAt, + } + + if len(s.Agents) > 0 { + agents := make([]api.SourceAgentItem, 0, len(s.Agents)) + for _, a := range s.Agents { + agents = append(agents, api.SourceAgentItem{Id: uuid.MustParse(a.ID), Associated: a.Associated}) + } + source.Agents = &agents } if s.Inventory != nil { diff --git a/internal/store/source.go b/internal/store/source.go index f1285f6..11ee6ab 100644 --- a/internal/store/source.go +++ b/internal/store/source.go @@ -12,13 +12,17 @@ import ( "gorm.io/gorm/clause" ) +var ( + ErrRecordNotFound error = errors.New("record not found") +) + type Source interface { List(ctx context.Context) (api.SourceList, error) - Create(ctx context.Context, sourceCreate api.SourceCreate) (*api.Source, error) + Create(ctx context.Context, id uuid.UUID) (*api.Source, error) DeleteAll(ctx context.Context) error Get(ctx context.Context, id uuid.UUID) (*api.Source, error) Delete(ctx context.Context, id uuid.UUID) error - Update(ctx context.Context, id uuid.UUID, status, statusInfo, credUrl *string, inventory *api.Inventory) (*api.Source, error) + Update(ctx context.Context, id uuid.UUID, inventory *api.Inventory) (*api.Source, error) InitialMigration(context.Context) error } @@ -47,8 +51,8 @@ func (s *SourceStore) List(ctx context.Context) (api.SourceList, error) { return sources.ToApiResource(), nil } -func (s *SourceStore) Create(ctx context.Context, sourceCreate api.SourceCreate) (*api.Source, error) { - source := model.NewSourceFromApiCreateResource(&sourceCreate) +func (s *SourceStore) Create(ctx context.Context, id uuid.UUID) (*api.Source, error) { + source := model.NewSourceFromApiCreateResource(id) result := s.getDB(ctx).Create(source) if result.Error != nil { return nil, result.Error @@ -64,8 +68,11 @@ func (s *SourceStore) DeleteAll(ctx context.Context) error { func (s *SourceStore) Get(ctx context.Context, id uuid.UUID) (*api.Source, error) { source := model.NewSourceFromId(id) - result := s.getDB(ctx).First(&source) + result := s.getDB(ctx).Preload("Agents").First(&source) if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, ErrRecordNotFound + } return nil, result.Error } apiSource := source.ToApiResource() @@ -82,26 +89,13 @@ func (s *SourceStore) Delete(ctx context.Context, id uuid.UUID) error { return nil } -func (s *SourceStore) Update(ctx context.Context, id uuid.UUID, status, statusInfo, credUrl *string, inventory *api.Inventory) (*api.Source, error) { +func (s *SourceStore) Update(ctx context.Context, id uuid.UUID, inventory *api.Inventory) (*api.Source, error) { source := model.NewSourceFromId(id) selectFields := []string{} - if status != nil { - source.Status = *status - selectFields = append(selectFields, "status") - } - if statusInfo != nil { - source.StatusInfo = *statusInfo - selectFields = append(selectFields, "status_info") - } if inventory != nil { source.Inventory = model.MakeJSONField(*inventory) selectFields = append(selectFields, "inventory") } - if credUrl != nil { - source.CredUrl = credUrl - selectFields = append(selectFields, "cred_url") - } - result := s.getDB(ctx).Model(source).Clauses(clause.Returning{}).Select(selectFields).Updates(&source) if result.Error != nil { return nil, result.Error diff --git a/internal/store/source_test.go b/internal/store/source_test.go index c9b1b77..e18de28 100644 --- a/internal/store/source_test.go +++ b/internal/store/source_test.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/google/uuid" - "github.com/kubev2v/migration-planner/api/v1alpha1" "github.com/kubev2v/migration-planner/internal/config" "github.com/kubev2v/migration-planner/internal/store" . "github.com/onsi/ginkgo/v2" @@ -15,7 +14,7 @@ import ( ) const ( - insertSourceStm = "INSERT INTO sources (id, name) VALUES ('%s', '%s');" + insertSourceStm = "INSERT INTO sources (id) VALUES ('%s');" ) var _ = Describe("source store", Ordered, func() { @@ -39,9 +38,9 @@ var _ = Describe("source store", Ordered, func() { Context("list", func() { It("successfully list all the sources", func() { - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString(), "name-1")) + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString(), "name-2")) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) Expect(tx.Error).To(BeNil()) sources, err := s.Source().List(context.TODO()) @@ -63,22 +62,21 @@ var _ = Describe("source store", Ordered, func() { Context("get", func() { It("successfully get a source", func() { id := uuid.New() - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, id, "name-1")) + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, id)) Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString(), "name-2")) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) Expect(tx.Error).To(BeNil()) source, err := s.Source().Get(context.TODO(), id) Expect(err).To(BeNil()) Expect(source).ToNot(BeNil()) - Expect(source.Name).To(Equal("name-1")) }) It("failed get a source -- source does not exists", func() { id := uuid.New() - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString(), "name-1")) + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString(), "name-2")) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) Expect(tx.Error).To(BeNil()) source, err := s.Source().Get(context.TODO(), id) @@ -93,11 +91,8 @@ var _ = Describe("source store", Ordered, func() { Context("create", func() { It("successfully creates one source", func() { - sshKey := "some key" - source, err := s.Source().Create(context.TODO(), v1alpha1.SourceCreate{ - Name: "name-1", - SshKey: &sshKey, - }) + sourceID := uuid.New() + source, err := s.Source().Create(context.TODO(), sourceID) Expect(err).To(BeNil()) Expect(source).NotTo(BeNil()) @@ -108,9 +103,8 @@ var _ = Describe("source store", Ordered, func() { }) It("successfully creates one source without sshkey", func() { - source, err := s.Source().Create(context.TODO(), v1alpha1.SourceCreate{ - Name: "name-1", - }) + sourceID := uuid.New() + source, err := s.Source().Create(context.TODO(), sourceID) Expect(err).To(BeNil()) Expect(source).NotTo(BeNil()) @@ -128,9 +122,9 @@ var _ = Describe("source store", Ordered, func() { Context("delete", func() { It("successfully delete a source", func() { id := uuid.New() - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, id, "name-1")) + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, id)) Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString(), "name-2")) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) Expect(tx.Error).To(BeNil()) err := s.Source().Delete(context.TODO(), id) @@ -144,9 +138,9 @@ var _ = Describe("source store", Ordered, func() { It("successfully delete all sources", func() { id := uuid.New() - tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, id, "name-1")) + tx := gormdb.Exec(fmt.Sprintf(insertSourceStm, id)) Expect(tx.Error).To(BeNil()) - tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString(), "name-2")) + tx = gormdb.Exec(fmt.Sprintf(insertSourceStm, uuid.NewString())) Expect(tx.Error).To(BeNil()) err := s.Source().DeleteAll(context.TODO()) diff --git a/internal/store/store_test.go b/internal/store/store_test.go index 7893e31..0b9f8fb 100644 --- a/internal/store/store_test.go +++ b/internal/store/store_test.go @@ -3,7 +3,7 @@ package store_test import ( "context" - api "github.com/kubev2v/migration-planner/api/v1alpha1" + "github.com/google/uuid" "github.com/kubev2v/migration-planner/internal/config" st "github.com/kubev2v/migration-planner/internal/store" "github.com/kubev2v/migration-planner/pkg/log" @@ -16,7 +16,6 @@ var _ = Describe("Store", Ordered, func() { var ( store st.Store gormDB *gorm.DB - sshKey string ) BeforeAll(func() { @@ -25,7 +24,6 @@ var _ = Describe("Store", Ordered, func() { db, err := st.InitDB(cfg, log) Expect(err).To(BeNil()) gormDB = db - sshKey = "ssh-key" store = st.NewStore(db, log.WithField("test", "store")) Expect(store).ToNot(BeNil()) @@ -40,7 +38,7 @@ var _ = Describe("Store", Ordered, func() { ctx, err := store.NewTransactionContext(context.TODO()) Expect(err).To(BeNil()) - source, err := store.Source().Create(ctx, api.SourceCreate{Name: "test", SshKey: &sshKey}) + source, err := store.Source().Create(ctx, uuid.New()) Expect(source).ToNot(BeNil()) Expect(err).To(BeNil()) @@ -58,7 +56,7 @@ var _ = Describe("Store", Ordered, func() { ctx, err := store.NewTransactionContext(context.TODO()) Expect(err).To(BeNil()) - source, err := store.Source().Create(ctx, api.SourceCreate{Name: "test", SshKey: &sshKey}) + source, err := store.Source().Create(ctx, uuid.New()) Expect(source).ToNot(BeNil()) Expect(err).To(BeNil())