Skip to content

Commit 2107571

Browse files
authored
Add E2e tests global cluster (#116)
Issue #, if available: Description of changes: Add simple e2e tests for global cluster CRD. It's just testing creation and deletion of global cluster. currently RDS global cluster doesn't support tags, so not add tag part here. It is part of aws-controllers-k8s/community#1440 Testing result: ``` scheduling tests via LoadScheduling tests/test_global_cluster.py::TestGlobalCluster::test_crud_postgresql_globalcluster [gw0] [100%] PASSED tests/test_global_cluster.py::TestGlobalCluster::test_crud_postgresql_globalcluster =============================================================================================================== 1 passed in 154.64s (0:02:34) ================================================================================================================ 2022-08-29T22:22:11+00:00 [INFO] Tests succeeded! 2022-08-29T22:22:11+00:00 [INFO] Running test cleanup ... INFO:root:🧹 Cleaning up resources ... INFO:root:Attempting cleanup Role INFO:root:Successfully cleaned up Role INFO:root:Attempting cleanup VPC INFO:root:Attempting cleanup Subnets INFO:root:Attempting cleanup RouteTable INFO:root:Successfully cleaned up RouteTable INFO:root:Successfully cleaned up Subnets INFO:root:Attempting cleanup Subnets INFO:root:Attempting cleanup RouteTable INFO:root:Attempting cleanup InternetGateway INFO:root:Successfully cleaned up InternetGateway INFO:root:Successfully cleaned up RouteTable INFO:root:Successfully cleaned up Subnets INFO:root:Successfully cleaned up VPC ``` By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent c288c4f commit 2107571

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed

test/e2e/global_cluster.py

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
# not use this file except in compliance with the License. A copy of the
5+
# License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is distributed
10+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
# express or implied. See the License for the specific language governing
12+
# permissions and limitations under the License.
13+
14+
"""Utilities for working with Global Cluster resources"""
15+
16+
import datetime
17+
import time
18+
import typing
19+
20+
import boto3
21+
import pytest
22+
23+
DEFAULT_WAIT_UNTIL_TIMEOUT_SECONDS = 60*10
24+
DEFAULT_WAIT_UNTIL_INTERVAL_SECONDS = 15
25+
DEFAULT_WAIT_UNTIL_DELETED_TIMEOUT_SECONDS = 60*10
26+
DEFAULT_WAIT_UNTIL_DELETED_INTERVAL_SECONDS = 15
27+
28+
GlobalClusterMatchFunc = typing.NewType(
29+
'GlobalClusterMatchFunc',
30+
typing.Callable[[dict], bool],
31+
)
32+
33+
class StatusMatcher:
34+
def __init__(self, status):
35+
self.match_on = status
36+
37+
def __call__(self, record: dict) -> bool:
38+
return 'Status' in record and record['Status'] == self.match_on
39+
40+
41+
def status_matches(status: str) -> GlobalClusterMatchFunc:
42+
return StatusMatcher(status)
43+
44+
45+
def wait_until(
46+
global_cluster_id: str,
47+
match_fn: GlobalClusterMatchFunc,
48+
timeout_seconds: int = DEFAULT_WAIT_UNTIL_TIMEOUT_SECONDS,
49+
interval_seconds: int = DEFAULT_WAIT_UNTIL_INTERVAL_SECONDS,
50+
) -> None:
51+
"""Waits until a DB global cluster with a supplied ID is returned from the RDS API
52+
and the matching functor returns True.
53+
54+
Usage:
55+
from e2e.db_global_cluster import wait_until, status_matches
56+
57+
wait_until(
58+
global_cluster_id,
59+
status_matches("available"),
60+
)
61+
62+
Raises:
63+
pytest.fail upon timeout
64+
"""
65+
now = datetime.datetime.now()
66+
timeout = now + datetime.timedelta(seconds=timeout_seconds)
67+
68+
while not match_fn(get(global_cluster_id)):
69+
if datetime.datetime.now() >= timeout:
70+
pytest.fail("failed to match Global Cluster before timeout")
71+
time.sleep(interval_seconds)
72+
73+
74+
def wait_until_deleted(
75+
global_cluster_id: str,
76+
timeout_seconds: int = DEFAULT_WAIT_UNTIL_DELETED_TIMEOUT_SECONDS,
77+
interval_seconds: int = DEFAULT_WAIT_UNTIL_DELETED_INTERVAL_SECONDS,
78+
) -> None:
79+
"""Waits until a Global Cluster with a supplied ID is no longer returned from
80+
the RDS API.
81+
82+
Usage:
83+
from e2e.global_cluster import wait_until_deleted
84+
85+
wait_until_deleted(global_cluster_id)
86+
87+
Raises:
88+
pytest.fail upon timeout or if the Global Cluster goes to any other status
89+
other than 'deleting'
90+
"""
91+
now = datetime.datetime.now()
92+
timeout = now + datetime.timedelta(seconds=timeout_seconds)
93+
94+
while True:
95+
if datetime.datetime.now() >= timeout:
96+
pytest.fail(
97+
"Timed out waiting for Global Cluster to be "
98+
"deleted in RDS API"
99+
)
100+
time.sleep(interval_seconds)
101+
102+
latest = get(global_cluster_id)
103+
if latest is None:
104+
break
105+
106+
if latest['Status'] != "deleting":
107+
pytest.fail(
108+
"Status is not 'deleting' for global cluster that was "
109+
"deleted. Status is " + latest['Status']
110+
)
111+
112+
113+
def get(global_cluster_id):
114+
"""Returns a dict containing the Global Cluster record from the RDS API.
115+
116+
If no such Global Cluster exists, returns None.
117+
"""
118+
c = boto3.client('rds')
119+
try:
120+
resp = c.describe_global_clusters(GlobalClusterIdentifier=global_cluster_id)
121+
assert len(resp['GlobalClusters']) == 1
122+
return resp['GlobalClusters'][0]
123+
except c.exceptions.GlobalClusterNotFoundFault:
124+
return None
125+
126+
127+
128+
def get_tags(global_cluster_arn):
129+
"""Returns a dict containing the Global Cluster's tag records from the RDS API.
130+
Currently RDS doesn't support add tags to global cluster, so this is a noop for now
131+
If no such global cluster exists, returns None.
132+
"""
133+
c = boto3.client('rds')
134+
try:
135+
resp = c.list_tags_for_resource(
136+
ResourceName=global_cluster_arn,
137+
)
138+
return resp['TagList']
139+
except c.exceptions.GlobalClusterNotFoundFault:
140+
return None
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
apiVersion: rds.services.k8s.aws/v1alpha1
2+
kind: GlobalCluster
3+
metadata:
4+
name: $GLOBAL_CLUSTER_NAME
5+
spec:
6+
globalClusterIdentifier: $GLOBAL_CLUSTER_NAME
7+
engine: $GLOBAL_CLUSTER_ENGINE
8+
databaseName: $GLOBAL_CLUSTER_DB_NAME

test/e2e/tests/test_global_cluster.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
# not use this file except in compliance with the License. A copy of the
5+
# License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is distributed
10+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
# express or implied. See the License for the specific language governing
12+
# permissions and limitations under the License.
13+
14+
"""Integration tests for the RDS API Global Cluster resource
15+
"""
16+
17+
import time
18+
19+
import pytest
20+
21+
from acktest.k8s import resource as k8s
22+
from e2e import service_marker, CRD_GROUP, CRD_VERSION, load_rds_resource
23+
from e2e.replacement_values import REPLACEMENT_VALUES
24+
from e2e import condition
25+
from e2e import global_cluster
26+
from e2e.fixtures import k8s_secret
27+
from e2e import tag
28+
from e2e.bootstrap_resources import get_bootstrap_resources
29+
30+
RESOURCE_PLURAL = 'globalclusters'
31+
32+
DELETE_WAIT_AFTER_SECONDS = 120
33+
34+
# Time we wait after resource becoming available in RDS and checking the CR's
35+
# Status has been updated.
36+
CHECK_STATUS_WAIT_SECONDS = 60*4
37+
38+
MODIFY_WAIT_AFTER_SECONDS = 20
39+
40+
41+
@service_marker
42+
@pytest.mark.canary
43+
class TestGlobalCluster:
44+
45+
def test_crud_postgresql_globalcluster(
46+
self,
47+
):
48+
global_cluster_id = "my-test-global-cluster"
49+
global_cluster_engine = "aurora-postgresql"
50+
global_cluster_db_name = 'testdb'
51+
52+
replacements = REPLACEMENT_VALUES.copy()
53+
replacements["GLOBAL_CLUSTER_NAME"] = global_cluster_id
54+
replacements["GLOBAL_CLUSTER_ENGINE"] = global_cluster_engine
55+
replacements["GLOBAL_CLUSTER_DB_NAME"] = global_cluster_db_name
56+
57+
resource_data = load_rds_resource(
58+
"global_cluster",
59+
additional_replacements=replacements,
60+
)
61+
62+
ref = k8s.CustomResourceReference(
63+
CRD_GROUP, CRD_VERSION, RESOURCE_PLURAL,
64+
global_cluster_id, namespace="default",
65+
)
66+
# First try create global cluster
67+
k8s.create_custom_resource(ref, resource_data)
68+
cr = k8s.wait_resource_consumed_by_controller(ref)
69+
70+
# global cluster is available immediately upon created
71+
assert cr is not None
72+
assert 'status' in cr
73+
assert 'status' in cr['status']
74+
assert cr['status']['status'] == 'available'
75+
76+
# assert global cluster is synced
77+
cr = k8s.get_resource(ref)
78+
assert cr is not None
79+
assert 'status' in cr
80+
assert 'status' in cr['status']
81+
condition.assert_synced(ref)
82+
83+
latest = global_cluster.get(global_cluster_id)
84+
arn = latest['GlobalClusterArn']
85+
86+
# now start delete global cluster
87+
k8s.delete_custom_resource(ref)
88+
89+
time.sleep(DELETE_WAIT_AFTER_SECONDS)
90+
91+
global_cluster.wait_until_deleted(global_cluster_id)

0 commit comments

Comments
 (0)