Skip to content

Commit ff87aee

Browse files
nullabeAntoine Belluardremyleone
authored
feat(containers): add scaling_option block (#2876)
* feat(container/serverless): add scaling_option block * Fix trailing space * Fix linter * Fix rebase issue. Sorry -_-' * Fix --------- Co-authored-by: Antoine Belluard <[email protected]> Co-authored-by: Rémy Léone <[email protected]>
1 parent 4110e51 commit ff87aee

File tree

8 files changed

+4738
-1
lines changed

8 files changed

+4738
-1
lines changed

docs/data-sources/container.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ In addition to all arguments above, the following attributes are exported:
9898
- `path` - Path to use for the HTTP health check.
9999
- `failure_threshold` - Number of consecutive health check failures before considering the container unhealthy.
100100
- `interval`- Period between health checks (in seconds).
101+
- `sandbox` - (Optional) Execution environment of the container.
102+
- `scaling_option` - Configuration block used to decide when to scale up or down. Possible values:
103+
- `concurrent_requests_threshold` - Scale depending on the number of concurrent requests being processed per container instance.
104+
- `cpu_usage_threshold` - Scale depending on the CPU usage of a container instance.
105+
- `memory_usage_threshold`- Scale depending on the memory usage of a container instance.
101106

102107
- `status` - The container status.
103108

docs/resources/container.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ The following arguments are supported:
9090
- `failure_threshold` - Number of consecutive health check failures before considering the container unhealthy.
9191
- `interval`- Period between health checks (in seconds).
9292

93+
- `scaling_option` - (Optional) Configuration block used to decide when to scale up or down. Possible values:
94+
- `concurrent_requests_threshold` - Scale depending on the number of concurrent requests being processed per container instance.
95+
- `cpu_usage_threshold` - Scale depending on the CPU usage of a container instance.
96+
- `memory_usage_threshold`- Scale depending on the memory usage of a container instance.
97+
9398
- `port` - (Optional) The port to expose the container.
9499

95100
- `deploy` - (Optional) Boolean indicating whether the container is in a production environment.
@@ -188,4 +193,26 @@ resource scaleway_container main {
188193

189194
~>**Important:** Another probe type can be set to TCP with the API, but currently the SDK has not been updated with this parameter.
190195
This is why the only probe that can be used here is the HTTP probe.
191-
Refer to the [API Reference](https://www.scaleway.com/en/developers/api/serverless-containers/#path-containers-create-a-new-container) for more information.
196+
Refer to the [Serverless Containers pricing](https://www.scaleway.com/en/docs/faq/serverless-containers/#prices) for more information.
197+
198+
## Scaling option configuration
199+
200+
Scaling option block configuration allows you to choose which parameter will scale up/down containers.
201+
Options are number of concurrent requests, CPU or memory usage.
202+
It replaces current `max_concurrency` that has been deprecated.
203+
204+
Example:
205+
206+
```terraform
207+
resource scaleway_container main {
208+
name = "my-container-02"
209+
namespace_id = scaleway_container_namespace.main.id
210+
211+
scaling_option {
212+
concurrent_requests_threshold = 15
213+
}
214+
}
215+
```
216+
217+
~>**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.
218+
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: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ func ResourceContainer() *schema.Resource {
130130
Type: schema.TypeInt,
131131
Optional: true,
132132
Computed: true,
133+
Deprecated: "Use scaling_option.concurrent_requests_threshold instead. This attribute will be removed.",
133134
Description: "The maximum the number of simultaneous requests your container can handle at the same time.",
134135
ValidateFunc: validation.IntAtMost(containerMaxConcurrencyLimit),
135136
},
@@ -209,6 +210,31 @@ func ResourceContainer() *schema.Resource {
209210
},
210211
},
211212
},
213+
"scaling_option": {
214+
Type: schema.TypeSet,
215+
Optional: true,
216+
Computed: true,
217+
Description: "Configuration used to decide when to scale up or down.",
218+
Elem: &schema.Resource{
219+
Schema: map[string]*schema.Schema{
220+
"concurrent_requests_threshold": {
221+
Type: schema.TypeInt,
222+
Description: "Scale depending on the number of concurrent requests being processed per container instance.",
223+
Optional: true,
224+
},
225+
"cpu_usage_threshold": {
226+
Type: schema.TypeInt,
227+
Description: "Scale depending on the CPU usage of a container instance.",
228+
Optional: true,
229+
},
230+
"memory_usage_threshold": {
231+
Type: schema.TypeInt,
232+
Description: "Scale depending on the memory usage of a container instance.",
233+
Optional: true,
234+
},
235+
},
236+
},
237+
},
212238
// computed
213239
"status": {
214240
Type: schema.TypeString,
@@ -323,6 +349,7 @@ func ResourceContainerRead(ctx context.Context, d *schema.ResourceData, m interf
323349
_ = d.Set("http_option", co.HTTPOption)
324350
_ = d.Set("sandbox", co.Sandbox)
325351
_ = d.Set("health_check", flattenHealthCheck(co.HealthCheck))
352+
_ = d.Set("scaling_option", flattenScalingOption(co.ScalingOption))
326353
_ = d.Set("region", co.Region.String())
327354

328355
return nil
@@ -429,6 +456,17 @@ func ResourceContainerUpdate(ctx context.Context, d *schema.ResourceData, m inte
429456
req.HealthCheck = healthCheckReq
430457
}
431458

459+
if d.HasChanges("scaling_option") {
460+
scalingOption := d.Get("scaling_option")
461+
462+
scalingOptionReq, err := expandScalingOptions(scalingOption)
463+
if err != nil {
464+
return diag.FromErr(err)
465+
}
466+
467+
req.ScalingOption = scalingOptionReq
468+
}
469+
432470
imageHasChanged := d.HasChanges("registry_sha256")
433471
if imageHasChanged {
434472
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
@@ -87,3 +87,38 @@ func TestAccDataSourceContainer_HealthCheck(t *testing.T) {
8787
},
8888
})
8989
}
90+
91+
func TestAccDataSourceContainer_ScalingOption(t *testing.T) {
92+
tt := acctest.NewTestTools(t)
93+
defer tt.Cleanup()
94+
resource.ParallelTest(t, resource.TestCase{
95+
PreCheck: func() { acctest.PreCheck(t) },
96+
ProviderFactories: tt.ProviderFactories,
97+
CheckDestroy: isNamespaceDestroyed(tt),
98+
Steps: []resource.TestStep{
99+
{
100+
Config: `
101+
resource scaleway_container_namespace main {}
102+
103+
resource scaleway_container main {
104+
namespace_id = scaleway_container_namespace.main.id
105+
deploy = false
106+
}
107+
108+
data scaleway_container main {
109+
namespace_id = scaleway_container_namespace.main.id
110+
container_id = scaleway_container.main.id
111+
}
112+
`,
113+
Check: resource.ComposeTestCheckFunc(
114+
isContainerPresent(tt, "scaleway_container.main"),
115+
// Check default option returned by the API when you don't specify the scaling_option block.
116+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
117+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
118+
resource.TestCheckResourceAttr("data.scaleway_container.main", "scaling_option.#", "1"),
119+
resource.TestCheckResourceAttr("data.scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
120+
),
121+
},
122+
},
123+
})
124+
}

internal/services/container/container_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,95 @@ func TestAccContainer_HealthCheck(t *testing.T) {
454454
})
455455
}
456456

457+
func TestAccContainer_ScalingOption(t *testing.T) {
458+
tt := acctest.NewTestTools(t)
459+
defer tt.Cleanup()
460+
resource.ParallelTest(t, resource.TestCase{
461+
PreCheck: func() { acctest.PreCheck(t) },
462+
ProviderFactories: tt.ProviderFactories,
463+
CheckDestroy: isContainerDestroyed(tt),
464+
Steps: []resource.TestStep{
465+
{
466+
Config: `
467+
resource scaleway_container_namespace main {}
468+
469+
resource scaleway_container main {
470+
namespace_id = scaleway_container_namespace.main.id
471+
deploy = false
472+
}
473+
`,
474+
Check: resource.ComposeTestCheckFunc(
475+
isContainerPresent(tt, "scaleway_container.main"),
476+
// Check default option returned by the API when you don't specify the scaling_option block.
477+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
478+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
479+
),
480+
},
481+
{
482+
Config: `
483+
resource scaleway_container_namespace main {}
484+
485+
resource scaleway_container main {
486+
namespace_id = scaleway_container_namespace.main.id
487+
deploy = false
488+
489+
scaling_option {
490+
concurrent_requests_threshold = 15
491+
}
492+
}
493+
`,
494+
Check: resource.ComposeTestCheckFunc(
495+
isContainerPresent(tt, "scaleway_container.main"),
496+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
497+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "15"),
498+
),
499+
},
500+
{
501+
Config: `
502+
resource scaleway_container_namespace main {}
503+
504+
resource scaleway_container main {
505+
namespace_id = scaleway_container_namespace.main.id
506+
deploy = false
507+
508+
min_scale = 1
509+
510+
scaling_option {
511+
cpu_usage_threshold = 72
512+
}
513+
}
514+
`,
515+
Check: resource.ComposeTestCheckFunc(
516+
isContainerPresent(tt, "scaleway_container.main"),
517+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
518+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.cpu_usage_threshold", "72"),
519+
),
520+
},
521+
522+
{
523+
Config: `
524+
resource scaleway_container_namespace main {}
525+
526+
resource scaleway_container main {
527+
namespace_id = scaleway_container_namespace.main.id
528+
deploy = false
529+
530+
min_scale = 1
531+
532+
scaling_option {
533+
memory_usage_threshold = 66
534+
}
535+
}
536+
`,
537+
Check: resource.ComposeTestCheckFunc(
538+
isContainerPresent(tt, "scaleway_container.main"),
539+
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.memory_usage_threshold", "66"),
540+
),
541+
},
542+
},
543+
})
544+
}
545+
457546
func isContainerPresent(tt *acctest.TestTools, n string) resource.TestCheckFunc {
458547
return func(state *terraform.State) error {
459548
rs, ok := state.RootModule().Resources[n]

internal/services/container/helpers_container.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ func setCreateContainerRequest(d *schema.ResourceData, region scw.Region) (*cont
126126
req.HealthCheck = healthCheckReq
127127
}
128128

129+
if scalingOption, ok := d.GetOk("scaling_option"); ok {
130+
scalingOptionReq, err := expandScalingOptions(scalingOption)
131+
if err != nil {
132+
return nil, err
133+
}
134+
135+
req.ScalingOption = scalingOptionReq
136+
}
137+
129138
return req, nil
130139
}
131140

@@ -220,6 +229,61 @@ func flattenHealthCheckHTTP(healthCheckHTTP *container.ContainerHealthCheckSpecH
220229
return flattenedHealthCheckHTTP
221230
}
222231

232+
func expandScalingOptions(scalingOptionSchema interface{}) (*container.ContainerScalingOption, error) {
233+
scalingOption, ok := scalingOptionSchema.(*schema.Set)
234+
if !ok {
235+
return &container.ContainerScalingOption{}, nil
236+
}
237+
238+
for _, option := range scalingOption.List() {
239+
rawOption, isRawOption := option.(map[string]interface{})
240+
if !isRawOption {
241+
continue
242+
}
243+
244+
setFields := 0
245+
246+
cso := &container.ContainerScalingOption{}
247+
if concurrentRequestThresold, ok := rawOption["concurrent_requests_threshold"].(int); ok && concurrentRequestThresold != 0 {
248+
cso.ConcurrentRequestsThreshold = scw.Uint32Ptr(uint32(concurrentRequestThresold))
249+
setFields++
250+
}
251+
252+
if cpuUsageThreshold, ok := rawOption["cpu_usage_threshold"].(int); ok && cpuUsageThreshold != 0 {
253+
cso.CPUUsageThreshold = scw.Uint32Ptr(uint32(cpuUsageThreshold))
254+
setFields++
255+
}
256+
257+
if memoryUsageThreshold, ok := rawOption["memory_usage_threshold"].(int); ok && memoryUsageThreshold != 0 {
258+
cso.MemoryUsageThreshold = scw.Uint32Ptr(uint32(memoryUsageThreshold))
259+
setFields++
260+
}
261+
262+
if setFields > 1 {
263+
return &container.ContainerScalingOption{}, errors.New("a maximum of one scaling option can be set")
264+
}
265+
266+
return cso, nil
267+
}
268+
269+
return &container.ContainerScalingOption{}, nil
270+
}
271+
272+
func flattenScalingOption(scalingOption *container.ContainerScalingOption) interface{} {
273+
if scalingOption == nil {
274+
return nil
275+
}
276+
277+
flattenedScalingOption := []map[string]interface{}(nil)
278+
flattenedScalingOption = append(flattenedScalingOption, map[string]interface{}{
279+
"concurrent_requests_threshold": types.FlattenUint32Ptr(scalingOption.ConcurrentRequestsThreshold),
280+
"cpu_usage_threshold": types.FlattenUint32Ptr(scalingOption.CPUUsageThreshold),
281+
"memory_usage_threshold": types.FlattenUint32Ptr(scalingOption.MemoryUsageThreshold),
282+
})
283+
284+
return flattenedScalingOption
285+
}
286+
223287
func expandContainerSecrets(secretsRawMap interface{}) []*container.Secret {
224288
secretsMap := secretsRawMap.(map[string]interface{})
225289
secrets := make([]*container.Secret, 0, len(secretsMap))

0 commit comments

Comments
 (0)