Skip to content

Commit

Permalink
feat(container/serverless): add scaling_option block
Browse files Browse the repository at this point in the history
  • Loading branch information
Antoine Belluard committed Jan 15, 2025
1 parent 7c34968 commit d04ca3f
Show file tree
Hide file tree
Showing 8 changed files with 4,732 additions and 2 deletions.
7 changes: 6 additions & 1 deletion docs/data-sources/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ In addition to all arguments above, the following attributes are exported:

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

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

- `scaling_option` - Configuration block used to decide when to scale up or down. Possible values:
- `concurrent_requests_threshold` - Scale depending on the number of concurrent requests being processed per container instance.
- `cpu_usage_threshold` - Scale depending on the CPU usage of a container instance.
- `memory_usage_threshold`- Scale depending on the memory usage of a container instance.

- `status` - The container status.

Expand Down
28 changes: 27 additions & 1 deletion docs/resources/container.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ The following arguments are supported:

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

- `scaling_option` - (Optional) Configuration block used to decide when to scale up or down. Possible values:
- `concurrent_requests_threshold` - Scale depending on the number of concurrent requests being processed per container instance.
- `cpu_usage_threshold` - Scale depending on the CPU usage of a container instance.
- `memory_usage_threshold`- Scale depending on the memory usage of a container instance.

- `port` - (Optional) The port to expose the container.

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

~>**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.
Refer to the [Serverless Containers pricing](https://www.scaleway.com/en/docs/faq/serverless-containers/#prices) for more information.
Refer to the [Serverless Containers pricing](https://www.scaleway.com/en/docs/faq/serverless-containers/#prices) for more information.

## Scaling option configuration

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.
It replaces current `max_concurrency` that has been deprecated.

Example:

```terraform
resource scaleway_container main {
name = "my-container-02"
namespace_id = scaleway_container_namespace.main.id
scaling_option {
concurrent_requests_threshold = 15
}
}
```

~>**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.
Refer to the [API Reference](https://www.scaleway.com/en/developers/api/serverless-containers/#path-containers-create-a-new-container) for more information.
37 changes: 37 additions & 0 deletions internal/services/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ func ResourceContainer() *schema.Resource {
Type: schema.TypeInt,
Optional: true,
Computed: true,
Deprecated: "Use scaling_option.concurrent_requests_threshold instead. This attribute will be removed.",
Description: "The maximum the number of simultaneous requests your container can handle at the same time.",
ValidateFunc: validation.IntAtMost(containerMaxConcurrencyLimit),
},
Expand Down Expand Up @@ -170,6 +171,31 @@ func ResourceContainer() *schema.Resource {
Description: "Execution environment of the container.",
ValidateDiagFunc: verify.ValidateEnum[container.ContainerSandbox](),
},
"scaling_option": {
Type: schema.TypeSet,
Optional: true,
Computed: true,
Description: "Configuration used to decide when to scale up or down.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"concurrent_requests_threshold": {
Type: schema.TypeInt,
Description: "Scale depending on the number of concurrent requests being processed per container instance.",
Optional: true,
},
"cpu_usage_threshold": {
Type: schema.TypeInt,
Description: "Scale depending on the CPU usage of a container instance.",
Optional: true,
},
"memory_usage_threshold": {
Type: schema.TypeInt,
Description: "Scale depending on the memory usage of a container instance.",
Optional: true,
},
},
},
},
// computed
"status": {
Type: schema.TypeString,
Expand Down Expand Up @@ -280,6 +306,7 @@ func ResourceContainerRead(ctx context.Context, d *schema.ResourceData, m interf
_ = d.Set("deploy", scw.BoolPtr(*types.ExpandBoolPtr(d.Get("deploy"))))
_ = d.Set("http_option", co.HTTPOption)
_ = d.Set("sandbox", co.Sandbox)
_ = d.Set("scaling_option", flattenScalingOption(co.ScalingOption))
_ = d.Set("region", co.Region.String())

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

if d.HasChanges("scaling_option") {
scalingOption := d.Get("scaling_option")

scalingOptionReq, err := expandScalingOptions(scalingOption)
if err != nil {
return diag.FromErr(err)
}
req.ScalingOption = scalingOptionReq
}

imageHasChanged := d.HasChanges("registry_sha256")
if imageHasChanged {
req.Redeploy = &imageHasChanged
Expand Down
35 changes: 35 additions & 0 deletions internal/services/container/container_data_source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,38 @@ func TestAccDataSourceContainer_Basic(t *testing.T) {
},
})
}

func TestAccDataSourceContainer_ScalingOption(t *testing.T) {
tt := acctest.NewTestTools(t)
defer tt.Cleanup()
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProviderFactories: tt.ProviderFactories,
CheckDestroy: isNamespaceDestroyed(tt),
Steps: []resource.TestStep{
{
Config: `
resource scaleway_container_namespace main {}
resource scaleway_container main {
namespace_id = scaleway_container_namespace.main.id
deploy = false
}
data scaleway_container main {
namespace_id = scaleway_container_namespace.main.id
container_id = scaleway_container.main.id
}
`,
Check: resource.ComposeTestCheckFunc(
isContainerPresent(tt, "scaleway_container.main"),
// Check default option returned by the API when you don't specify the scaling_option block.
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
resource.TestCheckResourceAttr("data.scaleway_container.main", "scaling_option.#", "1"),
resource.TestCheckResourceAttr("data.scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
),
},
},
})
}
89 changes: 89 additions & 0 deletions internal/services/container/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,95 @@ func TestAccContainer_Sandbox(t *testing.T) {
})
}

func TestAccContainer_ScalingOption(t *testing.T) {
tt := acctest.NewTestTools(t)
defer tt.Cleanup()
resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProviderFactories: tt.ProviderFactories,
CheckDestroy: isContainerDestroyed(tt),
Steps: []resource.TestStep{
{
Config: `
resource scaleway_container_namespace main {}
resource scaleway_container main {
namespace_id = scaleway_container_namespace.main.id
deploy = false
}
`,
Check: resource.ComposeTestCheckFunc(
isContainerPresent(tt, "scaleway_container.main"),
// Check default option returned by the API when you don't specify the scaling_option block.
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "50"),
),
},
{
Config: `
resource scaleway_container_namespace main {}
resource scaleway_container main {
namespace_id = scaleway_container_namespace.main.id
deploy = false
scaling_option {
concurrent_requests_threshold = 15
}
}
`,
Check: resource.ComposeTestCheckFunc(
isContainerPresent(tt, "scaleway_container.main"),
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.concurrent_requests_threshold", "15"),
),
},
{
Config: `
resource scaleway_container_namespace main {}
resource scaleway_container main {
namespace_id = scaleway_container_namespace.main.id
deploy = false
min_scale = 1
scaling_option {
cpu_usage_threshold = 72
}
}
`,
Check: resource.ComposeTestCheckFunc(
isContainerPresent(tt, "scaleway_container.main"),
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.#", "1"),
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.cpu_usage_threshold", "72"),
),
},

{
Config: `
resource scaleway_container_namespace main {}
resource scaleway_container main {
namespace_id = scaleway_container_namespace.main.id
deploy = false
min_scale = 1
scaling_option {
memory_usage_threshold = 66
}
}
`,
Check: resource.ComposeTestCheckFunc(
isContainerPresent(tt, "scaleway_container.main"),
resource.TestCheckResourceAttr("scaleway_container.main", "scaling_option.0.memory_usage_threshold", "66"),
),
},
},
})
}

func isContainerPresent(tt *acctest.TestTools, n string) resource.TestCheckFunc {
return func(state *terraform.State) error {
rs, ok := state.RootModule().Resources[n]
Expand Down
59 changes: 59 additions & 0 deletions internal/services/container/helpers_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,68 @@ func setCreateContainerRequest(d *schema.ResourceData, region scw.Region) (*cont
req.Sandbox = container.ContainerSandbox(sandbox.(string))
}

if scalingOption, ok := d.GetOk("scaling_option"); ok {
scalingOptionReq, err := expandScalingOptions(scalingOption)
if err != nil {
return nil, err
}
req.ScalingOption = scalingOptionReq
}

return req, nil
}

func expandScalingOptions(scalingOptionSchema interface{}) (*container.ContainerScalingOption, error) {
scalingOption, ok := scalingOptionSchema.(*schema.Set)
if !ok {
return &container.ContainerScalingOption{}, nil
}

for _, option := range scalingOption.List() {
rawOption, isRawOption := option.(map[string]interface{})
if !isRawOption {
continue
}

setFields := 0
cso := &container.ContainerScalingOption{}
if concurrentRequestThresold, ok := rawOption["concurrent_requests_threshold"].(int); ok && concurrentRequestThresold != 0 {
cso.ConcurrentRequestsThreshold = scw.Uint32Ptr(uint32(concurrentRequestThresold))
setFields++
}
if cpuUsageThreshold, ok := rawOption["cpu_usage_threshold"].(int); ok && cpuUsageThreshold != 0 {
cso.CPUUsageThreshold = scw.Uint32Ptr(uint32(cpuUsageThreshold))
setFields++
}
if memoryUsageThreshold, ok := rawOption["memory_usage_threshold"].(int); ok && memoryUsageThreshold != 0 {
cso.MemoryUsageThreshold = scw.Uint32Ptr(uint32(memoryUsageThreshold))
setFields++
}

if setFields > 1 {
return &container.ContainerScalingOption{}, errors.New("a maximum of one scaling option can be set")
}
return cso, nil
}

return &container.ContainerScalingOption{}, nil
}

func flattenScalingOption(scalingOption *container.ContainerScalingOption) interface{} {
if scalingOption == nil {
return nil
}

flattenedScalingOption := []map[string]interface{}(nil)
flattenedScalingOption = append(flattenedScalingOption, map[string]interface{}{
"concurrent_requests_threshold": types.FlattenUint32Ptr(scalingOption.ConcurrentRequestsThreshold),
"cpu_usage_threshold": types.FlattenUint32Ptr(scalingOption.CPUUsageThreshold),
"memory_usage_threshold": types.FlattenUint32Ptr(scalingOption.MemoryUsageThreshold),
})

return flattenedScalingOption
}

func expandContainerSecrets(secretsRawMap interface{}) []*container.Secret {
secretsMap := secretsRawMap.(map[string]interface{})
secrets := make([]*container.Secret, 0, len(secretsMap))
Expand Down
Loading

0 comments on commit d04ca3f

Please sign in to comment.