Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# =============================================================================
# Tests: Worker node autoscaling conditional expression
#
# PR Change (prod/compute.tf, module "k8s_worker_nodes"):
#
# Before:
# enable_autoscaling = var.enable_autoscaling
#
# After:
# enable_autoscaling = var.k8s_worker_instance_group_size > 0 ? var.enable_autoscaling : false
#
# Intent: When the worker MIG target_size is 0 (fully drained), the autoscaler
# must be disabled unconditionally so GCP does not fight the deliberately-empty
# group. The autoscaler_id output is null when no autoscaler is created.
#
# Tests:
# 1. size > 0, enable_autoscaling=true → autoscaler_id is non-null
# 2. size > 0, enable_autoscaling=false → autoscaler_id is null
# 3. size = 0, enable_autoscaling=true → autoscaler_id is null (conditional override)
# 4. size = 0, enable_autoscaling=false → autoscaler_id is null
# 5. size = 1 (boundary), enable_autoscaling=true → autoscaler_id is non-null
# 6. Master nodes: autoscaler_id always null (hardcoded enable_autoscaling=false)
# =============================================================================

mock_provider "google" {}

# ---------------------------------------------------------------------------
# Test 1: Normal operation — workers with size > 0 and autoscaling enabled.
# The conditional passes var.enable_autoscaling through: true → true.
# autoscaler_id should be a non-null string (resource was created).
# ---------------------------------------------------------------------------
run "worker_autoscaling_enabled_when_group_size_positive" {
command = plan

variables {
k8s_worker_instance_group_size = 2
enable_autoscaling = true
autoscaling_min_replicas = 2
autoscaling_max_replicas = 5

service_account_email = "sa@test-project.iam.gserviceaccount.com"
}

# size(2) > 0 and enable_autoscaling=true → conditional evaluates to true → autoscaler created
assert {
condition = module.k8s_worker_nodes.autoscaler_id != null
error_message = "Worker autoscaler must be created when group size > 0 and enable_autoscaling = true."
}
}

# ---------------------------------------------------------------------------
# Test 2: Workers with size > 0 but autoscaling explicitly disabled.
# The conditional passes var.enable_autoscaling through: false → false.
# autoscaler_id should be null.
# ---------------------------------------------------------------------------
run "worker_autoscaling_disabled_when_group_size_positive_but_autoscaling_false" {
command = plan

variables {
k8s_worker_instance_group_size = 2
enable_autoscaling = false
autoscaling_min_replicas = 2
autoscaling_max_replicas = 5

service_account_email = "sa@test-project.iam.gserviceaccount.com"
}

# size(2) > 0 but enable_autoscaling=false → conditional evaluates to false → no autoscaler
assert {
condition = module.k8s_worker_nodes.autoscaler_id == null
error_message = "Worker autoscaler must not be created when enable_autoscaling = false, even with positive group size."
}
}

# ---------------------------------------------------------------------------
# Test 3 (KEY NEW BEHAVIOR): Workers with size = 0 and enable_autoscaling=true.
# The conditional overrides enable_autoscaling to false, preventing the
# autoscaler from conflicting with a deliberately-empty MIG.
# autoscaler_id should be null despite enable_autoscaling=true.
# ---------------------------------------------------------------------------
run "worker_autoscaling_forced_false_when_group_size_is_zero" {
command = plan

variables {
k8s_worker_instance_group_size = 0
enable_autoscaling = true # user flag is true, but group is empty
autoscaling_min_replicas = 2
autoscaling_max_replicas = 5

service_account_email = "sa@test-project.iam.gserviceaccount.com"
}

# size(0) is NOT > 0 → conditional resolves to false → no autoscaler created
assert {
condition = module.k8s_worker_nodes.autoscaler_id == null
error_message = "Worker autoscaler must be suppressed when group size is 0, regardless of enable_autoscaling value."
}
}

# ---------------------------------------------------------------------------
# Test 4: Workers with size = 0 and enable_autoscaling = false.
# Both the explicit value and the conditional produce false; autoscaler_id null.
# ---------------------------------------------------------------------------
run "worker_autoscaling_null_when_group_size_zero_and_autoscaling_false" {
command = plan

variables {
k8s_worker_instance_group_size = 0
enable_autoscaling = false
autoscaling_min_replicas = 2
autoscaling_max_replicas = 5

service_account_email = "sa@test-project.iam.gserviceaccount.com"
}

assert {
condition = module.k8s_worker_nodes.autoscaler_id == null
error_message = "No autoscaler when group size is 0 and enable_autoscaling is false."
}
}

# ---------------------------------------------------------------------------
# Test 5: Boundary — size = 1 is the minimum value for the conditional to
# evaluate to true (size > 0). Autoscaling should be enabled.
# ---------------------------------------------------------------------------
run "worker_autoscaling_enabled_at_minimum_nonzero_boundary" {
command = plan

variables {
k8s_worker_instance_group_size = 1 # boundary: exactly one instance
enable_autoscaling = true
autoscaling_min_replicas = 1
autoscaling_max_replicas = 5

service_account_email = "sa@test-project.iam.gserviceaccount.com"
}

# size(1) > 0 and enable_autoscaling=true → conditional evaluates to true
assert {
condition = module.k8s_worker_nodes.autoscaler_id != null
error_message = "Autoscaler should be created when group size is exactly 1 (minimum non-zero boundary)."
}
}

# ---------------------------------------------------------------------------
# Test 6: Master nodes always have enable_autoscaling = false (hardcoded).
# Regression: The PR did not change the master node autoscaling policy.
# Master autoscaler_id must remain null even when enable_autoscaling=true globally.
# ---------------------------------------------------------------------------
run "master_autoscaling_always_disabled_regardless_of_global_enable_autoscaling" {
command = plan

variables {
k8s_master_instance_group_size = 1
enable_autoscaling = true # global flag is true, but masters ignore it
k8s_worker_instance_group_size = 2

service_account_email = "sa@test-project.iam.gserviceaccount.com"
}

# Master nodes have enable_autoscaling = false hardcoded in compute.tf → no autoscaler
assert {
condition = module.k8s_master_nodes.autoscaler_id == null
error_message = "Master nodes must never have an autoscaler; enable_autoscaling is hardcoded to false in compute.tf."
}
}
173 changes: 173 additions & 0 deletions terraform/modules/compute/tests/autoscaling_behavior_test.tftest.hcl
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# =============================================================================
# Tests: enable_autoscaling resource creation behavior
#
# PR Change: Worker nodes now use a conditional for enable_autoscaling:
# var.k8s_worker_instance_group_size > 0 ? var.enable_autoscaling : false
#
# The conditional is evaluated in the calling environment (prod/compute.tf)
# before being passed into this module. These module-level tests verify that
# the module correctly creates/omits the autoscaler based on the value it
# receives, validating the downstream behavior of that conditional.
#
# Tests:
# 1. enable_autoscaling=true → google_compute_autoscaler is created (count=1)
# 2. enable_autoscaling=false → google_compute_autoscaler is NOT created (count=0)
# 3. enable_autoscaling=true with cpu_target=0.7 → CPU policy is applied
# 4. Boundary: min_replicas=1 (minimum valid value)
# =============================================================================

mock_provider "google" {}

# ---------------------------------------------------------------------------
# Test 1: enable_autoscaling=true creates exactly one autoscaler resource.
# ---------------------------------------------------------------------------
run "autoscaler_created_when_autoscaling_enabled" {
command = plan

variables {
name_prefix = "test-workers"
network = "projects/test-project/global/networks/test-vpc"
subnetwork = "projects/test-project/regions/asia-northeast3/subnetworks/test-subnet"
create_instance_template = true
create_instance_group = true
instance_group_zone = "asia-northeast3-a"
instance_group_target_size = 2
update_policy_type = "OPPORTUNISTIC"
enable_autoscaling = true
autoscaling_min_replicas = 2
autoscaling_max_replicas = 5
autoscaling_cpu_target = 0.7
}

assert {
condition = length(google_compute_autoscaler.autoscaler) == 1
error_message = "Exactly one autoscaler should be created when enable_autoscaling is true."
}

assert {
condition = google_compute_autoscaler.autoscaler[0].autoscaling_policy[0].min_replicas == 2
error_message = "Autoscaler min_replicas should match autoscaling_min_replicas variable."
}

assert {
condition = google_compute_autoscaler.autoscaler[0].autoscaling_policy[0].max_replicas == 5
error_message = "Autoscaler max_replicas should match autoscaling_max_replicas variable."
}
}

# ---------------------------------------------------------------------------
# Test 2: enable_autoscaling=false creates NO autoscaler.
# This is the value produced when k8s_worker_instance_group_size == 0,
# because the conditional forces enable_autoscaling to false.
# Also mirrors the permanent behavior of k8s_master_nodes.
# ---------------------------------------------------------------------------
run "autoscaler_not_created_when_autoscaling_disabled" {
command = plan

variables {
name_prefix = "test-workers-empty"
network = "projects/test-project/global/networks/test-vpc"
subnetwork = "projects/test-project/regions/asia-northeast3/subnetworks/test-subnet"
create_instance_template = true
create_instance_group = true
instance_group_zone = "asia-northeast3-a"
instance_group_target_size = 0
update_policy_type = "OPPORTUNISTIC"
# The conditional in prod/compute.tf resolves to false when target_size == 0.
# Here we pass false directly to test the module-level behavior.
enable_autoscaling = false
}

assert {
condition = length(google_compute_autoscaler.autoscaler) == 0
error_message = "No autoscaler should be created when enable_autoscaling is false (as when group size is 0)."
}
}

# ---------------------------------------------------------------------------
# Test 3: CPU utilization target of 0.7 is applied when autoscaling is on.
# Worker nodes use autoscaling_cpu_target = 0.7 (hardcoded in compute.tf).
# ---------------------------------------------------------------------------
run "autoscaler_cpu_target_applied_at_0_7" {
command = plan

variables {
name_prefix = "test-workers-cpu"
network = "projects/test-project/global/networks/test-vpc"
subnetwork = "projects/test-project/regions/asia-northeast3/subnetworks/test-subnet"
create_instance_template = true
create_instance_group = true
instance_group_zone = "asia-northeast3-a"
instance_group_target_size = 2
update_policy_type = "OPPORTUNISTIC"
enable_autoscaling = true
autoscaling_min_replicas = 2
autoscaling_max_replicas = 5
autoscaling_cpu_target = 0.7
}

assert {
condition = google_compute_autoscaler.autoscaler[0].autoscaling_policy[0].cpu_utilization[0].target == 0.7
error_message = "CPU utilization target should be 0.7 as configured in compute.tf."
}
}

# ---------------------------------------------------------------------------
# Test 4: Boundary — minimum valid non-zero group size (size=1) with
# autoscaling enabled results in autoscaler being created.
# This corresponds to the boundary of the conditional expression (size > 0).
# ---------------------------------------------------------------------------
run "autoscaler_created_at_minimum_nonzero_group_size" {
command = plan

variables {
name_prefix = "test-workers-single"
network = "projects/test-project/global/networks/test-vpc"
subnetwork = "projects/test-project/regions/asia-northeast3/subnetworks/test-subnet"
create_instance_template = true
create_instance_group = true
instance_group_zone = "asia-northeast3-a"
instance_group_target_size = 1
update_policy_type = "OPPORTUNISTIC"
enable_autoscaling = true
autoscaling_min_replicas = 1
autoscaling_max_replicas = 5
autoscaling_cpu_target = 0.7
}

assert {
condition = length(google_compute_autoscaler.autoscaler) == 1
error_message = "Autoscaler should be created for minimum non-zero group size (size=1)."
}
}

# ---------------------------------------------------------------------------
# Test 5: Master node configuration — autoscaling hardcoded to false,
# update_policy_type = "OPPORTUNISTIC". No autoscaler regardless of group size.
# Regression test: master nodes must never get an autoscaler.
# ---------------------------------------------------------------------------
run "master_nodes_never_have_autoscaler" {
command = plan

variables {
name_prefix = "test-masters"
network = "projects/test-project/global/networks/test-vpc"
subnetwork = "projects/test-project/regions/asia-northeast3/subnetworks/test-subnet"
create_instance_template = true
create_instance_group = true
instance_group_zone = "asia-northeast3-a"
instance_group_target_size = 1
update_policy_type = "OPPORTUNISTIC"
enable_autoscaling = false # Master nodes: always false
}

assert {
condition = length(google_compute_autoscaler.autoscaler) == 0
error_message = "Master nodes must never have an autoscaler (enable_autoscaling is hardcoded to false)."
}

assert {
condition = google_compute_instance_group_manager.instance_group[0].update_policy[0].type == "OPPORTUNISTIC"
error_message = "Master node MIG must use OPPORTUNISTIC update policy."
}
}
Loading
Loading