Skip to content

Commit d04ca3f

Browse files
author
Antoine Belluard
committed
feat(container/serverless): add scaling_option block
1 parent 7c34968 commit d04ca3f

File tree

8 files changed

+4732
-2
lines changed

8 files changed

+4732
-2
lines changed

docs/data-sources/container.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,12 @@ In addition to all arguments above, the following attributes are exported:
9191

9292
- `deploy` - Boolean indicating whether the container is on a production environment.
9393

94-
- `sandbox` - (Optional) Execution environment of the container.
94+
- `sandbox` - Execution environment of the container.
95+
96+
- `scaling_option` - Configuration block used to decide when to scale up or down. Possible values:
97+
- `concurrent_requests_threshold` - Scale depending on the number of concurrent requests being processed per container instance.
98+
- `cpu_usage_threshold` - Scale depending on the CPU usage of a container instance.
99+
- `memory_usage_threshold`- Scale depending on the memory usage of a container instance.
95100

96101
- `status` - The container status.
97102

docs/resources/container.md

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ The following arguments are supported:
8484

8585
- `sandbox` - (Optional) Execution environment of the container.
8686

87+
- `scaling_option` - (Optional) Configuration block used to decide when to scale up or down. Possible values:
88+
- `concurrent_requests_threshold` - Scale depending on the number of concurrent requests being processed per container instance.
89+
- `cpu_usage_threshold` - Scale depending on the CPU usage of a container instance.
90+
- `memory_usage_threshold`- Scale depending on the memory usage of a container instance.
91+
8792
- `port` - (Optional) The port to expose the container.
8893

8994
- `deploy` - (Optional) Boolean indicating whether the container is in a production environment.
@@ -152,4 +157,25 @@ The `memory_limit` (in MB) must correspond with the right amount of vCPU. Refer
152157
| 4096 | 2240 |
153158

154159
~>**Important:** Make sure to select the right resources, as you will be billed based on compute usage over time and the number of Containers executions.
155-
Refer to the [Serverless Containers pricing](https://www.scaleway.com/en/docs/faq/serverless-containers/#prices) for more information.
160+
Refer to the [Serverless Containers pricing](https://www.scaleway.com/en/docs/faq/serverless-containers/#prices) for more information.
161+
162+
## Scaling option configuration
163+
164+
Scaling option block configuration allows you to choose which parameter will scale up/down containers. Options are number of concurrent requests, CPU or memory usage.
165+
It replaces current `max_concurrency` that has been deprecated.
166+
167+
Example:
168+
169+
```terraform
170+
resource scaleway_container main {
171+
name = "my-container-02"
172+
namespace_id = scaleway_container_namespace.main.id
173+
174+
scaling_option {
175+
concurrent_requests_threshold = 15
176+
}
177+
}
178+
```
179+
180+
~>**Important**: A maximum of one of these parameters may be set. Also, when `cpu_usage_threshold` or `memory_usage_threshold` are used, `min_scale` can't be set to 0.
181+
Refer to the [API Reference](https://www.scaleway.com/en/developers/api/serverless-containers/#path-containers-create-a-new-container) for more information.

internal/services/container/container.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ func ResourceContainer() *schema.Resource {
129129
Type: schema.TypeInt,
130130
Optional: true,
131131
Computed: true,
132+
Deprecated: "Use scaling_option.concurrent_requests_threshold instead. This attribute will be removed.",
132133
Description: "The maximum the number of simultaneous requests your container can handle at the same time.",
133134
ValidateFunc: validation.IntAtMost(containerMaxConcurrencyLimit),
134135
},
@@ -170,6 +171,31 @@ func ResourceContainer() *schema.Resource {
170171
Description: "Execution environment of the container.",
171172
ValidateDiagFunc: verify.ValidateEnum[container.ContainerSandbox](),
172173
},
174+
"scaling_option": {
175+
Type: schema.TypeSet,
176+
Optional: true,
177+
Computed: true,
178+
Description: "Configuration used to decide when to scale up or down.",
179+
Elem: &schema.Resource{
180+
Schema: map[string]*schema.Schema{
181+
"concurrent_requests_threshold": {
182+
Type: schema.TypeInt,
183+
Description: "Scale depending on the number of concurrent requests being processed per container instance.",
184+
Optional: true,
185+
},
186+
"cpu_usage_threshold": {
187+
Type: schema.TypeInt,
188+
Description: "Scale depending on the CPU usage of a container instance.",
189+
Optional: true,
190+
},
191+
"memory_usage_threshold": {
192+
Type: schema.TypeInt,
193+
Description: "Scale depending on the memory usage of a container instance.",
194+
Optional: true,
195+
},
196+
},
197+
},
198+
},
173199
// computed
174200
"status": {
175201
Type: schema.TypeString,
@@ -280,6 +306,7 @@ func ResourceContainerRead(ctx context.Context, d *schema.ResourceData, m interf
280306
_ = d.Set("deploy", scw.BoolPtr(*types.ExpandBoolPtr(d.Get("deploy"))))
281307
_ = d.Set("http_option", co.HTTPOption)
282308
_ = d.Set("sandbox", co.Sandbox)
309+
_ = d.Set("scaling_option", flattenScalingOption(co.ScalingOption))
283310
_ = d.Set("region", co.Region.String())
284311

285312
return nil
@@ -375,6 +402,16 @@ func ResourceContainerUpdate(ctx context.Context, d *schema.ResourceData, m inte
375402
req.Sandbox = container.ContainerSandbox(d.Get("sandbox").(string))
376403
}
377404

405+
if d.HasChanges("scaling_option") {
406+
scalingOption := d.Get("scaling_option")
407+
408+
scalingOptionReq, err := expandScalingOptions(scalingOption)
409+
if err != nil {
410+
return diag.FromErr(err)
411+
}
412+
req.ScalingOption = scalingOptionReq
413+
}
414+
378415
imageHasChanged := d.HasChanges("registry_sha256")
379416
if imageHasChanged {
380417
req.Redeploy = &imageHasChanged

internal/services/container/container_data_source_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,38 @@ func TestAccDataSourceContainer_Basic(t *testing.T) {
4949
},
5050
})
5151
}
52+
53+
func TestAccDataSourceContainer_ScalingOption(t *testing.T) {
54+
tt := acctest.NewTestTools(t)
55+
defer tt.Cleanup()
56+
resource.ParallelTest(t, resource.TestCase{
57+
PreCheck: func() { acctest.PreCheck(t) },
58+
ProviderFactories: tt.ProviderFactories,
59+
CheckDestroy: isNamespaceDestroyed(tt),
60+
Steps: []resource.TestStep{
61+
{
62+
Config: `
63+
resource scaleway_container_namespace main {}
64+
65+
resource scaleway_container main {
66+
namespace_id = scaleway_container_namespace.main.id
67+
deploy = false
68+
}
69+
70+
data scaleway_container main {
71+
namespace_id = scaleway_container_namespace.main.id
72+
container_id = scaleway_container.main.id
73+
}
74+
`,
75+
Check: resource.ComposeTestCheckFunc(
76+
isContainerPresent(tt, "scaleway_container.main"),
77+
// Check default option returned by the API when you don't specify the scaling_option block.
78+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
79+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
80+
resource.TestCheckResourceAttr("data.scaleway_container.main", "scaling_option.#", "1"),
81+
resource.TestCheckResourceAttr("data.scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
82+
),
83+
},
84+
},
85+
})
86+
}

internal/services/container/container_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,95 @@ func TestAccContainer_Sandbox(t *testing.T) {
400400
})
401401
}
402402

403+
func TestAccContainer_ScalingOption(t *testing.T) {
404+
tt := acctest.NewTestTools(t)
405+
defer tt.Cleanup()
406+
resource.ParallelTest(t, resource.TestCase{
407+
PreCheck: func() { acctest.PreCheck(t) },
408+
ProviderFactories: tt.ProviderFactories,
409+
CheckDestroy: isContainerDestroyed(tt),
410+
Steps: []resource.TestStep{
411+
{
412+
Config: `
413+
resource scaleway_container_namespace main {}
414+
415+
resource scaleway_container main {
416+
namespace_id = scaleway_container_namespace.main.id
417+
deploy = false
418+
}
419+
`,
420+
Check: resource.ComposeTestCheckFunc(
421+
isContainerPresent(tt, "scaleway_container.main"),
422+
// Check default option returned by the API when you don't specify the scaling_option block.
423+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
424+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
425+
),
426+
},
427+
{
428+
Config: `
429+
resource scaleway_container_namespace main {}
430+
431+
resource scaleway_container main {
432+
namespace_id = scaleway_container_namespace.main.id
433+
deploy = false
434+
435+
scaling_option {
436+
concurrent_requests_threshold = 15
437+
}
438+
}
439+
`,
440+
Check: resource.ComposeTestCheckFunc(
441+
isContainerPresent(tt, "scaleway_container.main"),
442+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
443+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "15"),
444+
),
445+
},
446+
{
447+
Config: `
448+
resource scaleway_container_namespace main {}
449+
450+
resource scaleway_container main {
451+
namespace_id = scaleway_container_namespace.main.id
452+
deploy = false
453+
454+
min_scale = 1
455+
456+
scaling_option {
457+
cpu_usage_threshold = 72
458+
}
459+
}
460+
`,
461+
Check: resource.ComposeTestCheckFunc(
462+
isContainerPresent(tt, "scaleway_container.main"),
463+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
464+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.cpu_usage_threshold", "72"),
465+
),
466+
},
467+
468+
{
469+
Config: `
470+
resource scaleway_container_namespace main {}
471+
472+
resource scaleway_container main {
473+
namespace_id = scaleway_container_namespace.main.id
474+
deploy = false
475+
476+
min_scale = 1
477+
478+
scaling_option {
479+
memory_usage_threshold = 66
480+
}
481+
}
482+
`,
483+
Check: resource.ComposeTestCheckFunc(
484+
isContainerPresent(tt, "scaleway_container.main"),
485+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.memory_usage_threshold", "66"),
486+
),
487+
},
488+
},
489+
})
490+
}
491+
403492
func isContainerPresent(tt *acctest.TestTools, n string) resource.TestCheckFunc {
404493
return func(state *terraform.State) error {
405494
rs, ok := state.RootModule().Resources[n]

internal/services/container/helpers_container.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,9 +115,68 @@ func setCreateContainerRequest(d *schema.ResourceData, region scw.Region) (*cont
115115
req.Sandbox = container.ContainerSandbox(sandbox.(string))
116116
}
117117

118+
if scalingOption, ok := d.GetOk("scaling_option"); ok {
119+
scalingOptionReq, err := expandScalingOptions(scalingOption)
120+
if err != nil {
121+
return nil, err
122+
}
123+
req.ScalingOption = scalingOptionReq
124+
}
125+
118126
return req, nil
119127
}
120128

129+
func expandScalingOptions(scalingOptionSchema interface{}) (*container.ContainerScalingOption, error) {
130+
scalingOption, ok := scalingOptionSchema.(*schema.Set)
131+
if !ok {
132+
return &container.ContainerScalingOption{}, nil
133+
}
134+
135+
for _, option := range scalingOption.List() {
136+
rawOption, isRawOption := option.(map[string]interface{})
137+
if !isRawOption {
138+
continue
139+
}
140+
141+
setFields := 0
142+
cso := &container.ContainerScalingOption{}
143+
if concurrentRequestThresold, ok := rawOption["concurrent_requests_threshold"].(int); ok && concurrentRequestThresold != 0 {
144+
cso.ConcurrentRequestsThreshold = scw.Uint32Ptr(uint32(concurrentRequestThresold))
145+
setFields++
146+
}
147+
if cpuUsageThreshold, ok := rawOption["cpu_usage_threshold"].(int); ok && cpuUsageThreshold != 0 {
148+
cso.CPUUsageThreshold = scw.Uint32Ptr(uint32(cpuUsageThreshold))
149+
setFields++
150+
}
151+
if memoryUsageThreshold, ok := rawOption["memory_usage_threshold"].(int); ok && memoryUsageThreshold != 0 {
152+
cso.MemoryUsageThreshold = scw.Uint32Ptr(uint32(memoryUsageThreshold))
153+
setFields++
154+
}
155+
156+
if setFields > 1 {
157+
return &container.ContainerScalingOption{}, errors.New("a maximum of one scaling option can be set")
158+
}
159+
return cso, nil
160+
}
161+
162+
return &container.ContainerScalingOption{}, nil
163+
}
164+
165+
func flattenScalingOption(scalingOption *container.ContainerScalingOption) interface{} {
166+
if scalingOption == nil {
167+
return nil
168+
}
169+
170+
flattenedScalingOption := []map[string]interface{}(nil)
171+
flattenedScalingOption = append(flattenedScalingOption, map[string]interface{}{
172+
"concurrent_requests_threshold": types.FlattenUint32Ptr(scalingOption.ConcurrentRequestsThreshold),
173+
"cpu_usage_threshold": types.FlattenUint32Ptr(scalingOption.CPUUsageThreshold),
174+
"memory_usage_threshold": types.FlattenUint32Ptr(scalingOption.MemoryUsageThreshold),
175+
})
176+
177+
return flattenedScalingOption
178+
}
179+
121180
func expandContainerSecrets(secretsRawMap interface{}) []*container.Secret {
122181
secretsMap := secretsRawMap.(map[string]interface{})
123182
secrets := make([]*container.Secret, 0, len(secretsMap))

0 commit comments

Comments
 (0)