From f9ddc50b71fc86f6c4719727034227ff219987c0 Mon Sep 17 00:00:00 2001 From: tautvydasliekis Date: Mon, 28 Aug 2023 13:39:20 +0300 Subject: [PATCH 1/2] FIX: Allow 0 as rebalancing_min_nodes value --- castai/resource_rebalancing_schedule.go | 2 +- castai/resource_rebalancing_schedule_test.go | 29 ++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/castai/resource_rebalancing_schedule.go b/castai/resource_rebalancing_schedule.go index bc7f9a0f..eb30fed5 100644 --- a/castai/resource_rebalancing_schedule.go +++ b/castai/resource_rebalancing_schedule.go @@ -92,7 +92,7 @@ func resourceRebalancingSchedule() *schema.Resource { "rebalancing_min_nodes": { Type: schema.TypeInt, Optional: true, - ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(1)), + ValidateDiagFunc: validation.ToDiagFunc(validation.IntAtLeast(0)), Description: "Minimum number of nodes that should be kept in the cluster after rebalancing.", }, "keep_drain_timeout_nodes": { diff --git a/castai/resource_rebalancing_schedule_test.go b/castai/resource_rebalancing_schedule_test.go index ff022442..d97d1779 100644 --- a/castai/resource_rebalancing_schedule_test.go +++ b/castai/resource_rebalancing_schedule_test.go @@ -43,6 +43,13 @@ func TestAccResourceRebalancingSchedule_basic(t *testing.T) { resource.TestCheckResourceAttr("castai_rebalancing_schedule.test", "schedule.0.cron", "1 4 * * *"), ), }, + { + Config: makeUpdatedMinNodes(rName + " min_nodes_zero"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("castai_rebalancing_schedule.test", "name", rName+" min_nodes_zero"), + resource.TestCheckResourceAttr("castai_rebalancing_schedule.test", "launch_configuration.0.rebalancing_min_nodes", "0"), + ), + }, }, }) } @@ -103,3 +110,25 @@ resource "castai_rebalancing_schedule" "test" { ` return fmt.Sprintf(template, rName) } + +func makeUpdatedMinNodes(rName string) string { + template := ` +resource "castai_rebalancing_schedule" "test" { + name = %q + schedule { + cron = "1 4 * * *" + } + trigger_conditions { + savings_percentage = 1.23456 + } + launch_configuration { + rebalancing_min_nodes = 0 + execution_conditions { + enabled = true + achieved_savings_percentage = 10 + } + } +} +` + return fmt.Sprintf(template, rName) +} From e43006fc1a76806f4a64f8e27e36d219806d8e95 Mon Sep 17 00:00:00 2001 From: tautvydasliekis Date: Mon, 28 Aug 2023 14:20:36 +0300 Subject: [PATCH 2/2] FIX: Allow 0 as rebalancing_min_nodes value --- Makefile | 2 +- castai/sdk/api.gen.go | 78 ++++++++++++++++++++++++++------------- castai/sdk/client.gen.go | 42 ++++++++++----------- castai/sdk/mock/client.go | 70 +++++++++++++++++------------------ e2e/gke_test.go | 4 +- 5 files changed, 111 insertions(+), 85 deletions(-) diff --git a/Makefile b/Makefile index 2f3af05b..0db9fd99 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ init-examples: generate-sdk: @echo "==> Generating castai sdk client" - @API_TAGS=ExternalClusterAPI,PoliciesAPI,NodeConfigurationAPI,NodeTemplatesAPI,AuthTokenAPI,ScheduledRebalancingAPI,InventoryAPI,UsersAPI go generate castai/sdk/generate.go + @API_TAGS=ExternalClusterAPI,PoliciesAPI,NodeConfigurationAPI,NodeTemplatesAPI,AuthTokenAPI,ScheduledRebalancingAPI,InventoryAPI,UsersAPI,OperationsAPI go generate castai/sdk/generate.go # The following command also rewrites existing documentation generate-docs: diff --git a/castai/sdk/api.gen.go b/castai/sdk/api.gen.go index 3f57e21a..aa6da0cf 100644 --- a/castai/sdk/api.gen.go +++ b/castai/sdk/api.gen.go @@ -137,31 +137,6 @@ type NewOrganizationUser struct { UserId string `json:"userId"` } -// OperationResponse defines model for OperationResponse. -type OperationResponse struct { - // Operation creation time in RFC3339Nano format. - CreatedAt time.Time `json:"createdAt"` - - // Indicates whether the operation is done. If 'true', the operation has finished. If 'false', the operation is still in progress. - Done bool `json:"done"` - - // Error details for the operation. Only populated when the operation is done and has failed. If operation has completed successfully, the error will not be set. - Error *struct { - // Human readable caption text describing the error reason. - Details string `json:"details"` - - // Reason is an operation specific failure code. Refer to documentation of the endpoint which generated the long-running operation about possible outcomes. Common error reasons: - // * `internal_error`: An unknown error occurred. Retry the operation. - Reason string `json:"reason"` - } `json:"error,omitempty"` - - // Operation finishe time in RFC3339Nano format. - FinishedAt *time.Time `json:"finishedAt,omitempty"` - - // ID of the operation. - Id string `json:"id"` -} - // Organization defines model for Organization. type Organization struct { // Organization ID @@ -528,6 +503,34 @@ type CastaiMetricsV1beta1ClusterMetrics struct { SpotNodesCount *int32 `json:"spotNodesCount,omitempty"` } +// Operation object. +type CastaiOperationsV1beta1Operation struct { + // Operation creation timestamp in RFC3339Nano format. + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // Indicates whether the operation has finished or not. If 'false', the operation is still in progress. If 'true', + // the has finished. + Done *bool `json:"done,omitempty"` + + // OperationError object. + Error *CastaiOperationsV1beta1OperationError `json:"error,omitempty"` + + // Operation finish timestamp in RFC3339Nano format. + FinishedAt *time.Time `json:"finishedAt,omitempty"` + + // ID of the operation. + Id *string `json:"id,omitempty"` +} + +// OperationError object. +type CastaiOperationsV1beta1OperationError struct { + // Details is a concise human readable explanation for the error. + Details *string `json:"details,omitempty"` + + // Reason is an operation specific failure code. Refer to documentation about possible outcomes. + Reason *string `json:"reason,omitempty"` +} + // Types of cloud service providers CAST AI supports. // // - invalid: Invalid. @@ -637,7 +640,8 @@ type ExternalclusterV1Cluster struct { ProviderType *string `json:"providerType,omitempty"` // Shows last reconcile error if any. - ReconcileError *string `json:"reconcileError"` + ReconcileError *string `json:"reconcileError"` + ReconcileInfo *ExternalclusterV1ClusterReconcileInfo `json:"reconcileInfo,omitempty"` // Timestamp when the last reconcile was performed. ReconciledAt *time.Time `json:"reconciledAt"` @@ -658,6 +662,28 @@ type ExternalclusterV1Cluster struct { Zones *[]ExternalclusterV1Zone `json:"zones,omitempty"` } +// ExternalclusterV1ClusterReconcileInfo defines model for externalcluster.v1.Cluster.ReconcileInfo. +type ExternalclusterV1ClusterReconcileInfo struct { + // Shows last reconcile error if any. + Error *string `json:"error"` + + // Number of times the reconcile was retried. + ErrorCount *int32 `json:"errorCount,omitempty"` + Mode *string `json:"mode,omitempty"` + + // Timestamp when the last reconcile was performed. + ReconciledAt *time.Time `json:"reconciledAt"` + + // Timestamp when the reconcile was retried. + RetryAt *time.Time `json:"retryAt"` + + // Timestamp when the reconcile was started. + StartedAt *time.Time `json:"startedAt"` + + // Reconcile status. + Status *string `json:"status"` +} + // ExternalclusterV1ClusterUpdate defines model for externalcluster.v1.ClusterUpdate. type ExternalclusterV1ClusterUpdate struct { // JSON encoded cluster credentials string. diff --git a/castai/sdk/client.gen.go b/castai/sdk/client.gen.go index 528c5a82..d4a23056 100644 --- a/castai/sdk/client.gen.go +++ b/castai/sdk/client.gen.go @@ -208,8 +208,8 @@ type ClientInterface interface { ExternalClusterAPIRegisterCluster(ctx context.Context, body ExternalClusterAPIRegisterClusterJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) - // GetExternalClusterOperation request - GetExternalClusterOperation(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) + // OperationsAPIGetOperation request + OperationsAPIGetOperation(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) // ExternalClusterAPIDeleteCluster request ExternalClusterAPIDeleteCluster(ctx context.Context, clusterId string, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -900,8 +900,8 @@ func (c *Client) ExternalClusterAPIRegisterCluster(ctx context.Context, body Ext return c.Client.Do(req) } -func (c *Client) GetExternalClusterOperation(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { - req, err := NewGetExternalClusterOperationRequest(c.Server, id) +func (c *Client) OperationsAPIGetOperation(ctx context.Context, id string, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewOperationsAPIGetOperationRequest(c.Server, id) if err != nil { return nil, err } @@ -2898,8 +2898,8 @@ func NewExternalClusterAPIRegisterClusterRequestWithBody(server string, contentT return req, nil } -// NewGetExternalClusterOperationRequest generates requests for GetExternalClusterOperation -func NewGetExternalClusterOperationRequest(server string, id string) (*http.Request, error) { +// NewOperationsAPIGetOperationRequest generates requests for OperationsAPIGetOperation +func NewOperationsAPIGetOperationRequest(server string, id string) (*http.Request, error) { var err error var pathParam0 string @@ -4928,8 +4928,8 @@ type ClientWithResponsesInterface interface { ExternalClusterAPIRegisterClusterWithResponse(ctx context.Context, body ExternalClusterAPIRegisterClusterJSONRequestBody) (*ExternalClusterAPIRegisterClusterResponse, error) - // GetExternalClusterOperation request - GetExternalClusterOperationWithResponse(ctx context.Context, id string) (*GetExternalClusterOperationResponse, error) + // OperationsAPIGetOperation request + OperationsAPIGetOperationWithResponse(ctx context.Context, id string) (*OperationsAPIGetOperationResponse, error) // ExternalClusterAPIDeleteCluster request ExternalClusterAPIDeleteClusterWithResponse(ctx context.Context, clusterId string) (*ExternalClusterAPIDeleteClusterResponse, error) @@ -6001,14 +6001,14 @@ func (r ExternalClusterAPIRegisterClusterResponse) GetBody() []byte { // TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 -type GetExternalClusterOperationResponse struct { +type OperationsAPIGetOperationResponse struct { Body []byte HTTPResponse *http.Response - JSON200 *OperationResponse + JSON200 *CastaiOperationsV1beta1Operation } // Status returns HTTPResponse.Status -func (r GetExternalClusterOperationResponse) Status() string { +func (r OperationsAPIGetOperationResponse) Status() string { if r.HTTPResponse != nil { return r.HTTPResponse.Status } @@ -6016,7 +6016,7 @@ func (r GetExternalClusterOperationResponse) Status() string { } // StatusCode returns HTTPResponse.StatusCode -func (r GetExternalClusterOperationResponse) StatusCode() int { +func (r OperationsAPIGetOperationResponse) StatusCode() int { if r.HTTPResponse != nil { return r.HTTPResponse.StatusCode } @@ -6025,7 +6025,7 @@ func (r GetExternalClusterOperationResponse) StatusCode() int { // TODO: to have common interface. https://github.com/deepmap/oapi-codegen/issues/240 // Body returns body of byte array -func (r GetExternalClusterOperationResponse) GetBody() []byte { +func (r OperationsAPIGetOperationResponse) GetBody() []byte { return r.Body } @@ -7730,13 +7730,13 @@ func (c *ClientWithResponses) ExternalClusterAPIRegisterClusterWithResponse(ctx return ParseExternalClusterAPIRegisterClusterResponse(rsp) } -// GetExternalClusterOperationWithResponse request returning *GetExternalClusterOperationResponse -func (c *ClientWithResponses) GetExternalClusterOperationWithResponse(ctx context.Context, id string) (*GetExternalClusterOperationResponse, error) { - rsp, err := c.GetExternalClusterOperation(ctx, id) +// OperationsAPIGetOperationWithResponse request returning *OperationsAPIGetOperationResponse +func (c *ClientWithResponses) OperationsAPIGetOperationWithResponse(ctx context.Context, id string) (*OperationsAPIGetOperationResponse, error) { + rsp, err := c.OperationsAPIGetOperation(ctx, id) if err != nil { return nil, err } - return ParseGetExternalClusterOperationResponse(rsp) + return ParseOperationsAPIGetOperationResponse(rsp) } // ExternalClusterAPIDeleteClusterWithResponse request returning *ExternalClusterAPIDeleteClusterResponse @@ -9027,22 +9027,22 @@ func ParseExternalClusterAPIRegisterClusterResponse(rsp *http.Response) (*Extern return response, nil } -// ParseGetExternalClusterOperationResponse parses an HTTP response from a GetExternalClusterOperationWithResponse call -func ParseGetExternalClusterOperationResponse(rsp *http.Response) (*GetExternalClusterOperationResponse, error) { +// ParseOperationsAPIGetOperationResponse parses an HTTP response from a OperationsAPIGetOperationWithResponse call +func ParseOperationsAPIGetOperationResponse(rsp *http.Response) (*OperationsAPIGetOperationResponse, error) { bodyBytes, err := ioutil.ReadAll(rsp.Body) defer rsp.Body.Close() if err != nil { return nil, err } - response := &GetExternalClusterOperationResponse{ + response := &OperationsAPIGetOperationResponse{ Body: bodyBytes, HTTPResponse: rsp, } switch { case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: - var dest OperationResponse + var dest CastaiOperationsV1beta1Operation if err := json.Unmarshal(bodyBytes, &dest); err != nil { return nil, err } diff --git a/castai/sdk/mock/client.go b/castai/sdk/mock/client.go index 7f32c6df..0d9c5e36 100644 --- a/castai/sdk/mock/client.go +++ b/castai/sdk/mock/client.go @@ -995,26 +995,6 @@ func (mr *MockClientInterfaceMockRecorder) ExternalClusterAPIUpdateClusterWithBo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExternalClusterAPIUpdateClusterWithBody", reflect.TypeOf((*MockClientInterface)(nil).ExternalClusterAPIUpdateClusterWithBody), varargs...) } -// GetExternalClusterOperation mocks base method. -func (m *MockClientInterface) GetExternalClusterOperation(ctx context.Context, id string, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { - m.ctrl.T.Helper() - varargs := []interface{}{ctx, id} - for _, a := range reqEditors { - varargs = append(varargs, a) - } - ret := m.ctrl.Call(m, "GetExternalClusterOperation", varargs...) - ret0, _ := ret[0].(*http.Response) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetExternalClusterOperation indicates an expected call of GetExternalClusterOperation. -func (mr *MockClientInterfaceMockRecorder) GetExternalClusterOperation(ctx, id interface{}, reqEditors ...interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - varargs := append([]interface{}{ctx, id}, reqEditors...) - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExternalClusterOperation", reflect.TypeOf((*MockClientInterface)(nil).GetExternalClusterOperation), varargs...) -} - // GetOrganization mocks base method. func (m *MockClientInterface) GetOrganization(ctx context.Context, id string, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -1595,6 +1575,26 @@ func (mr *MockClientInterfaceMockRecorder) NodeTemplatesAPIUpdateNodeTemplateWit return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeTemplatesAPIUpdateNodeTemplateWithBody", reflect.TypeOf((*MockClientInterface)(nil).NodeTemplatesAPIUpdateNodeTemplateWithBody), varargs...) } +// OperationsAPIGetOperation mocks base method. +func (m *MockClientInterface) OperationsAPIGetOperation(ctx context.Context, id string, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { + m.ctrl.T.Helper() + varargs := []interface{}{ctx, id} + for _, a := range reqEditors { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "OperationsAPIGetOperation", varargs...) + ret0, _ := ret[0].(*http.Response) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OperationsAPIGetOperation indicates an expected call of OperationsAPIGetOperation. +func (mr *MockClientInterfaceMockRecorder) OperationsAPIGetOperation(ctx, id interface{}, reqEditors ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{ctx, id}, reqEditors...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OperationsAPIGetOperation", reflect.TypeOf((*MockClientInterface)(nil).OperationsAPIGetOperation), varargs...) +} + // PoliciesAPIGetClusterNodeConstraints mocks base method. func (m *MockClientInterface) PoliciesAPIGetClusterNodeConstraints(ctx context.Context, clusterId string, reqEditors ...sdk.RequestEditorFn) (*http.Response, error) { m.ctrl.T.Helper() @@ -2848,21 +2848,6 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) ExternalClusterAPIUpdate return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExternalClusterAPIUpdateClusterWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).ExternalClusterAPIUpdateClusterWithResponse), ctx, clusterId, body) } -// GetExternalClusterOperationWithResponse mocks base method. -func (m *MockClientWithResponsesInterface) GetExternalClusterOperationWithResponse(ctx context.Context, id string) (*sdk.GetExternalClusterOperationResponse, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetExternalClusterOperationWithResponse", ctx, id) - ret0, _ := ret[0].(*sdk.GetExternalClusterOperationResponse) - ret1, _ := ret[1].(error) - return ret0, ret1 -} - -// GetExternalClusterOperationWithResponse indicates an expected call of GetExternalClusterOperationWithResponse. -func (mr *MockClientWithResponsesInterfaceMockRecorder) GetExternalClusterOperationWithResponse(ctx, id interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetExternalClusterOperationWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).GetExternalClusterOperationWithResponse), ctx, id) -} - // GetOrganizationUsersWithResponse mocks base method. func (m *MockClientWithResponsesInterface) GetOrganizationUsersWithResponse(ctx context.Context, id string) (*sdk.GetOrganizationUsersResponse, error) { m.ctrl.T.Helper() @@ -3298,6 +3283,21 @@ func (mr *MockClientWithResponsesInterfaceMockRecorder) NodeTemplatesAPIUpdateNo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NodeTemplatesAPIUpdateNodeTemplateWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).NodeTemplatesAPIUpdateNodeTemplateWithResponse), ctx, clusterId, nodeTemplateName, body) } +// OperationsAPIGetOperationWithResponse mocks base method. +func (m *MockClientWithResponsesInterface) OperationsAPIGetOperationWithResponse(ctx context.Context, id string) (*sdk.OperationsAPIGetOperationResponse, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OperationsAPIGetOperationWithResponse", ctx, id) + ret0, _ := ret[0].(*sdk.OperationsAPIGetOperationResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OperationsAPIGetOperationWithResponse indicates an expected call of OperationsAPIGetOperationWithResponse. +func (mr *MockClientWithResponsesInterfaceMockRecorder) OperationsAPIGetOperationWithResponse(ctx, id interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OperationsAPIGetOperationWithResponse", reflect.TypeOf((*MockClientWithResponsesInterface)(nil).OperationsAPIGetOperationWithResponse), ctx, id) +} + // PoliciesAPIGetClusterNodeConstraintsWithResponse mocks base method. func (m *MockClientWithResponsesInterface) PoliciesAPIGetClusterNodeConstraintsWithResponse(ctx context.Context, clusterId string) (*sdk.PoliciesAPIGetClusterNodeConstraintsResponse, error) { m.ctrl.T.Helper() diff --git a/e2e/gke_test.go b/e2e/gke_test.go index b62ab03a..6959b9ea 100644 --- a/e2e/gke_test.go +++ b/e2e/gke_test.go @@ -51,11 +51,11 @@ func TestTerraformGKEOnboarding(t *testing.T) { fmt.Println("Waiting for node to be added") lastBodyForOp := "" r.Eventually(func() bool { - opStatus, err := castAIClient.GetExternalClusterOperationWithResponse(ctx, addNode.JSON200.OperationId) + opStatus, err := castAIClient.OperationsAPIGetOperationWithResponse(ctx, addNode.JSON200.OperationId) r.NoError(err) lastBodyForOp = string(opStatus.Body) r.False(opStatus.JSON200 != nil && opStatus.JSON200.Error != nil, fmt.Sprintf("Error while waiting for operation end. body: %s", lastBodyForOp)) - return opStatus.JSON200 != nil && opStatus.JSON200.Done + return opStatus.JSON200 != nil && opStatus.JSON200.Done != nil && *opStatus.JSON200.Done }, time.Minute*5, time.Second*15, fmt.Sprintf("waiting for add node operation timeout. body: %s, opID: %s", lastBodyForOp, addNode.JSON200.OperationId))