Skip to content

Commit df7908d

Browse files
committed
tests(redshift-scheduler): implement stop operation
This commit adds end-to-end testing to validate the AWS Lambda redshift scheduler actually works expected. They key changes include: * Created a new test-execution module that: * Triggers the aws stop lambda * Waits appropriate time intervals before and after function execution * Retrieves the redshift power states to verify function effectiveness
1 parent e26bfa0 commit df7908d

File tree

10 files changed

+288
-14
lines changed

10 files changed

+288
-14
lines changed

examples/redshift-scheduler/main.tf

Lines changed: 106 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,94 @@ resource "aws_kms_key" "scheduler" {
66
deletion_window_in_days = 7
77
}
88

9+
resource "aws_vpc" "redshift_vpc" {
10+
cidr_block = "10.0.0.0/16"
11+
tags = {
12+
Name = "redshift-vpc-${random_pet.suffix.id}"
13+
}
14+
}
15+
16+
resource "aws_internet_gateway" "redshift_igw" {
17+
vpc_id = aws_vpc.redshift_vpc.id
18+
tags = {
19+
Name = "redshift-igw-${random_pet.suffix.id}"
20+
}
21+
}
22+
23+
resource "aws_route_table" "redshift_route_table" {
24+
vpc_id = aws_vpc.redshift_vpc.id
25+
26+
route {
27+
cidr_block = "0.0.0.0/0"
28+
gateway_id = aws_internet_gateway.redshift_igw.id
29+
}
30+
31+
tags = {
32+
Name = "redshift-rt-${random_pet.suffix.id}"
33+
}
34+
}
35+
36+
resource "aws_route_table_association" "redshift_rt_assoc_1" {
37+
subnet_id = aws_subnet.redshift_subnet_1.id
38+
route_table_id = aws_route_table.redshift_route_table.id
39+
}
40+
41+
resource "aws_route_table_association" "redshift_rt_assoc_2" {
42+
subnet_id = aws_subnet.redshift_subnet_2.id
43+
route_table_id = aws_route_table.redshift_route_table.id
44+
}
45+
46+
resource "aws_subnet" "redshift_subnet_1" {
47+
vpc_id = aws_vpc.redshift_vpc.id
48+
cidr_block = "10.0.1.0/24"
49+
availability_zone = "eu-west-1a"
50+
map_public_ip_on_launch = true
51+
}
52+
53+
resource "aws_subnet" "redshift_subnet_2" {
54+
vpc_id = aws_vpc.redshift_vpc.id
55+
cidr_block = "10.0.2.0/24"
56+
availability_zone = "eu-west-1b"
57+
map_public_ip_on_launch = true
58+
}
59+
60+
resource "aws_redshift_subnet_group" "redshift_subnet_group" {
61+
name = "redshift-subnet-group-${random_pet.suffix.id}"
62+
subnet_ids = [aws_subnet.redshift_subnet_1.id, aws_subnet.redshift_subnet_2.id]
63+
}
64+
65+
resource "aws_security_group" "redshift_sg" {
66+
name = "redshift-sg-${random_pet.suffix.id}"
67+
description = "Security group for Redshift clusters"
68+
vpc_id = aws_vpc.redshift_vpc.id
69+
70+
ingress {
71+
from_port = 5439
72+
to_port = 5439
73+
protocol = "tcp"
74+
cidr_blocks = ["0.0.0.0/0"]
75+
description = "Allow Redshift access"
76+
}
77+
78+
egress {
79+
from_port = 0
80+
to_port = 0
81+
protocol = "-1"
82+
cidr_blocks = ["0.0.0.0/0"]
83+
}
84+
}
85+
986
resource "aws_redshift_cluster" "scheduled" {
10-
cluster_identifier = "test-to-stop-${random_pet.suffix.id}"
11-
database_name = "mydb"
12-
master_username = "exampleuser"
13-
master_password = "Mustbe8characters"
14-
node_type = "dc2.large"
15-
cluster_type = "single-node"
16-
skip_final_snapshot = true
87+
cluster_identifier = "test-to-stop-${random_pet.suffix.id}"
88+
database_name = "mydb"
89+
master_username = "exampleuser"
90+
master_password = "Mustbe8characters"
91+
node_type = "dc2.large"
92+
cluster_type = "single-node"
93+
publicly_accessible = false
94+
skip_final_snapshot = true
95+
cluster_subnet_group_name = aws_redshift_subnet_group.redshift_subnet_group.name
96+
vpc_security_group_ids = [aws_security_group.redshift_sg.id]
1797

1898
tags = {
1999
tostop = "true"
@@ -26,13 +106,16 @@ resource "aws_redshift_cluster_snapshot" "scheduled" {
26106
}
27107

28108
resource "aws_redshift_cluster" "not_scheduled" {
29-
cluster_identifier = "test-not-to-stop-${random_pet.suffix.id}"
30-
database_name = "mydb"
31-
master_username = "exampleuser"
32-
master_password = "Mustbe8characters"
33-
node_type = "dc2.large"
34-
cluster_type = "single-node"
35-
skip_final_snapshot = true
109+
cluster_identifier = "test-not-to-stop-${random_pet.suffix.id}"
110+
database_name = "mydb"
111+
master_username = "exampleuser"
112+
master_password = "Mustbe8characters"
113+
node_type = "dc2.large"
114+
cluster_type = "single-node"
115+
publicly_accessible = false
116+
skip_final_snapshot = true
117+
cluster_subnet_group_name = aws_redshift_subnet_group.redshift_subnet_group.name
118+
vpc_security_group_ids = [aws_security_group.redshift_sg.id]
36119

37120
tags = {
38121
tostop = "false"
@@ -71,3 +154,12 @@ module "redshift-start-monday" {
71154
value = "true"
72155
}
73156
}
157+
158+
module "test-execution" {
159+
count = var.test_mode ? 1 : 0
160+
source = "./test-execution"
161+
162+
lambda_stop_name = module.redshift-stop-friday.scheduler_lambda_name
163+
redshift_cluster_to_scheduled_name = aws_redshift_cluster.scheduled.cluster_identifier
164+
redshift_cluster_not_scheduled_name = aws_redshift_cluster.not_scheduled.cluster_identifier
165+
}

examples/redshift-scheduler/outputs.tf

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@ output "lambda_start_name" {
1515
output "lambda_start_arn" {
1616
value = module.redshift-start-monday.scheduler_lambda_arn
1717
}
18+
19+
output "redshift_cluster_scheduled_identifier" {
20+
value = aws_redshift_cluster.scheduled.cluster_identifier
21+
}
22+
23+
output "redshift_cluster_not_scheduled_identifier" {
24+
value = aws_redshift_cluster.not_scheduled.cluster_identifier
25+
}

examples/redshift-scheduler/terraform.tftest.hcl

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
run "create_test_infrastructure" {
22
command = apply
33

4+
variables {
5+
test_mode = true
6+
}
7+
48
assert {
59
condition = module.redshift-stop-friday.scheduler_lambda_name == "stop-redshift-${random_pet.suffix.id}"
610
error_message = "Invalid Stop lambda name"
@@ -10,4 +14,28 @@ run "create_test_infrastructure" {
1014
condition = module.redshift-start-monday.scheduler_lambda_name == "start-redshift-${random_pet.suffix.id}"
1115
error_message = "Invalid Start lambda name"
1216
}
17+
18+
assert {
19+
condition = module.test-execution[0].redshift_cluster_to_scheduled_state == "pausing\n"
20+
error_message = "Invalid Redshift cluster state"
21+
}
22+
23+
assert {
24+
condition = module.test-execution[0].redshift_cluster_not_scheduled_state == "available\n"
25+
error_message = "Invalid Redshift cluster state"
26+
}
27+
}
28+
29+
# Add this cleanup step to restore the cluster to 'available' state before destruction
30+
run "cleanup_test_resources" {
31+
command = apply
32+
33+
variables {
34+
redshift_cluster_name = run.create_test_infrastructure.redshift_cluster_scheduled_identifier
35+
}
36+
37+
# This will start the stopped cluster to ensure proper deletion
38+
module {
39+
source = "./test-cleanup"
40+
}
1341
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
resource "null_resource" "start_docdb_cluster" {
2+
provisioner "local-exec" {
3+
command = <<-EOT
4+
TIMEOUT=600
5+
START_TIME=$(date +%s)
6+
7+
echo "Waiting for Redshift cluster ${var.redshift_cluster_name} to reach 'stopped' state (timeout: $TIMEOUT seconds)..."
8+
9+
while true; do
10+
# Check the current state of the DocumentDB cluster
11+
CURRENT_STATE=$(aws redshift describe-clusters --cluster-identifier ${var.redshift_cluster_name} --query 'Clusters[0].ClusterStatus' --output text)
12+
13+
# Get current elapsed time
14+
CURRENT_TIME=$(date +%s)
15+
ELAPSED=$((CURRENT_TIME - START_TIME))
16+
17+
# Check if cluster is paused
18+
if [ "$CURRENT_STATE" = "paused" ]; then
19+
aws redshift start-cluster --cluster-identifier ${var.redshift_cluster_name}
20+
exit 0
21+
fi
22+
23+
# Check if we've exceeded the timeout
24+
if [ $ELAPSED -ge $TIMEOUT ]; then
25+
echo "Timeout reached. Redshift cluster did not reach 'paused' state within $TIMEOUT seconds."
26+
exit 1
27+
fi
28+
29+
# Wait 10 seconds before checking again
30+
echo "Current state: $CURRENT_STATE (elapsed: $ELAPSED seconds/ $TIMEOUT seconds)..."
31+
sleep 10
32+
done
33+
EOT
34+
}
35+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
variable "redshift_cluster_name" {
2+
description = "Name of the Redshift cluster to start before deletion"
3+
type = string
4+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
resource "time_sleep" "before_stop_wait_240_seconds" {
2+
create_duration = "240s"
3+
}
4+
5+
resource "aws_lambda_invocation" "stop_redshift" {
6+
function_name = var.lambda_stop_name
7+
8+
input = jsonencode({
9+
key1 = "value1"
10+
key2 = "value2"
11+
})
12+
13+
depends_on = [time_sleep.before_stop_wait_240_seconds]
14+
}
15+
16+
resource "time_sleep" "after_stop_wait_60_seconds" {
17+
create_duration = "60s"
18+
19+
depends_on = [aws_lambda_invocation.stop_redshift]
20+
}
21+
22+
resource "null_resource" "redshift_cluster_to_scheduled" {
23+
provisioner "local-exec" {
24+
command = <<-EOT
25+
aws redshift describe-clusters \
26+
--cluster-identifier ${var.redshift_cluster_to_scheduled_name} \
27+
--query 'Clusters[0].ClusterStatus' \
28+
--output text > ${path.module}/redshift_cluster_to_scheduled.state
29+
EOT
30+
}
31+
32+
depends_on = [time_sleep.after_stop_wait_60_seconds]
33+
}
34+
35+
data "local_file" "redshift_cluster_to_scheduled" {
36+
filename = "${path.module}/redshift_cluster_to_scheduled.state"
37+
38+
depends_on = [null_resource.redshift_cluster_to_scheduled]
39+
}
40+
41+
resource "null_resource" "redshift_cluster_not_scheduled" {
42+
provisioner "local-exec" {
43+
command = <<-EOT
44+
aws redshift describe-clusters \
45+
--cluster-identifier ${var.redshift_cluster_not_scheduled_name} \
46+
--query 'Clusters[0].ClusterStatus' \
47+
--output text > ${path.module}/redshift_cluster_not_scheduled.state
48+
EOT
49+
}
50+
51+
depends_on = [time_sleep.after_stop_wait_60_seconds]
52+
}
53+
54+
data "local_file" "redshift_cluster_not_scheduled" {
55+
filename = "${path.module}/redshift_cluster_not_scheduled.state"
56+
57+
depends_on = [null_resource.redshift_cluster_not_scheduled]
58+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
output "redshift_cluster_to_scheduled_state" {
2+
description = "State of the Redshift cluster that should be stopped"
3+
value = data.local_file.redshift_cluster_to_scheduled.content
4+
}
5+
6+
output "redshift_cluster_not_scheduled_state" {
7+
description = "State of the Redshift cluster that should not be stopped"
8+
value = data.local_file.redshift_cluster_not_scheduled.content
9+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
variable "lambda_stop_name" {
2+
description = "Name of the lambda function to stop Redshift clusters"
3+
type = string
4+
}
5+
6+
variable "redshift_cluster_to_scheduled_name" {
7+
description = "Name of the Redshift cluster that should be stopped"
8+
type = string
9+
}
10+
11+
variable "redshift_cluster_not_scheduled_name" {
12+
description = "Name of the Redshift cluster that should not be stopped"
13+
type = string
14+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
terraform {
2+
required_version = ">= 1.3.0"
3+
required_providers {
4+
aws = {
5+
source = "hashicorp/aws"
6+
version = ">= 5.94.1"
7+
}
8+
null = {
9+
source = "hashicorp/null"
10+
version = ">= 3.0.0, < 4.0"
11+
}
12+
local = {
13+
source = "hashicorp/local"
14+
version = ">= 2.0.0, < 3.0"
15+
}
16+
time = {
17+
source = "hashicorp/time"
18+
version = "0.13.0"
19+
}
20+
}
21+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
variable "test_mode" {
2+
description = "Whether to run in test mode"
3+
type = bool
4+
default = false
5+
}

0 commit comments

Comments
 (0)