diff --git a/.github/workflows/terraform-validate.yml b/.github/workflows/terraform-validate.yml index ca6ea5c..7adcd08 100644 --- a/.github/workflows/terraform-validate.yml +++ b/.github/workflows/terraform-validate.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - cloud: [aws, azure, gcp] + cloud: [aws, azure, gcp, openstack] steps: - name: Checkout repository uses: actions/checkout@v6 diff --git a/.gitignore b/.gitignore index 510a6e2..203a47a 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ crash.log terraform/aws/terraform.tfvars terraform/azure/terraform.tfvars terraform/gcp/terraform.tfvars +terraform/openstack/terraform.tfvars # inst/demo-lps-2025/00_ml_month.ipynb — openEO job downloads inst/demo-lps-2025/data/output-month/ diff --git a/DESCRIPTION b/DESCRIPTION index b543e32..acbacda 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -22,7 +22,6 @@ URL: https://github.com/Open-Earth-Monitor/openeocraft BugReports: https://github.com/Open-Earth-Monitor/openeocraft/issues Encoding: UTF-8 Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 Depends: R (>= 4.5.0) Imports: @@ -58,3 +57,4 @@ Collate: 'pgraphs.R' 'mock.R' 'run.R' +Config/roxygen2/version: 8.0.0 diff --git a/README.md b/README.md index 9a14613..a5be0df 100644 --- a/README.md +++ b/README.md @@ -208,10 +208,10 @@ docker run -d --name openeocraft --restart unless-stopped \ #### Terraform (recommended) -For repeatable cloud provisioning, use the Terraform stacks in [`terraform/README.md`](terraform/README.md). Defaults target **GPU with at least 32 GB RAM** on AWS (`g4dn.2xlarge`), Azure (`Standard_NC8as_T4_v3`), and GCP (`g2-standard-8`), with NVIDIA drivers and `docker run --gpus all`. See each cloud README for quota, auth, and CPU fallbacks. +For repeatable cloud provisioning, use the Terraform stacks in [`terraform/README.md`](terraform/README.md). Defaults target **GPU with at least 32 GB RAM** on AWS, Azure, and GCP; OpenStack uses a configurable `flavor_name` with a **32 GB RAM minimum**. See each cloud README for quota, auth, and CPU fallbacks. ```bash -cd terraform/aws # or azure/ or gcp/ +cd terraform/aws # or azure/, gcp/, or openstack/ cp terraform.tfvars.example terraform.tfvars # Set credentials, key pair / SSH key, ssh_cidr_blocks, api_cidr_blocks terraform init diff --git a/inst/examples/06_ml_tempcnn_tune_grid.R b/inst/examples/06_ml_tempcnn_tune_grid.R new file mode 100644 index 0000000..ee0192e --- /dev/null +++ b/inst/examples/06_ml_tempcnn_tune_grid.R @@ -0,0 +1,102 @@ +# TempCNN fine-tuning (grid search) + inference via the openEO R client. +# +# Prerequisites: +# - OpenEOcraft backend running locally (e.g. source docker/plumber.R in R/RStudio, +# or docker compose up) +# - openeo R package installed +# +# Run from repo root: +# Rscript inst/examples/06_ml_tempcnn_tune_grid.R +# +# Default connection below matches docker/plumber.R demo credentials (user brian). +# With a local plumber process, job artefacts are written under the backend +# workdir, e.g. /workspace//jobs// (tuning_results.json, +# saved model, GeoTIFF). Set the workdir when starting the API if needed. +# +# Cube bands must match the training RDS timeline and features (P16D; add NDVI +# when samples were built with an NDVI band). See inst/demo-lps-2025/00_ml_month.ipynb. +# Grid search uses a small param_grid for demo wall time; see inst/demo-lps-2025/. + +library(openeo) + +con <- connect( + host = "http://127.0.0.1:8000", + user = "brian", + password = "123456" +) + +p <- processes() + +deforestation_data <- + "https://github.com/e-sensing/sitsdata/raw/main/data/samples_deforestation_rondonia.rds" + +tempcnn_model_init <- p$mlm_class_tempcnn( + optimizer = "adam", + learning_rate = 0.0005, + seed = 42, + epochs = 20, + batch_size = 64, + verbose = TRUE +) + +param_grid <- list( + learning_rate = c(0.0005, 0.0001), + epochs = c(20, 40) +) + +tempcnn_tuned <- p$ml_tune_grid( + model = tempcnn_model_init, + training_data = deforestation_data, + target = "label", + parameters = param_grid, + scoring = "accuracy", + cv = 0, + seed = 42 +) + +tempcnn_model <- p$save_ml_model( + data = tempcnn_tuned, + name = "tempcnn_rondonia_tuned_v1", + return_model = TRUE +) + +datacube <- p$load_collection( + id = "mpc-sentinel-2-l2a", + spatial_extent = list( + west = -63.50, + south = -8.92, + east = -63.35, + north = -8.78 + ), + temporal_extent = c("2022-01-01", "2022-12-31"), + bands = list( + "b02", "b03", "b04", "b05", "b06", "b07", "b08", + "b11", "b12", "b8a" + ) +) + +datacube <- p$cube_regularize( + data = datacube, + period = "P16D", + resolution = 30 +) + +datacube <- p$ndvi( + data = datacube, + red = "B04", + nir = "B08", + target_band = "NDVI" +) + +data <- p$ml_predict(data = datacube, model = tempcnn_model) + +ml_job <- p$save_result(data = data, format = "GTiff") + +job <- create_job( + graph = ml_job, + title = "TempCNN fine-tuning + inference", + description = "Grid-search TempCNN on Rondonia samples; predict GeoTIFF" +) +job <- start_job(job) + +describe_job(job) diff --git a/terraform/README.md b/terraform/README.md index 1b94a83..903eb1c 100644 --- a/terraform/README.md +++ b/terraform/README.md @@ -1,6 +1,6 @@ # OpenEOcraft Terraform (multi-cloud) -Repeatable provisioning for OpenEOcraft on **AWS**, **Azure**, and **Google Cloud**. Each stack launches a single **x86_64** Ubuntu 22.04 VM, installs Docker via cloud-init, optionally installs the **NVIDIA driver + NVIDIA Container Toolkit**, pulls the OpenEOcraft image, and starts the API on port **8000**. +Repeatable provisioning for OpenEOcraft on **AWS**, **Azure**, **Google Cloud**, and **OpenStack**. Each stack launches a single **x86_64** Ubuntu 22.04 VM, installs Docker via cloud-init, optionally installs the **NVIDIA driver + NVIDIA Container Toolkit**, pulls the OpenEOcraft image, and starts the API on port **8000**. All defaults target **at least 32 GB instance RAM** (30 GB container limit, leaving headroom for the OS). @@ -9,6 +9,7 @@ All defaults target **at least 32 GB instance RAM** (30 GB container limit, leav | AWS | [`aws/`](aws/) | `g4dn.2xlarge` (32 GB, 1× T4) | `m5.2xlarge` (32 GB) | | Azure | [`azure/`](azure/) | `Standard_NC8as_T4_v3` (56 GB, 1× T4) | `Standard_D8s_v5` (32 GB) | | GCP | [`gcp/`](gcp/) | `g2-standard-8` (32 GB, 1× L4) | `n2-standard-8` (32 GB) | +| OpenStack | [`openstack/`](openstack/) | cloud-specific GPU flavor | `flavor_name` ≥ 32 GB RAM (default `m1.8xlarge`) | Bootstrap scripts live in [`shared/cloud_init.tftpl`](shared/cloud_init.tftpl) and are shared across clouds. Workspace disks are mounted only via stable cloud-specific device paths (never heuristic disk guessing); if the data disk is not ready, bootstrap falls back to a directory on the root filesystem. @@ -17,7 +18,7 @@ Bootstrap scripts live in [`shared/cloud_init.tftpl`](shared/cloud_init.tftpl) a Pick a cloud, copy the example variables, edit credentials and CIDR blocks, then apply: ```bash -cd terraform/aws # or azure/ or gcp/ +cd terraform/aws # or azure/, gcp/, or openstack/ cp terraform.tfvars.example terraform.tfvars # Edit terraform.tfvars (key pair / SSH key, your public IP /32) terraform init @@ -34,7 +35,7 @@ Allow **15–20 minutes** on first boot when `enable_gpu = true`. | ----------- | ----- | | [Terraform](https://developer.hashicorp.com/terraform/install) >= 1.5 | | | Cloud account with billing enabled | GPU instances require quota in most accounts | -| SSH access | AWS: EC2 key pair name; Azure/GCP: SSH public key | +| SSH access | AWS/OpenStack: key pair name; Azure/GCP: SSH public key | | Your public IP as CIDR | `curl -s ifconfig.me` → `x.x.x.x/32` for SSH and API rules | ## Cloud-specific docs @@ -42,6 +43,7 @@ Allow **15–20 minutes** on first boot when `enable_gpu = true`. - [AWS EC2](aws/README.md) - [Azure VM](azure/README.md) - [Google Compute Engine](gcp/README.md) +- [OpenStack Nova](openstack/README.md) ## What each stack creates diff --git a/terraform/openstack/.terraform.lock.hcl b/terraform/openstack/.terraform.lock.hcl new file mode 100644 index 0000000..cd6007a --- /dev/null +++ b/terraform/openstack/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/terraform-provider-openstack/openstack" { + version = "2.1.0" + constraints = "~> 2.1" + hashes = [ + "h1:FFgxjgOlyRstaP7vYdPpgai9q1U0T0OF9B4FF7ZknrM=", + "zh:113661750398bf21c8fe36aade9fb6f5eb82b5bcd3bcd30bd37ac805d83398f4", + "zh:1b3c26347b9cd61e413ee93c2f422cc3278a77f55fd3516eaabb3e2a85f65281", + "zh:1b751bbf1e4152829a643b532fd3f5967a2e89a41fac381257e0b41665be3306", + "zh:1b967bbfd9b344419c0e0df0c3a15fcbd731e91f19a18955a55aace8d9ec039a", + "zh:1bc0fc7c0a21e568db043b654501ce668ba19bf7628d37a7d2aaa512fd6e5aeb", + "zh:425cbf61757d4b503e7bf0f409ea59835ca3afbd2432d56ad552c2e5d234a572", + "zh:67d4f059cb4d73bf6c060313ec32962c4e5bd8dc7be2542a6f2098ab32575cd9", + "zh:7fe841ac5b68a4f52fb3cf45070828f3845de44746679d434e4349f3c23e3ef2", + "zh:ac1ed4c6ef0b6a3410568a05d3f9933d184497f065988503c43da0b2f0786ab2", + "zh:c5c0d14c86fabd9ab6a5d555e6a8d511942665fb5fa948dd452b0d1934068344", + "zh:c9ae5c210192275185d6823566a9421983e8e64c2665a4cae00b92dd0706bd19", + "zh:ee9865ccc053e7f345e532654fb628d1cf1e81cd2e929643c1691bebffcf7b98", + "zh:f3416d2f666095e740522c4964e436470bb9ec17bd53aaae8169ad93297d07bd", + "zh:fbca85457dd49e17168989d64f7cfc4a519d55ef4e00e89cea2859e87ad87f83", + ] +} diff --git a/terraform/openstack/README.md b/terraform/openstack/README.md new file mode 100644 index 0000000..efcacb5 --- /dev/null +++ b/terraform/openstack/README.md @@ -0,0 +1,104 @@ +# OpenEOcraft on OpenStack (Terraform) + +This stack provisions a single **x86_64** Ubuntu 22.04 Nova instance on a dedicated tenant network with a floating IP, installs Docker via cloud-init, optionally installs the **NVIDIA driver + NVIDIA Container Toolkit** (GPU mode), pulls the OpenEOcraft image, and starts the API on port **8000**. + +**Default target:** CPU flavor with **≥ 32 GB RAM** (`flavor_name`, default `m1.8xlarge` — verify on your cloud with `openstack flavor list`). + +## Prerequisites + +| Requirement | Notes | +| ----------- | ----- | +| [Terraform](https://developer.hashicorp.com/terraform/install) >= 1.5 | | +| OpenStack project with compute + networking + block storage | | +| Nova **key pair** | `openstack keypair create …` | +| Flavor with **≥ 32 GB RAM** | `openstack flavor list` — names vary by cloud | +| External network for floating IPs | Often `public` or `ext-net` | +| Your public IP as CIDR | `curl -s ifconfig.me` → `x.x.x.x/32` | + +### OpenStack authentication + +Set standard OpenStack environment variables (from `openstack rc` or your cloud dashboard): + +```bash +export OS_AUTH_URL=... +export OS_USERNAME=... +export OS_PASSWORD=... +export OS_PROJECT_NAME=... +export OS_USER_DOMAIN_NAME=Default +export OS_PROJECT_DOMAIN_NAME=Default +export OS_REGION_NAME=... +openstack token issue +``` + +Or use `clouds.yaml` / application credentials as supported by the [OpenStack Terraform provider](https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs). + +## Quick start + +```bash +cd terraform/openstack +cp terraform.tfvars.example terraform.tfvars +``` + +Edit `terraform.tfvars`: + +- `key_pair_name` — your Nova key pair +- `flavor_name` — a flavor with **≥ 32768 MB RAM** on your cloud +- `external_network_name` / `floating_ip_pool` — your provider external network +- `ssh_cidr_blocks` / `api_cidr_blocks` — your public IP `/32` + +```bash +terraform init +terraform plan +terraform apply +``` + +After apply: + +```bash +terraform output api_url +``` + +**Bootstrap time:** allow **15–20 minutes** on first boot when `enable_gpu = true`. + +## What gets created + +| Resource | Purpose | +| -------- | ------- | +| Network, subnet, router | Tenant network with external gateway | +| Security group | SSH (22) and OpenEOcraft API (8000) from your CIDR lists | +| Nova instance | Ubuntu 22.04, Docker, optional NVIDIA stack, OpenEOcraft container | +| Cinder volume (optional) | Persistent `/var/openeo/workspace` on `/dev/vdb` (default 200 GB) | +| Floating IP | Public access to the API | + +## Variables + +See [`variables.tf`](variables.tf). Key settings: + +| Variable | Default | Notes | +| -------- | ------- | ----- | +| `flavor_name` | `m1.8xlarge` | Must exist on your cloud with **≥ 32 GB RAM** | +| `enable_gpu` | `false` | Set `true` only with a GPU flavor; names are cloud-specific | +| `docker_cpus` / `docker_memory_gb` | `8` / `30` | Container limits | +| `workspace_volume_size_gb` | `200` | Set to `0` to skip separate Cinder volume | +| `external_network_name` | `public` | Provider external network for router + floating IP | + +### GPU (optional) + +GPU flavor names differ widely between OpenStack deployments. Copy [`terraform.gpu.tfvars.example`](terraform.gpu.tfvars.example), set `flavor_name` to a GPU flavor from your cloud, and set `enable_gpu = true`. + +## Verify after apply + +```bash +ssh ubuntu@$(terraform output -raw public_ip) + +nvidia-smi # when enable_gpu = true +sudo docker ps +sudo docker logs openeocraft --tail 50 +curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8000/ +``` + +## Teardown + +```bash +terraform destroy +``` diff --git a/terraform/openstack/main.tf b/terraform/openstack/main.tf new file mode 100644 index 0000000..0751f9c --- /dev/null +++ b/terraform/openstack/main.tf @@ -0,0 +1,160 @@ +data "openstack_networking_network_v2" "external" { + name = var.external_network_name +} + +data "openstack_compute_flavor_v2" "openeocraft" { + name = var.flavor_name + + lifecycle { + postcondition { + condition = self.ram >= 32768 + error_message = "flavor_name must provide at least 32 GB RAM (32768 MB); selected flavor has ${self.ram} MB." + } + } +} + +data "openstack_images_image_v2" "ubuntu" { + name = var.image_name + most_recent = true + + properties = { + os_distro = "ubuntu" + } +} + +locals { + name_prefix = var.project_name + instance_tags = merge( + { + Project = var.project_name + ManagedBy = "terraform" + Application = "openeocraft" + }, + var.tags + ) + workspace_device = var.workspace_volume_size_gb > 0 ? "/dev/vdb" : "" + cloud_init = templatefile("${path.module}/../shared/cloud_init.tftpl", { + docker_image = var.docker_image + docker_cpus = var.docker_cpus + docker_memory_gb = var.docker_memory_gb + api_port = var.api_port + mount_workspace = var.workspace_volume_size_gb > 0 + workspace_device = local.workspace_device + enable_gpu = var.enable_gpu + nvidia_driver_major = var.nvidia_driver_major + swap_size_gb = var.swap_size_gb + }) +} + +resource "openstack_networking_network_v2" "openeocraft" { + name = "${local.name_prefix}-net" + admin_state_up = true +} + +resource "openstack_networking_subnet_v2" "openeocraft" { + name = "${local.name_prefix}-subnet" + network_id = openstack_networking_network_v2.openeocraft.id + cidr = var.network_cidr + ip_version = 4 + dns_nameservers = var.dns_nameservers +} + +resource "openstack_networking_router_v2" "openeocraft" { + name = "${local.name_prefix}-router" + admin_state_up = true + external_network_id = data.openstack_networking_network_v2.external.id +} + +resource "openstack_networking_router_interface_v2" "openeocraft" { + router_id = openstack_networking_router_v2.openeocraft.id + subnet_id = openstack_networking_subnet_v2.openeocraft.id +} + +resource "openstack_networking_secgroup_v2" "openeocraft" { + name = "${local.name_prefix}-sg" + description = "SSH and OpenEOcraft API access" +} + +resource "openstack_networking_secgroup_rule_v2" "ssh" { + for_each = toset(var.ssh_cidr_blocks) + + direction = "ingress" + ethertype = "IPv4" + protocol = "tcp" + port_range_min = 22 + port_range_max = 22 + remote_ip_prefix = each.value + security_group_id = openstack_networking_secgroup_v2.openeocraft.id +} + +resource "openstack_networking_secgroup_rule_v2" "api" { + for_each = toset(var.api_cidr_blocks) + + direction = "ingress" + ethertype = "IPv4" + protocol = "tcp" + port_range_min = var.api_port + port_range_max = var.api_port + remote_ip_prefix = each.value + security_group_id = openstack_networking_secgroup_v2.openeocraft.id +} + +resource "openstack_networking_secgroup_rule_v2" "egress" { + direction = "egress" + ethertype = "IPv4" + remote_ip_prefix = "0.0.0.0/0" + security_group_id = openstack_networking_secgroup_v2.openeocraft.id +} + +resource "openstack_blockstorage_volume_v3" "workspace" { + count = var.workspace_volume_size_gb > 0 ? 1 : 0 + + name = "${local.name_prefix}-workspace" + size = var.workspace_volume_size_gb + enable_online_resize = true +} + +resource "openstack_compute_instance_v2" "openeocraft" { + name = "${local.name_prefix}-vm" + flavor_id = data.openstack_compute_flavor_v2.openeocraft.id + image_id = data.openstack_images_image_v2.ubuntu.id + key_pair = var.key_pair_name + security_groups = [openstack_networking_secgroup_v2.openeocraft.name] + user_data = local.cloud_init + + network { + uuid = openstack_networking_network_v2.openeocraft.id + } + + block_device { + uuid = data.openstack_images_image_v2.ubuntu.id + source_type = "image" + destination_type = "volume" + volume_size = var.root_volume_size_gb + boot_index = 0 + delete_on_termination = true + } + + metadata = local.instance_tags + + lifecycle { + ignore_changes = [image_id] + } +} + +resource "openstack_compute_volume_attach_v2" "workspace" { + count = var.workspace_volume_size_gb > 0 ? 1 : 0 + + instance_id = openstack_compute_instance_v2.openeocraft.id + volume_id = openstack_blockstorage_volume_v3.workspace[0].id + device = local.workspace_device +} + +resource "openstack_networking_floatingip_v2" "openeocraft" { + pool = var.floating_ip_pool +} + +resource "openstack_networking_floatingip_associate_v2" "openeocraft" { + floating_ip = openstack_networking_floatingip_v2.openeocraft.address + port_id = openstack_compute_instance_v2.openeocraft.network[0].port +} diff --git a/terraform/openstack/outputs.tf b/terraform/openstack/outputs.tf new file mode 100644 index 0000000..685965b --- /dev/null +++ b/terraform/openstack/outputs.tf @@ -0,0 +1,54 @@ +output "instance_id" { + description = "Nova instance ID." + value = openstack_compute_instance_v2.openeocraft.id +} + +output "instance_name" { + description = "Nova instance name." + value = openstack_compute_instance_v2.openeocraft.name +} + +output "public_ip" { + description = "Floating IPv4 address of the OpenEOcraft server." + value = openstack_networking_floatingip_v2.openeocraft.address +} + +output "private_ip" { + description = "Tenant network IPv4 address of the instance." + value = openstack_compute_instance_v2.openeocraft.network[0].fixed_ip_v4 +} + +output "api_url" { + description = "OpenEOcraft API base URL." + value = "http://${openstack_networking_floatingip_v2.openeocraft.address}:${var.api_port}" +} + +output "ssh_command" { + description = "Example SSH command using the configured key pair." + value = "ssh ubuntu@${openstack_networking_floatingip_v2.openeocraft.address}" +} + +output "security_group_id" { + description = "Security group attached to the instance." + value = openstack_networking_secgroup_v2.openeocraft.id +} + +output "workspace_volume_id" { + description = "Cinder volume ID for persistent workspace data, if enabled." + value = try(openstack_blockstorage_volume_v3.workspace[0].id, null) +} + +output "flavor_name" { + description = "Nova flavor used for the instance." + value = data.openstack_compute_flavor_v2.openeocraft.name +} + +output "flavor_ram_mb" { + description = "RAM (MB) of the selected flavor." + value = data.openstack_compute_flavor_v2.openeocraft.ram +} + +output "gpu_enabled" { + description = "Whether GPU bootstrap is enabled." + value = var.enable_gpu +} diff --git a/terraform/openstack/terraform.cpu.tfvars.example b/terraform/openstack/terraform.cpu.tfvars.example new file mode 100644 index 0000000..63d3d03 --- /dev/null +++ b/terraform/openstack/terraform.cpu.tfvars.example @@ -0,0 +1,31 @@ +# CPU-only default (32+ GB RAM). Copy to terraform.tfvars and set flavor_name for your cloud. + +region = "" +project_name = "openeocraft" + +key_pair_name = "my-keypair" + +flavor_name = "m1.8xlarge" +image_name = "Ubuntu 22.04" + +external_network_name = "public" +floating_ip_pool = "public" + +ssh_cidr_blocks = ["203.0.113.10/32"] +api_cidr_blocks = ["203.0.113.10/32"] + +enable_gpu = false + +docker_cpus = 8 +docker_memory_gb = 30 + +swap_size_gb = 0 +root_volume_size_gb = 50 +workspace_volume_size_gb = 0 + +docker_image = "brianpondi/openeocraft:latest" + +tags = { + Environment = "dev" + Workload = "cpu" +} diff --git a/terraform/openstack/terraform.gpu.tfvars.example b/terraform/openstack/terraform.gpu.tfvars.example new file mode 100644 index 0000000..b78193a --- /dev/null +++ b/terraform/openstack/terraform.gpu.tfvars.example @@ -0,0 +1,33 @@ +# GPU example — flavor names are cloud-specific. List GPU flavors with: +# openstack flavor list --long | grep -i gpu + +region = "" +project_name = "openeocraft-gpu" + +key_pair_name = "my-keypair" + +flavor_name = "gpu.t4.medium" +image_name = "Ubuntu 22.04" + +external_network_name = "public" +floating_ip_pool = "public" + +ssh_cidr_blocks = ["203.0.113.10/32"] +api_cidr_blocks = ["203.0.113.10/32"] + +enable_gpu = true +nvidia_driver_major = 535 + +docker_cpus = 8 +docker_memory_gb = 30 + +swap_size_gb = 0 +root_volume_size_gb = 120 +workspace_volume_size_gb = 200 + +docker_image = "brianpondi/openeocraft:latest" + +tags = { + Environment = "dev" + Workload = "gpu" +} diff --git a/terraform/openstack/terraform.tfvars.example b/terraform/openstack/terraform.tfvars.example new file mode 100644 index 0000000..089c23b --- /dev/null +++ b/terraform/openstack/terraform.tfvars.example @@ -0,0 +1,33 @@ +region = "" +project_name = "openeocraft" + +# Existing Nova key pair (required). +key_pair_name = "my-keypair" + +# Pick a flavor with >= 32 GB RAM on your cloud: openstack flavor list +flavor_name = "m1.8xlarge" +image_name = "Ubuntu 22.04" + +# External network / floating IP pool on your OpenStack (often "public" or "ext-net"). +external_network_name = "public" +floating_ip_pool = "public" + +# Restrict to your public IP: curl -s ifconfig.me → x.x.x.x/32 +ssh_cidr_blocks = ["203.0.113.10/32"] +api_cidr_blocks = ["203.0.113.10/32"] + +enable_gpu = false + +docker_cpus = 8 +docker_memory_gb = 30 + +swap_size_gb = 0 +root_volume_size_gb = 120 +workspace_volume_size_gb = 200 + +docker_image = "brianpondi/openeocraft:latest" + +tags = { + Environment = "dev" + Workload = "cpu" +} diff --git a/terraform/openstack/variables.tf b/terraform/openstack/variables.tf new file mode 100644 index 0000000..616a157 --- /dev/null +++ b/terraform/openstack/variables.tf @@ -0,0 +1,137 @@ +variable "region" { + description = "OpenStack region name." + type = string + default = "" +} + +variable "project_name" { + description = "Prefix used for resource names and tags." + type = string + default = "openeocraft" +} + +variable "flavor_name" { + description = "Nova flavor with at least 32 GB RAM (x86_64). Run `openstack flavor list` on your cloud and pick a matching name." + type = string + default = "m1.8xlarge" +} + +variable "image_name" { + description = "Glance image name for Ubuntu 22.04 x86_64." + type = string + default = "Ubuntu 22.04" +} + +variable "key_pair_name" { + description = "Name of an existing Nova key pair for SSH access." + type = string +} + +variable "ssh_cidr_blocks" { + description = "CIDR blocks allowed to connect on SSH (port 22)." + type = list(string) + + validation { + condition = length(var.ssh_cidr_blocks) > 0 + error_message = "Provide at least one CIDR block for SSH access." + } +} + +variable "api_cidr_blocks" { + description = "CIDR blocks allowed to reach the OpenEOcraft API (port 8000)." + type = list(string) + + validation { + condition = length(var.api_cidr_blocks) > 0 + error_message = "Provide at least one CIDR block for API access." + } +} + +variable "api_port" { + description = "Port exposed by the OpenEOcraft container." + type = number + default = 8000 +} + +variable "docker_image" { + description = "Docker image for OpenEOcraft." + type = string + default = "brianpondi/openeocraft:latest" +} + +variable "docker_cpus" { + description = "CPU limit passed to docker run (--cpus)." + type = number + default = 8 +} + +variable "docker_memory_gb" { + description = "Memory limit passed to docker run (-m), in gigabytes. Set to 0 to omit the limit. Default 30 GB on a 32+ GB VM leaves headroom for the OS." + type = number + default = 30 + + validation { + condition = var.docker_memory_gb == 0 || var.docker_memory_gb >= 4 + error_message = "docker_memory_gb must be 0 (no limit) or at least 4." + } +} + +variable "swap_size_gb" { + description = "Swap file size in GB added during bootstrap. Use on very small CPU instances only; keep 0 for GPU." + type = number + default = 0 +} + +variable "enable_gpu" { + description = "Install NVIDIA drivers + container runtime and run OpenEOcraft with --gpus all. Requires a GPU flavor on your OpenStack cloud." + type = bool + default = false +} + +variable "nvidia_driver_major" { + description = "NVIDIA driver major version to install on Ubuntu (for example 535)." + type = number + default = 535 +} + +variable "root_volume_size_gb" { + description = "Root disk size in GB." + type = number + default = 120 +} + +variable "workspace_volume_size_gb" { + description = "Optional Cinder volume for /var/openeo/workspace. Set to 0 to disable." + type = number + default = 200 +} + +variable "external_network_name" { + description = "Name of the provider external network used for floating IPs and router gateway." + type = string + default = "public" +} + +variable "floating_ip_pool" { + description = "Floating IP pool (usually the same as external_network_name)." + type = string + default = "public" +} + +variable "network_cidr" { + description = "CIDR for the tenant network created by this stack." + type = string + default = "10.0.0.0/24" +} + +variable "dns_nameservers" { + description = "DNS servers for the tenant subnet." + type = list(string) + default = ["1.1.1.1", "8.8.8.8"] +} + +variable "tags" { + description = "Additional metadata tags applied to the instance." + type = map(string) + default = {} +} diff --git a/terraform/openstack/versions.tf b/terraform/openstack/versions.tf new file mode 100644 index 0000000..d38cd7a --- /dev/null +++ b/terraform/openstack/versions.tf @@ -0,0 +1,23 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + openstack = { + source = "terraform-provider-openstack/openstack" + version = "~> 2.1" + } + } + + # Uncomment and configure for team/production use (Swift/S3-compatible object store). + # backend "s3" { + # bucket = "my-tf-state" + # key = "openeocraft/openstack/terraform.tfstate" + # region = "us-east-1" + # endpoints = { s3 = "https://object-store.example.com" } + # skip_credentials_validation = true + # skip_metadata_api_check = true + # skip_region_validation = true + # } +} + +provider "openstack" {}