diff --git a/coordinator/terraform/README.md b/coordinator/terraform/README.md index 2214b8d..2624184 100644 --- a/coordinator/terraform/README.md +++ b/coordinator/terraform/README.md @@ -3,13 +3,6 @@ This is a Terraform module facilitating the deployment of mimir-coordinator-k8s charm, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.5 | -| [juju](#requirement\_juju) | >= 1.0 | - ## Providers | Name | Version | diff --git a/terraform/README.md b/terraform/README.md index c3f85a6..4dcd2aa 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -6,13 +6,6 @@ This is a Terraform module facilitating the deployment of Mimir solution, using > `s3-integrator` itself doesn't act as an S3 object storage system. For the HA solution to be functional, `s3-integrator` needs to point to an S3-like storage. See [this guide](https://discourse.charmhub.io/t/cos-lite-docs-set-up-minio/15211) to learn how to connect to an S3-like storage for traces. -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.5 | -| [juju](#requirement\_juju) | >= 1.0 | - ## Providers | Name | Version | @@ -23,20 +16,14 @@ This is a Terraform module facilitating the deployment of Mimir solution, using | Name | Source | Version | |------|--------|---------| -| [mimir\_backend](#module\_mimir\_backend) | ../worker/terraform | n/a | | [mimir\_coordinator](#module\_mimir\_coordinator) | ../coordinator/terraform | n/a | -| [mimir\_read](#module\_mimir\_read) | ../worker/terraform | n/a | -| [mimir\_write](#module\_mimir\_write) | ../worker/terraform | n/a | +| [mimir\_worker](#module\_mimir\_worker) | ../worker/terraform | n/a | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [anti\_affinity](#input\_anti\_affinity) | Enable anti-affinity constraints. | `bool` | `true` | no | -| [backend\_config](#input\_backend\_config) | Map of the backend worker configuration options | `map(string)` | `{}` | no | -| [backend\_name](#input\_backend\_name) | Name of the Mimir backend (meta role) app | `string` | `"mimir-backend"` | no | -| [backend\_units](#input\_backend\_units) | Number of Mimir worker units with the backend meta role | `number` | `1` | no | -| [backend\_worker\_storage\_directives](#input\_backend\_worker\_storage\_directives) | Map of storage used by the backend worker application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no | | [channel](#input\_channel) | Channel that the applications are deployed from | `string` | n/a | yes | | [coordinator\_config](#input\_coordinator\_config) | Map of the coordinator configuration options | `map(string)` | `{}` | no | | [coordinator\_constraints](#input\_coordinator\_constraints) | String listing constraints for the coordinator application | `string` | `"arch=amd64"` | no | @@ -45,13 +32,9 @@ This is a Terraform module facilitating the deployment of Mimir solution, using | [coordinator\_storage\_directives](#input\_coordinator\_storage\_directives) | Map of storage used by the coordinator application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no | | [coordinator\_units](#input\_coordinator\_units) | Number of Mimir coordinator units | `number` | `1` | no | | [model\_uuid](#input\_model\_uuid) | Reference to an existing model resource or data source for the model to deploy to | `string` | n/a | yes | -| [read\_config](#input\_read\_config) | Map of the read worker configuration options | `map(string)` | `{}` | no | -| [read\_name](#input\_read\_name) | Name of the Mimir read (meta role) app | `string` | `"mimir-read"` | no | -| [read\_units](#input\_read\_units) | Number of Mimir worker units with the read meta role | `number` | `1` | no | -| [read\_worker\_storage\_directives](#input\_read\_worker\_storage\_directives) | Map of storage used by the read worker application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no | -| [s3\_access\_key](#input\_s3\_access\_key) | S3 access-key credential | `string` | n/a | yes | +| [s3\_access\_key](#input\_s3\_access\_key) | S3 access-key credential. Set to null (along with s3\_endpoint and s3\_secret\_key) to skip deploying the S3 integrator. | `string` | `null` | no | | [s3\_bucket](#input\_s3\_bucket) | Bucket name | `string` | `"mimir"` | no | -| [s3\_endpoint](#input\_s3\_endpoint) | S3 endpoint | `string` | n/a | yes | +| [s3\_endpoint](#input\_s3\_endpoint) | S3 endpoint. When null, the S3 integrator is not deployed and the caller must handle storage integration externally. | `string` | `null` | no | | [s3\_integrator\_channel](#input\_s3\_integrator\_channel) | Channel that the s3-integrator application is deployed from | `string` | `"2/stable"` | no | | [s3\_integrator\_config](#input\_s3\_integrator\_config) | Map of the s3-integrator configuration options | `map(string)` | `{}` | no | | [s3\_integrator\_constraints](#input\_s3\_integrator\_constraints) | String listing constraints for the s3-integrator application | `string` | `"arch=amd64"` | no | @@ -59,14 +42,10 @@ This is a Terraform module facilitating the deployment of Mimir solution, using | [s3\_integrator\_revision](#input\_s3\_integrator\_revision) | Revision number of the s3-integrator application | `number` | `null` | no | | [s3\_integrator\_storage\_directives](#input\_s3\_integrator\_storage\_directives) | Map of storage used by the s3-integrator application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no | | [s3\_integrator\_units](#input\_s3\_integrator\_units) | Number of S3 integrator units | `number` | `1` | no | -| [s3\_secret\_key](#input\_s3\_secret\_key) | S3 secret-key credential | `string` | n/a | yes | -| [worker\_constraints](#input\_worker\_constraints) | String listing constraints for the worker application | `string` | `"arch=amd64"` | no | +| [s3\_secret\_key](#input\_s3\_secret\_key) | S3 secret-key credential. Set to null (along with s3\_endpoint and s3\_access\_key) to skip deploying the S3 integrator. | `string` | `null` | no | | [worker\_resources](#input\_worker\_resources) | The worker application's resources i.e., a resource revision number from CharmHub or a custom OCI image resource | `map(string)` | `{}` | no | | [worker\_revision](#input\_worker\_revision) | Revision number of the worker application | `number` | `null` | no | -| [write\_config](#input\_write\_config) | Map of the write worker configuration options | `map(string)` | `{}` | no | -| [write\_name](#input\_write\_name) | Name of the Mimir write (meta role) app | `string` | `"mimir-write"` | no | -| [write\_units](#input\_write\_units) | Number of Mimir worker units with the write meta role | `number` | `1` | no | -| [write\_worker\_storage\_directives](#input\_write\_worker\_storage\_directives) | Map of storage used by the write worker application, which defaults to 1 GB, allocated by Juju | `map(string)` | `{}` | no | +| [workers](#input\_workers) | Map of worker roles to deploy. Keys must be one of: all, backend, read, write. When 'all' is used, a single worker with role-all is created. |
map(object({
units = optional(number, 1)
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
storage_directives = optional(map(string), {})
app_name = optional(string)
}))
|
{
"backend": {},
"read": {},
"write": {}
}
| no | ## Outputs @@ -79,7 +58,96 @@ This is a Terraform module facilitating the deployment of Mimir solution, using ## Usage -### Microservice deployment +### Microservices deployment (default) + +By default, this module deploys three separate workers — `backend`, `read`, and `write` — each with 1 unit: + +```hcl +module "mimir" { + source = "git::https://github.com/canonical/mimir-operators//terraform" + model_uuid = juju_model.cos.uuid + channel = "dev/edge" + + s3_endpoint = "https://s3.example.com" + s3_access_key = "my-access-key" + s3_secret_key = "my-secret-key" +} +``` + +To scale individual roles: + +```hcl +module "mimir" { + source = "git::https://github.com/canonical/mimir-operators//terraform" + model_uuid = juju_model.cos.uuid + channel = "dev/edge" + + workers = { + backend = { units = 3, storage_directives = { "data" = "50G" } } + read = { units = 2 } + write = { units = 2 } + } + + s3_endpoint = "https://s3.example.com" + s3_access_key = "my-access-key" + s3_secret_key = "my-secret-key" +} +``` -By default, this Terraform module will deploy each Mimir worker with `1` unit. To configure the module to run `x` units of any worker role, you can run `terraform apply -var="_units="`. See [Mimir worker roles](https://discourse.charmhub.io/t/mimir-worker-roles/15484) for the recommended scale for each role. + +### Monolithic deployment + +To deploy a single worker with all roles combined: + +```hcl +module "mimir" { + source = "git::https://github.com/canonical/mimir-operators//terraform" + model_uuid = juju_model.cos.uuid + channel = "dev/edge" + + workers = { + all = { units = 3 } + } + + s3_endpoint = "https://s3.example.com" + s3_access_key = "my-access-key" + s3_secret_key = "my-secret-key" +} +``` + +> [!NOTE] +> When using `all`, no other worker roles may be specified. + +### External storage backend (no S3 integrator) + +When using an external storage backend (e.g. SeaweedFS), omit the `s3_*` variables. The module will not deploy the S3 integrator, and you are responsible for integrating your storage with the coordinator's `s3` endpoint: + +```hcl +module "mimir" { + source = "git::https://github.com/canonical/mimir-operators//terraform" + model_uuid = juju_model.cos.uuid + channel = "dev/edge" + + workers = { + backend = { units = 2 } + read = { units = 2 } + write = { units = 2 } + } +} + +# Wire external storage to the coordinator +resource "juju_integration" "seaweedfs_mimir" { + model_uuid = juju_model.cos.uuid + + application { + name = module.seaweedfs.app_name + endpoint = module.seaweedfs.provides.s3 + } + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.requires.s3 + } +} +``` diff --git a/terraform/tests/s3_optional.tftest.hcl b/terraform/tests/s3_optional.tftest.hcl new file mode 100644 index 0000000..b748873 --- /dev/null +++ b/terraform/tests/s3_optional.tftest.hcl @@ -0,0 +1,64 @@ +mock_provider "juju" {} + +variables { + model_uuid = "00000000-0000-0000-0000-000000000000" + channel = "dev/edge" +} + +# --- default: S3 disabled (no integrator deployed) --- + +run "s3_disabled_by_default" { + command = plan + + assert { + condition = length(juju_application.s3_integrator) == 0 + error_message = "Expected no s3-integrator when s3_endpoint is null" + } + + assert { + condition = length(juju_secret.mimir_s3_credentials_secret) == 0 + error_message = "Expected no s3 credentials secret when s3_endpoint is null" + } + + assert { + condition = length(juju_access_secret.mimir_s3_secret_access) == 0 + error_message = "Expected no s3 access secret when s3_endpoint is null" + } + + assert { + condition = length(juju_integration.coordinator_to_s3_integrator) == 0 + error_message = "Expected no s3 integration when s3_endpoint is null" + } +} + +# --- S3 enabled: all resources created --- + +run "s3_enabled" { + command = plan + + variables { + s3_endpoint = "https://s3.example.com" + s3_access_key = "access-key" + s3_secret_key = "secret-key" + } + + assert { + condition = length(juju_application.s3_integrator) == 1 + error_message = "Expected s3-integrator when s3_endpoint is set" + } + + assert { + condition = length(juju_secret.mimir_s3_credentials_secret) == 1 + error_message = "Expected s3 credentials secret when s3_endpoint is set" + } + + assert { + condition = length(juju_access_secret.mimir_s3_secret_access) == 1 + error_message = "Expected s3 access secret when s3_endpoint is set" + } + + assert { + condition = length(juju_integration.coordinator_to_s3_integrator) == 1 + error_message = "Expected s3 integration when s3_endpoint is set" + } +} diff --git a/terraform/tests/validation.tftest.hcl b/terraform/tests/validation.tftest.hcl new file mode 100644 index 0000000..3a0b23d --- /dev/null +++ b/terraform/tests/validation.tftest.hcl @@ -0,0 +1,92 @@ +mock_provider "juju" {} + +variables { + model_uuid = "00000000-0000-0000-0000-000000000000" + channel = "dev/edge" +} + +# --- invalid worker key --- + +run "invalid_worker_key" { + command = plan + expect_failures = [var.workers] + + variables { + workers = { + invalid_role = {} + } + } +} + +# --- role-all with other roles --- + +run "all_with_other_roles" { + command = plan + expect_failures = [var.workers] + + variables { + workers = { + all = {} + backend = {} + } + } +} + +# --- zero units --- + +run "zero_units" { + command = plan + expect_failures = [var.workers] + + variables { + workers = { + backend = { units = 0 } + } + } +} + +# --- partial S3 config: endpoint without credentials --- + +run "s3_endpoint_without_credentials" { + command = plan + expect_failures = [var.s3_endpoint] + + variables { + s3_endpoint = "https://s3.example.com" + } +} + +# --- partial S3 config: credentials without endpoint --- + +run "s3_credentials_without_endpoint" { + command = plan + expect_failures = [var.s3_endpoint] + + variables { + s3_access_key = "access-key" + s3_secret_key = "secret-key" + } +} + +# --- invalid channel track --- + +run "invalid_channel" { + command = plan + expect_failures = [var.channel] + + variables { + channel = "stable" + } +} + +# --- anti-affinity with custom coordinator constraints --- + +run "anti_affinity_with_custom_coordinator_constraints" { + command = plan + expect_failures = [var.coordinator_constraints] + + variables { + anti_affinity = true + coordinator_constraints = "arch=arm64" + } +} diff --git a/terraform/tests/workers.tftest.hcl b/terraform/tests/workers.tftest.hcl new file mode 100644 index 0000000..90aa62f --- /dev/null +++ b/terraform/tests/workers.tftest.hcl @@ -0,0 +1,120 @@ +mock_provider "juju" {} + +variables { + model_uuid = "00000000-0000-0000-0000-000000000000" + channel = "dev/edge" +} + +# --- default: three workers (backend, read, write) --- + +run "default_workers" { + command = plan + + assert { + condition = length(module.mimir_worker) == 3 + error_message = "Expected 3 worker modules with default workers config" + } + + assert { + condition = length(juju_integration.coordinator_to_worker) == 3 + error_message = "Expected 3 coordinator-to-worker integrations" + } +} + +# --- monolithic mode: single worker with role-all --- + +run "monolithic_mode" { + command = plan + + variables { + workers = { + all = {} + } + } + + assert { + condition = length(module.mimir_worker) == 1 + error_message = "Expected 1 worker module in monolithic mode" + } + + assert { + condition = length(juju_integration.coordinator_to_worker) == 1 + error_message = "Expected 1 coordinator-to-worker integration in monolithic mode" + } +} + +# --- custom units per worker --- + +run "custom_units" { + command = plan + + variables { + workers = { + backend = { units = 3 } + read = { units = 2 } + write = { units = 5 } + } + } + + assert { + condition = module.mimir_worker["backend"].app_name == "mimir-backend" + error_message = "Expected backend worker app_name to be mimir-backend" + } + + assert { + condition = module.mimir_worker["read"].app_name == "mimir-read" + error_message = "Expected read worker app_name to be mimir-read" + } + + assert { + condition = module.mimir_worker["write"].app_name == "mimir-write" + error_message = "Expected write worker app_name to be mimir-write" + } +} + +# --- custom app_name override --- + +run "custom_app_name" { + command = plan + + variables { + workers = { + backend = { app_name = "my-backend" } + read = {} + write = {} + } + } + + assert { + condition = module.mimir_worker["backend"].app_name == "my-backend" + error_message = "Expected custom app_name to be used" + } + + assert { + condition = module.mimir_worker["read"].app_name == "mimir-read" + error_message = "Expected default app_name mimir-read" + } +} + +# --- subset of workers (only read and write) --- + +run "subset_workers" { + command = plan + + variables { + workers = { + read = { units = 2 } + write = { units = 2 } + } + } + + assert { + condition = length(module.mimir_worker) == 2 + error_message = "Expected 2 worker modules" + } + + assert { + condition = length(juju_integration.coordinator_to_worker) == 2 + error_message = "Expected 2 coordinator-to-worker integrations" + } +} diff --git a/worker/terraform/README.md b/worker/terraform/README.md index ea2095c..01b4200 100644 --- a/worker/terraform/README.md +++ b/worker/terraform/README.md @@ -3,13 +3,6 @@ This is a Terraform module facilitating the deployment of mimir-worker-k8s charm, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). -## Requirements - -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.5 | -| [juju](#requirement\_juju) | >= 1.0 | - ## Providers | Name | Version |