Skip to content

Commit 5279dd7

Browse files
committed
NOJIRA: Add terraform support for price adjustments in node template.
1 parent 8b4b0d3 commit 5279dd7

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed

castai/resource_node_template.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ const (
8888
FieldNodeTemplateSharedGpuName = "gpu_name"
8989
FieldNodeTemplateClmEnabled = "clm_enabled"
9090
FieldNodeTemplateEdgeLocationIDs = "edge_location_ids"
91+
FieldNodeTemplatePriceAdjustmentConfiguration = "price_adjustment_configuration"
92+
FieldNodeTemplateInstanceTypeAdjustments = "instance_type_adjustments"
9193
)
9294

9395
const (
@@ -719,6 +721,24 @@ func resourceNodeTemplate() *schema.Resource {
719721
},
720722
Description: "List of edge location IDs to associate with this node template. Must be valid UUIDs referencing castai_edge_location resources.",
721723
},
724+
FieldNodeTemplatePriceAdjustmentConfiguration: {
725+
Type: schema.TypeList,
726+
MaxItems: 1,
727+
Optional: true,
728+
Description: "Configuration for adjusting instance type prices during autoscaling. Adjustments only affect placement decisions, not cost reporting.",
729+
Elem: &schema.Resource{
730+
Schema: map[string]*schema.Schema{
731+
FieldNodeTemplateInstanceTypeAdjustments: {
732+
Type: schema.TypeMap,
733+
Optional: true,
734+
Elem: &schema.Schema{
735+
Type: schema.TypeString,
736+
},
737+
Description: "Map of instance type names to price adjustment multipliers (as strings). Example: {\"r7a.xlarge\": \"1.0\", \"r7i.xlarge\": \"1.20\"}",
738+
},
739+
},
740+
},
741+
},
722742
},
723743
}
724744
}
@@ -814,9 +834,29 @@ func resourceNodeTemplateRead(ctx context.Context, d *schema.ResourceData, meta
814834
}
815835
}
816836

837+
if nodeTemplate.PriceAdjustmentConfiguration != nil {
838+
priceAdjustment := flattenPriceAdjustmentConfiguration(nodeTemplate.PriceAdjustmentConfiguration)
839+
if err := d.Set(FieldNodeTemplatePriceAdjustmentConfiguration, priceAdjustment); err != nil {
840+
return diag.FromErr(fmt.Errorf("setting price adjustment configuration: %w", err))
841+
}
842+
}
843+
817844
return nil
818845
}
819846

847+
func flattenPriceAdjustmentConfiguration(pac *sdk.NodetemplatesV1PriceAdjustmentConfiguration) []map[string]any {
848+
if pac == nil {
849+
return nil
850+
}
851+
852+
out := make(map[string]any)
853+
if pac.InstanceTypeAdjustments != nil {
854+
out[FieldNodeTemplateInstanceTypeAdjustments] = *pac.InstanceTypeAdjustments
855+
}
856+
857+
return []map[string]any{out}
858+
}
859+
820860
func flattenGpuSettings(g *sdk.NodetemplatesV1GPU) ([]map[string]any, error) {
821861
if g == nil {
822862
return nil, nil
@@ -1123,6 +1163,7 @@ func updateNodeTemplate(ctx context.Context, d *schema.ResourceData, meta any, s
11231163
FieldNodeTemplateSharedClientsPerGpu,
11241164
FieldNodeTemplateClmEnabled,
11251165
FieldNodeTemplateEdgeLocationIDs,
1166+
FieldNodeTemplatePriceAdjustmentConfiguration,
11261167
) {
11271168
log.Printf("[INFO] Nothing to update in node template")
11281169
return nil
@@ -1204,6 +1245,12 @@ func updateNodeTemplate(ctx context.Context, d *schema.ResourceData, meta any, s
12041245
req.EdgeLocationIds = toPtr(toStringList(v))
12051246
}
12061247

1248+
if v, ok := d.Get(FieldNodeTemplatePriceAdjustmentConfiguration).([]any); ok && len(v) > 0 {
1249+
req.PriceAdjustmentConfiguration = toPriceAdjustmentConfiguration(v[0].(map[string]any))
1250+
} else if d.HasChange(FieldNodeTemplatePriceAdjustmentConfiguration) {
1251+
req.PriceAdjustmentConfiguration = &sdk.NodetemplatesV1PriceAdjustmentConfiguration{}
1252+
}
1253+
12071254
resp, err := client.NodeTemplatesAPIUpdateNodeTemplateWithResponse(ctx, clusterID, name, req)
12081255
if checkErr := sdk.CheckOKResponse(resp, err); checkErr != nil {
12091256
return diag.FromErr(checkErr)
@@ -1286,6 +1333,10 @@ func resourceNodeTemplateCreate(ctx context.Context, d *schema.ResourceData, met
12861333
req.Gpu = toTemplateGpu(v[0].(map[string]any))
12871334
}
12881335

1336+
if v, ok := d.Get(FieldNodeTemplatePriceAdjustmentConfiguration).([]any); ok && len(v) > 0 {
1337+
req.PriceAdjustmentConfiguration = toPriceAdjustmentConfiguration(v[0].(map[string]any))
1338+
}
1339+
12891340
resp, err := client.NodeTemplatesAPICreateNodeTemplateWithResponse(ctx, clusterID, req)
12901341
if checkErr := sdk.CheckOKResponse(resp, err); checkErr != nil {
12911342
return diag.FromErr(checkErr)
@@ -1788,6 +1839,23 @@ func toTemplateConstraintsNodeAffinity(o map[string]any) *sdk.NodetemplatesV1Tem
17881839
return &out
17891840
}
17901841

1842+
func toPriceAdjustmentConfiguration(obj map[string]any) *sdk.NodetemplatesV1PriceAdjustmentConfiguration {
1843+
if obj == nil {
1844+
return nil
1845+
}
1846+
1847+
out := &sdk.NodetemplatesV1PriceAdjustmentConfiguration{}
1848+
if v, ok := obj[FieldNodeTemplateInstanceTypeAdjustments].(map[string]any); ok && len(v) > 0 {
1849+
adjustments := make(map[string]string)
1850+
for k, val := range v {
1851+
adjustments[k] = val.(string)
1852+
}
1853+
out.InstanceTypeAdjustments = &adjustments
1854+
}
1855+
1856+
return out
1857+
}
1858+
17911859
// compareLists compares state of two lists
17921860
// inspired by https://github.com/hashicorp/terraform-plugin-sdk/issues/477#issuecomment-1238807249
17931861
func compareLists(key, oldValue, newValue string, d *schema.ResourceData) bool {

castai/resource_node_template_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,101 @@ func Test_flattenNodeAffinity(t *testing.T) {
363363
}
364364
}
365365

366+
func Test_flattenPriceAdjustmentConfiguration(t *testing.T) {
367+
tt := []struct {
368+
name string
369+
input *sdk.NodetemplatesV1PriceAdjustmentConfiguration
370+
want []map[string]any
371+
}{
372+
{
373+
name: "nil input returns nil",
374+
input: nil,
375+
want: nil,
376+
},
377+
{
378+
name: "empty configuration",
379+
input: &sdk.NodetemplatesV1PriceAdjustmentConfiguration{},
380+
want: []map[string]any{{}},
381+
},
382+
{
383+
name: "configuration with adjustments",
384+
input: &sdk.NodetemplatesV1PriceAdjustmentConfiguration{
385+
InstanceTypeAdjustments: &map[string]string{
386+
"r7a.xlarge": "1.0",
387+
"r7i.xlarge": "1.20",
388+
"c6a.xlarge": "0.90",
389+
},
390+
},
391+
want: []map[string]any{
392+
{
393+
FieldNodeTemplateInstanceTypeAdjustments: map[string]string{
394+
"r7a.xlarge": "1.0",
395+
"r7i.xlarge": "1.20",
396+
"c6a.xlarge": "0.90",
397+
},
398+
},
399+
},
400+
},
401+
}
402+
403+
for _, tc := range tt {
404+
t.Run(tc.name, func(t *testing.T) {
405+
r := require.New(t)
406+
got := flattenPriceAdjustmentConfiguration(tc.input)
407+
r.Equal(tc.want, got)
408+
})
409+
}
410+
}
411+
412+
func Test_toPriceAdjustmentConfiguration(t *testing.T) {
413+
tt := []struct {
414+
name string
415+
input map[string]any
416+
want *sdk.NodetemplatesV1PriceAdjustmentConfiguration
417+
}{
418+
{
419+
name: "nil input returns nil",
420+
input: nil,
421+
want: nil,
422+
},
423+
{
424+
name: "empty map returns empty configuration",
425+
input: map[string]any{},
426+
want: &sdk.NodetemplatesV1PriceAdjustmentConfiguration{},
427+
},
428+
{
429+
name: "map with adjustments",
430+
input: map[string]any{
431+
FieldNodeTemplateInstanceTypeAdjustments: map[string]any{
432+
"r7a.xlarge": "1.0",
433+
"r7i.xlarge": "1.20",
434+
},
435+
},
436+
want: &sdk.NodetemplatesV1PriceAdjustmentConfiguration{
437+
InstanceTypeAdjustments: &map[string]string{
438+
"r7a.xlarge": "1.0",
439+
"r7i.xlarge": "1.20",
440+
},
441+
},
442+
},
443+
{
444+
name: "empty adjustments map",
445+
input: map[string]any{
446+
FieldNodeTemplateInstanceTypeAdjustments: map[string]any{},
447+
},
448+
want: &sdk.NodetemplatesV1PriceAdjustmentConfiguration{},
449+
},
450+
}
451+
452+
for _, tc := range tt {
453+
t.Run(tc.name, func(t *testing.T) {
454+
r := require.New(t)
455+
got := toPriceAdjustmentConfiguration(tc.input)
456+
r.Equal(tc.want, got)
457+
})
458+
}
459+
}
460+
366461
func TestNodeTemplateResourceReadContextEmptyList(t *testing.T) {
367462
r := require.New(t)
368463
mockctrl := gomock.NewController(t)
@@ -699,6 +794,11 @@ func TestAccEKS_ResourceNodeTemplate_basic(t *testing.T) {
699794
resource.TestCheckResourceAttr(resourceName, "edge_location_ids.#", "2"),
700795
resource.TestCheckResourceAttrSet(resourceName, "edge_location_ids.0"),
701796
resource.TestCheckResourceAttrSet(resourceName, "edge_location_ids.1"),
797+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.#", "1"),
798+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.0.instance_type_adjustments.%", "3"),
799+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.0.instance_type_adjustments.m5.xlarge", "1.0"),
800+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.0.instance_type_adjustments.m5.2xlarge", "1.10"),
801+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.0.instance_type_adjustments.c5.xlarge", "0.95"),
702802
),
703803
},
704804
{
@@ -781,6 +881,10 @@ func TestAccEKS_ResourceNodeTemplate_basic(t *testing.T) {
781881
resource.TestCheckResourceAttr(resourceName, "gpu.0.enable_time_sharing", "false"),
782882
resource.TestCheckResourceAttr(resourceName, "edge_location_ids.#", "1"),
783883
resource.TestCheckResourceAttrSet(resourceName, "edge_location_ids.0"),
884+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.#", "1"),
885+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.0.instance_type_adjustments.%", "2"),
886+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.0.instance_type_adjustments.m5.xlarge", "1.05"),
887+
resource.TestCheckResourceAttr(resourceName, "price_adjustment_configuration.0.instance_type_adjustments.r5.xlarge", "0.90"),
784888
),
785889
},
786890
},
@@ -830,6 +934,14 @@ func testAccNodeTemplateConfig(rName, clusterName string) string {
830934
831935
edge_location_ids = [castai_edge_location.test_1.id, castai_edge_location.test_2.id]
832936
937+
price_adjustment_configuration {
938+
instance_type_adjustments = {
939+
"m5.xlarge" = "1.0"
940+
"m5.2xlarge" = "1.10"
941+
"c5.xlarge" = "0.95"
942+
}
943+
}
944+
833945
constraints {
834946
fallback_restore_rate_seconds = 1800
835947
spot = true
@@ -973,6 +1085,13 @@ func testNodeTemplateUpdated(rName, clusterName string) string {
9731085
9741086
edge_location_ids = [castai_edge_location.test_2.id]
9751087
1088+
price_adjustment_configuration {
1089+
instance_type_adjustments = {
1090+
"m5.xlarge" = "1.05"
1091+
"r5.xlarge" = "0.90"
1092+
}
1093+
}
1094+
9761095
constraints {
9771096
use_spot_fallbacks = true
9781097
spot = true

0 commit comments

Comments
 (0)