Skip to content

Commit

Permalink
Skip deletion of default node template + Add is_enabled flat to node …
Browse files Browse the repository at this point in the history
…template (#207)

* feat: Skip deletion of default node template

* feat: Add is_enabled flat to node template
  • Loading branch information
jansyk13 authored Aug 3, 2023
1 parent 1e5369e commit 88b6ed4
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 0 deletions.
31 changes: 31 additions & 0 deletions castai/resource_node_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
FieldNodeTemplateIncludeNames = "include_names"
FieldNodeTemplateInstanceFamilies = "instance_families"
FieldNodeTemplateIsDefault = "is_default"
FieldNodeTemplateIsEnabled = "is_enabled"
FieldNodeTemplateIsGpuOnly = "is_gpu_only"
FieldNodeTemplateManufacturers = "manufacturers"
FieldNodeTemplateMaxCount = "max_count"
Expand Down Expand Up @@ -92,6 +93,12 @@ func resourceNodeTemplate() *schema.Resource {
ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotWhiteSpace),
Description: "Name of the node template.",
},
FieldNodeTemplateIsEnabled: {
Type: schema.TypeBool,
Optional: true,
Computed: true,
Description: "Flag whether the node template is enabled and considered for autoscaling.",
},
FieldNodeTemplateIsDefault: {
Type: schema.TypeBool,
Optional: true,
Expand Down Expand Up @@ -390,6 +397,9 @@ func resourceNodeTemplateRead(ctx context.Context, d *schema.ResourceData, meta
if err := d.Set(FieldNodeTemplateName, nodeTemplate.Name); err != nil {
return diag.FromErr(fmt.Errorf("setting name: %w", err))
}
if err := d.Set(FieldNodeTemplateIsEnabled, nodeTemplate.IsEnabled); err != nil {
return diag.FromErr(fmt.Errorf("setting is enabled: %w", err))
}
if err := d.Set(FieldNodeTemplateIsDefault, nodeTemplate.IsDefault); err != nil {
return diag.FromErr(fmt.Errorf("setting is default: %w", err))
}
Expand Down Expand Up @@ -535,6 +545,18 @@ func resourceNodeTemplateDelete(ctx context.Context, d *schema.ResourceData, met
clusterID := d.Get(FieldClusterID).(string)
name := d.Get(FieldNodeTemplateName).(string)

if isDefault, ok := d.Get(FieldNodeTemplateIsDefault).(bool); ok && isDefault {
return diag.Diagnostics{
{
Severity: diag.Warning,
Summary: fmt.Sprintf("Skipping delete of \"%s\" node template", name),
Detail: "Default node templates cannot be deleted from CAST.ai. If you want to autoscaler to stop " +
"considering this node template, you can disable it (either from UI or by setting `is_enabled` " +
"flag to false).",
},
}
}

resp, err := client.NodeTemplatesAPIDeleteNodeTemplateWithResponse(ctx, clusterID, name)
if checkErr := sdk.CheckOKResponse(resp, err); checkErr != nil {
return diag.FromErr(checkErr)
Expand All @@ -554,6 +576,7 @@ func resourceNodeTemplateUpdate(ctx context.Context, d *schema.ResourceData, met
FieldNodeTemplateCustomTaints,
FieldNodeTemplateCustomInstancesEnabled,
FieldNodeTemplateConstraints,
FieldNodeTemplateIsEnabled,
) {
log.Printf("[INFO] Nothing to update in node configuration")
return nil
Expand All @@ -568,6 +591,10 @@ func resourceNodeTemplateUpdate(ctx context.Context, d *schema.ResourceData, met
req.IsDefault = toPtr(v.(bool))
}

if v, ok := d.GetOk(FieldNodeTemplateIsEnabled); ok {
req.IsEnabled = toPtr(v.(bool))
}

if v, ok := d.GetOk(FieldNodeTemplateConfigurationId); ok {
req.ConfigurationId = toPtr(v.(string))
}
Expand Down Expand Up @@ -639,6 +666,10 @@ func resourceNodeTemplateCreate(ctx context.Context, d *schema.ResourceData, met
ShouldTaint: lo.ToPtr(d.Get(FieldNodeTemplateShouldTaint).(bool)),
}

if v, ok := d.GetOk(FieldNodeTemplateIsEnabled); ok {
req.IsEnabled = lo.ToPtr(v.(bool))
}

if v, ok := d.Get(FieldNodeTemplateRebalancingConfigMinNodes).(int32); ok {
req.RebalancingConfig = &sdk.NodetemplatesV1RebalancingConfiguration{
MinNodes: lo.ToPtr(v),
Expand Down
76 changes: 76 additions & 0 deletions castai/resource_node_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/golang/mock/gomock"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
Expand Down Expand Up @@ -41,6 +42,7 @@ func TestNodeTemplateResourceReadContext(t *testing.T) {
"template": {
"configurationId": "7dc4f922-29c9-4377-889c-0c8c5fb8d497",
"configurationName": "default",
"isEnabled": true,
"name": "gpu",
"constraints": {
"spot": false,
Expand Down Expand Up @@ -171,6 +173,7 @@ custom_taints.1.effect = NoSchedule
custom_taints.1.key = some-key-2
custom_taints.1.value = some-value-2
is_default = false
is_enabled = true
name = gpu
rebalancing_config_min_nodes = 0
should_taint = true
Expand Down Expand Up @@ -211,6 +214,77 @@ func TestNodeTemplateResourceReadContextEmptyList(t *testing.T) {
r.Equal(result[0].Summary, "failed to find node template with name: gpu")
}

func TestNodeTemplateResourceDelete_defaultNodeTemplate(t *testing.T) {
r := require.New(t)
mockctrl := gomock.NewController(t)
mockClient := mock_sdk.NewMockClientInterface(mockctrl)

ctx := context.Background()
provider := &ProviderConfig{
api: &sdk.ClientWithResponses{
ClientInterface: mockClient,
},
}

clusterId := "b6bfc074-a267-400f-b8f1-db0850c369b1"
body := io.NopCloser(bytes.NewReader([]byte(`
{
"items": [
{
"template": {
"configurationId": "7dc4f922-29c9-4377-889c-0c8c5fb8d497",
"configurationName": "default",
"name": "default-by-castai",
"isEnabled": true,
"isDefault": true,
"constraints": {
"spot": false,
"onDemand": true,
"minCpu": 10,
"maxCpu": 10000,
"architectures": ["amd64", "arm64"]
},
"version": "3",
"shouldTaint": true,
"customLabels": {},
"customTaints": [],
"rebalancingConfig": {
"minNodes": 0
},
"customInstancesEnabled": true
}
}
]
}
`)))
mockClient.EXPECT().
NodeTemplatesAPIListNodeTemplates(gomock.Any(), clusterId, &sdk.NodeTemplatesAPIListNodeTemplatesParams{IncludeDefault: lo.ToPtr(true)}).
Return(&http.Response{StatusCode: 200, Body: body, Header: map[string][]string{"Content-Type": {"json"}}}, nil)

resource := resourceNodeTemplate()
val := cty.ObjectVal(map[string]cty.Value{
FieldClusterId: cty.StringVal(clusterId),
FieldNodeTemplateName: cty.StringVal("default-by-castai"),
})
state := terraform.NewInstanceStateShimmedFromValue(val, 0)
state.ID = "default-by-castai"

data := resource.Data(state)
result := resource.ReadContext(ctx, data, provider)
r.Nil(result)
r.False(result.HasError())

result = resource.DeleteContext(ctx, data, provider)
r.NotNil(result)
r.Len(result, 1)
r.False(result.HasError())
r.Equal(diag.Warning, result[0].Severity)
r.Equal("Skipping delete of \"default-by-castai\" node template", result[0].Summary)
r.Equal("Default node templates cannot be deleted from CAST.ai. If you want to autoscaler to stop"+
" considering this node template, you can disable it (either from UI or by setting `is_enabled` flag to"+
" false).", result[0].Detail)
}

func TestAccResourceNodeTemplate_basic(t *testing.T) {
rName := fmt.Sprintf("%v-node-template-%v", ResourcePrefix, acctest.RandString(8))
resourceName := "castai_node_template.test"
Expand All @@ -225,6 +299,7 @@ func TestAccResourceNodeTemplate_basic(t *testing.T) {
Config: testAccNodeTemplateConfig(rName, clusterName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "is_enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "should_taint", "true"),
resource.TestCheckResourceAttr(resourceName, "custom_instances_enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "custom_label.#", "0"),
Expand Down Expand Up @@ -267,6 +342,7 @@ func TestAccResourceNodeTemplate_basic(t *testing.T) {
Config: testNodeTemplateUpdated(rName, clusterName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rName),
resource.TestCheckResourceAttr(resourceName, "is_enabled", "true"),
resource.TestCheckResourceAttr(resourceName, "should_taint", "true"),
resource.TestCheckResourceAttr(resourceName, "custom_instances_enabled", "false"),
resource.TestCheckResourceAttr(resourceName, "custom_label.#", "0"),
Expand Down
1 change: 1 addition & 0 deletions docs/resources/node_template.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 88b6ed4

Please sign in to comment.