Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 21 additions & 16 deletions modules/management/tenant-space/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@ locals {
namespace_storage_limit = var.namespace_storage_limit != null ? var.namespace_storage_limit : var.storage_limit
namespaces = var.namespaces != null ? (var.create_default_namespace ? distinct(concat([var.project_name], var.namespaces)) : var.namespaces) : (var.create_default_namespace ? [var.project_name] : [])

# create_net_ns is true when explicitly requested OR when a vlan_id is set.
# create_net_ns is true when explicitly requested OR when vlan_id has entries.
# Keeping backward compat: callers already using vlan_id still get the namespace.
create_net_ns = var.create_network_namespace || var.vlan_id != null
network_namespace = local.create_net_ns ? "${var.project_name}-net" : null
create_net_ns = var.create_network_namespace || (var.vlan_id != null && length(var.vlan_id) > 0)
network_namespace = local.create_net_ns ? coalesce(var.network_namespace_name, "${var.project_name}-net") : null
Comment thread
HiranAdikari marked this conversation as resolved.

# VyOS path: compute a deterministic /23 subnet from 10.0.0.0/8 using the VLAN
# index. Only relevant when vyos_endpoint is set; auto-routed environments
# (physical switch / DigiOps-issued VLANs) do not need explicit subnets.
use_vyos = var.vlan_id != null && var.vyos_endpoint != null
tenant_subnet = local.use_vyos ? cidrsubnet("10.0.0.0/8", 15, var.vlan_id - 1000) : null
# index. Only relevant when vyos_endpoint is set and exactly one VLAN is given.
# Auto-routed environments (physical switch / DigiOps-issued VLANs) use multiple
# VLANs with route_mode=auto; no explicit subnets needed.
use_vyos = var.vlan_id != null && length(var.vlan_id) > 0 && var.vyos_endpoint != null
tenant_subnet = local.use_vyos ? cidrsubnet("10.0.0.0/8", 15, var.vlan_id[0] - 1000) : null
tenant_gateway = local.use_vyos ? cidrhost(local.tenant_subnet, 1) : null
}

Expand Down Expand Up @@ -54,6 +55,10 @@ resource "rancher2_project" "this" {
])
error_message = "Quota variables (memory_limit, storage_limit, namespace_*_limit) are only applied when cpu_limit is set. Either set cpu_limit or remove the other quota variables."
}
precondition {
condition = !local.use_vyos || length(var.vlan_id) == 1
error_message = "VyOS path requires exactly one VLAN ID. Set vyos_endpoint = null for multi-VLAN auto-route configurations."
}
}
}

Expand Down Expand Up @@ -100,21 +105,21 @@ resource "rancher2_namespace" "network" {
# the harvester_network resource to attach VMs to the correct VLAN.

resource "harvester_network" "tenant" {
count = var.vlan_id != null ? 1 : 0
name = "${var.project_name}-vlan${var.vlan_id}"
for_each = var.vlan_id != null ? toset([for id in var.vlan_id : tostring(id)]) : toset([])
name = lookup(var.vlan_network_names, each.value, "${var.project_name}-vlan${each.value}")
namespace = rancher2_namespace.network[0].name
vlan_id = var.vlan_id
vlan_id = tonumber(each.value)
cluster_network_name = var.cluster_network_name

# VyOS path: manual routing with a deterministic /23 from 10.0.0.0/8.
# VyOS path: manual routing with a deterministic /23 from 10.0.0.0/8 (single VLAN only).
# DigiOps / physical-switch path: auto routing — the upstream router
# advertises the gateway; no explicit CIDR or gateway needed here.
route_mode = local.use_vyos ? "manual" : "auto"
route_cidr = local.tenant_subnet
route_gateway = local.tenant_gateway
route_cidr = local.use_vyos ? local.tenant_subnet : null
route_gateway = local.use_vyos ? local.tenant_gateway : null

# When VyOS is configured, wait for the vif/DHCP to be provisioned before
# the network is visible to tenant VMs. count=0 module depends_on is a no-op.
# the network is visible to tenant VMs. for_each with empty set is a no-op.
depends_on = [rancher2_namespace.network, module.vyos_tenant]
}

Expand All @@ -124,11 +129,11 @@ resource "harvester_network" "tenant" {
# vif sub-interface, DHCP server, and NAT rule in addition.

module "vyos_tenant" {
count = var.vlan_id != null && var.vyos_endpoint != null ? 1 : 0
count = local.use_vyos ? 1 : 0
source = "../../network/vyos-tenant"

tenant_name = var.project_name
vlan_id = var.vlan_id
vlan_id = var.vlan_id[0]
vyos_endpoint = var.vyos_endpoint
vyos_api_key = var.vyos_api_key
}
Expand Down
6 changes: 3 additions & 3 deletions modules/management/tenant-space/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ output "network_namespace_id" {
description = "Rancher namespace ID of the network namespace. Non-null when create_network_namespace = true or vlan_id is set."
}

output "network_name" {
value = var.vlan_id != null ? "${harvester_network.tenant[0].namespace}/${harvester_network.tenant[0].name}" : null
description = "Full harvester_network reference (<namespace>/<name>) for attaching tenant VMs. Null when vlan_id is not set."
output "network_names" {
value = { for id, r in harvester_network.tenant : id => "${r.namespace}/${r.name}" }
description = "Map of VLAN ID (string) → full harvester_network reference (<namespace>/<name>) for attaching tenant VMs. Empty map when vlan_id is null or empty."
}

output "subnet_cidr" {
Expand Down
27 changes: 23 additions & 4 deletions modules/management/tenant-space/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -110,15 +110,34 @@ variable "create_network_namespace" {
}

variable "vlan_id" {
type = number
description = "VLAN ID for this tenant's network (>= 1000). When set, always creates the network namespace and a harvester_network. Routing mode depends on vyos_endpoint: if set, route_mode=manual with a deterministic /23 from 10.0.0.0/8 plus full VyOS vif/DHCP/NAT config; if null, route_mode=auto (upstream router / DigiOps-issued VLAN handles routing). When vlan_id is null, no network resources are created."
type = list(number)
description = "List of VLAN IDs for this tenant's networks. Each entry creates a harvester_network in the network namespace. When non-empty, the network namespace is always created. VyOS path (vyos_endpoint set) requires exactly one VLAN ID — a deterministic /23 from 10.0.0.0/8 is computed and full VyOS vif/DHCP/NAT config is provisioned. Auto-route path (vyos_endpoint null) supports multiple VLANs — the upstream router handles routing. When null or empty, no network resources are created."
default = null
validation {
condition = var.vlan_id == null || (var.vlan_id >= 1 && var.vlan_id <= 4094)
error_message = "vlan_id must be a valid 802.1Q VLAN ID (1–4094)."
condition = var.vlan_id == null || (
length(var.vlan_id) > 0 &&
alltrue([for id in var.vlan_id : id >= 1 && id <= 4094])
)
error_message = "vlan_id must be null or a non-empty list of valid 802.1Q VLAN IDs (1–4094)."
}
}

variable "network_namespace_name" {
type = string
description = "Override the name of the network namespace. Defaults to <project_name>-net. Use this when importing a brownfield namespace whose name differs from the default."
default = null
validation {
condition = var.network_namespace_name == null ? true : trimspace(var.network_namespace_name) != ""
error_message = "network_namespace_name must be null or a non-empty string."
}
}

variable "vlan_network_names" {
type = map(string)
description = "Map of VLAN ID (as string) to harvester_network resource name override. Use when importing brownfield networks whose names differ from the default <project_name>-vlan<id> pattern. Example: { \"608\" = \"vm-subnet-008\" }."
default = {}
}

variable "cluster_network_name" {
type = string
description = "Harvester cluster network carrying tenant VLANs. Defaults to 'vm-network' — override only if your datacenter uses a different cluster network name."
Expand Down
Loading