Skip to content

Commit 2241294

Browse files
test: Add secrets manager test and iam+failover (#1069)
1 parent d13b509 commit 2241294

2 files changed

Lines changed: 280 additions & 3 deletions

File tree

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License").
4+
# You may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import json
16+
from typing import Callable
17+
from uuid import uuid4
18+
19+
import boto3
20+
import pytest
21+
22+
from aws_advanced_python_wrapper import AwsWrapperConnection
23+
from aws_advanced_python_wrapper.errors import (AwsWrapperError,
24+
FailoverSuccessError)
25+
from aws_advanced_python_wrapper.utils.properties import Properties
26+
from .utils.conditions import (disable_on_features, enable_on_deployments,
27+
enable_on_features, enable_on_num_instances)
28+
from .utils.database_engine_deployment import DatabaseEngineDeployment
29+
from .utils.driver_helper import DriverHelper
30+
from .utils.rds_test_utility import RdsTestUtility
31+
from .utils.test_environment import TestEnvironment
32+
from .utils.test_environment_features import TestEnvironmentFeatures
33+
34+
35+
@enable_on_deployments([DatabaseEngineDeployment.AURORA, DatabaseEngineDeployment.RDS_MULTI_AZ_CLUSTER])
36+
class TestAwsSecretsManager:
37+
"""Test class for AWS Secrets Manager authentication"""
38+
39+
@pytest.fixture(scope='class')
40+
def props(self):
41+
return Properties({
42+
"plugins": "aws_secrets_manager",
43+
"socket_timeout": 10,
44+
"connect_timeout": 10
45+
})
46+
47+
@pytest.fixture(scope='class')
48+
def create_secret(self, conn_utils):
49+
"""Create a secret in AWS Secrets Manager with database credentials."""
50+
region = TestEnvironment.get_current().get_info().get_region()
51+
client = boto3.client('secretsmanager', region_name=region)
52+
env = TestEnvironment.get_current()
53+
54+
secret_name = f"TestSecret-{uuid4()}"
55+
56+
engine = "postgres" if env.get_engine() == "pg" else "mysql"
57+
secret_value = {
58+
"engine": engine,
59+
"dbname": env.get_info().get_database_info().get_default_db_name(),
60+
"host": env.get_info().get_database_info().get_cluster_endpoint(),
61+
"username": conn_utils.user,
62+
"password": conn_utils.password,
63+
"description": "Test secret generated by integration tests."
64+
}
65+
66+
try:
67+
response = client.create_secret(
68+
Name=secret_name,
69+
SecretString=json.dumps(secret_value)
70+
)
71+
secret_arn = response['ARN']
72+
yield secret_name, secret_arn
73+
finally:
74+
try:
75+
client.delete_secret(
76+
SecretId=secret_name,
77+
ForceDeleteWithoutRecovery=True
78+
)
79+
except Exception:
80+
pass
81+
82+
def test_connection(self, test_driver, conn_utils, create_secret, props):
83+
"""Test basic connection using AWS Secrets Manager."""
84+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
85+
secret_name, _ = create_secret
86+
region = TestEnvironment.get_current().get_info().get_region()
87+
88+
props.update({
89+
"secrets_manager_secret_id": secret_name,
90+
"secrets_manager_region": region
91+
})
92+
93+
self.validate_connection(
94+
target_driver_connect,
95+
**conn_utils.get_connect_params(user="IncorrectUser", password="IncorrectPassword"),
96+
**props
97+
)
98+
99+
def test_connect_with_arn(self, test_driver, conn_utils, create_secret, props):
100+
"""Test connection using secret ARN."""
101+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
102+
_, secret_arn = create_secret
103+
104+
props.update({
105+
"secrets_manager_secret_id": secret_arn
106+
})
107+
108+
self.validate_connection(
109+
target_driver_connect,
110+
**conn_utils.get_connect_params(user="IncorrectUser", password="IncorrectPassword"),
111+
**props
112+
)
113+
114+
def test_incorrect_secret_id(self, test_driver, conn_utils, props):
115+
"""Test connection with incorrect secret ID should fail."""
116+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
117+
region = TestEnvironment.get_current().get_info().get_region()
118+
119+
props.update({
120+
"secrets_manager_secret_id": "incorrectSecretId",
121+
"secrets_manager_region": region
122+
})
123+
124+
with pytest.raises(AwsWrapperError):
125+
with AwsWrapperConnection.connect(
126+
target_driver_connect,
127+
**conn_utils.get_connect_params(),
128+
**props
129+
) as conn:
130+
conn.cursor()
131+
132+
def test_missing_secret_id(self, test_driver, conn_utils, props):
133+
"""Test connection with missing secret ID should fail."""
134+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
135+
region = TestEnvironment.get_current().get_info().get_region()
136+
137+
props.update({
138+
"secrets_manager_region": region
139+
})
140+
141+
with pytest.raises(AwsWrapperError):
142+
with AwsWrapperConnection.connect(
143+
target_driver_connect,
144+
**conn_utils.get_connect_params(user="incorrectUser", password="incorrectPassword"),
145+
**props
146+
) as conn:
147+
conn.cursor()
148+
149+
def test_invalid_region(self, test_driver, conn_utils, create_secret, props):
150+
"""Test connection with invalid region should fail."""
151+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
152+
secret_name, _ = create_secret
153+
154+
props.update({
155+
"secrets_manager_secret_id": secret_name,
156+
"secrets_manager_region": "invalidRegion"
157+
})
158+
159+
with pytest.raises(AwsWrapperError):
160+
with AwsWrapperConnection.connect(
161+
target_driver_connect,
162+
**conn_utils.get_connect_params(user="incorrectUser", password="incorrectPassword"),
163+
**props
164+
) as conn:
165+
conn.cursor()
166+
167+
def test_missing_region(self, test_driver, conn_utils, create_secret, props):
168+
"""Test connection with missing region should fail."""
169+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
170+
secret_name, _ = create_secret
171+
172+
props.update({
173+
"secrets_manager_secret_id": secret_name
174+
})
175+
176+
with pytest.raises(AwsWrapperError):
177+
with AwsWrapperConnection.connect(
178+
target_driver_connect,
179+
**conn_utils.get_connect_params(user="incorrectUser", password="incorrectPassword"),
180+
**props
181+
) as conn:
182+
conn.cursor()
183+
184+
def test_incorrect_region(self, test_driver, conn_utils, create_secret, props):
185+
"""Test connection with incorrect region should fail."""
186+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
187+
secret_name, _ = create_secret
188+
189+
props.update({
190+
"secrets_manager_secret_id": secret_name,
191+
"secrets_manager_region": "ca-central-1"
192+
})
193+
194+
with pytest.raises(AwsWrapperError):
195+
with AwsWrapperConnection.connect(
196+
target_driver_connect,
197+
**conn_utils.get_connect_params(user="incorrectUser", password="incorrectPassword"),
198+
**props
199+
) as conn:
200+
conn.cursor()
201+
202+
@enable_on_num_instances(min_instances=2)
203+
@disable_on_features([TestEnvironmentFeatures.RUN_AUTOSCALING_TESTS_ONLY,
204+
TestEnvironmentFeatures.BLUE_GREEN_DEPLOYMENT,
205+
TestEnvironmentFeatures.PERFORMANCE])
206+
@enable_on_features([TestEnvironmentFeatures.FAILOVER_SUPPORTED, TestEnvironmentFeatures.IAM])
207+
def test_failover_with_secrets_manager(
208+
self, test_driver, props, conn_utils, create_secret):
209+
region = TestEnvironment.get_current().get_info().get_region()
210+
aurora_utility = RdsTestUtility(region)
211+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
212+
initial_writer_id = aurora_utility.get_cluster_writer_instance_id()
213+
secret_name, _ = create_secret
214+
215+
props.update({
216+
"plugins": "failover,aws_secrets_manager",
217+
"secrets_manager_secret_id": secret_name,
218+
"secrets_manager_region": region
219+
})
220+
221+
with AwsWrapperConnection.connect(
222+
target_driver_connect, **conn_utils.get_connect_params(user="incorrectUser", password="incorrectPassword"), **props) as aws_conn:
223+
aurora_utility.failover_cluster_and_wait_until_writer_changed()
224+
225+
aurora_utility.assert_first_query_throws(aws_conn, FailoverSuccessError)
226+
227+
current_connection_id = aurora_utility.query_instance_id(aws_conn)
228+
assert aurora_utility.is_db_instance_writer(current_connection_id) is True
229+
assert current_connection_id != initial_writer_id
230+
231+
def validate_connection(self, target_driver_connect: Callable, **connect_params):
232+
with AwsWrapperConnection.connect(target_driver_connect, **connect_params) as conn, \
233+
conn.cursor() as cursor:
234+
cursor.execute("SELECT 1")
235+
records = cursor.fetchall()
236+
assert len(records) == 1

tests/integration/container/test_iam_authentication.py

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,15 @@
3131
import pytest
3232

3333
from aws_advanced_python_wrapper import AwsWrapperConnection
34-
from aws_advanced_python_wrapper.errors import AwsWrapperError
35-
from tests.integration.container.utils.conditions import (disable_on_features,
36-
enable_on_features)
34+
from aws_advanced_python_wrapper.errors import (AwsWrapperError,
35+
FailoverSuccessError)
36+
from tests.integration.container.utils.conditions import (
37+
disable_on_features, enable_on_deployments, enable_on_features,
38+
enable_on_num_instances)
39+
from tests.integration.container.utils.database_engine_deployment import \
40+
DatabaseEngineDeployment
3741
from tests.integration.container.utils.driver_helper import DriverHelper
42+
from tests.integration.container.utils.rds_test_utility import RdsTestUtility
3843
from tests.integration.container.utils.test_environment import TestEnvironment
3944

4045

@@ -125,6 +130,42 @@ def test_iam_valid_connection_properties_no_password(
125130

126131
self.validate_connection(target_driver_connect, **params, **props)
127132

133+
@enable_on_num_instances(min_instances=2)
134+
@enable_on_deployments([DatabaseEngineDeployment.AURORA, DatabaseEngineDeployment.RDS_MULTI_AZ_CLUSTER])
135+
@disable_on_features([TestEnvironmentFeatures.RUN_AUTOSCALING_TESTS_ONLY,
136+
TestEnvironmentFeatures.BLUE_GREEN_DEPLOYMENT,
137+
TestEnvironmentFeatures.PERFORMANCE])
138+
@enable_on_features([TestEnvironmentFeatures.FAILOVER_SUPPORTED, TestEnvironmentFeatures.IAM])
139+
def test_failover_with_iam(
140+
self, test_environment: TestEnvironment, test_driver: TestDriver, props, conn_utils):
141+
target_driver_connect = DriverHelper.get_connect_func(test_driver)
142+
region = TestEnvironment.get_current().get_info().get_region()
143+
aurora_utility = RdsTestUtility(region)
144+
initial_writer_id = aurora_utility.get_cluster_writer_instance_id()
145+
146+
props.update({
147+
"plugins": "failover,iam",
148+
"socket_timeout": 10,
149+
"connect_timeout": 10,
150+
"monitoring-connect_timeout": 5,
151+
"monitoring-socket_timeout": 5,
152+
"topology_refresh_ms": 10,
153+
"autocommit": True
154+
})
155+
156+
with AwsWrapperConnection.connect(
157+
target_driver_connect, **conn_utils.get_connect_params(user=conn_utils.iam_user), **props) as aws_conn:
158+
# crash instance1 and nominate a new writer
159+
aurora_utility.failover_cluster_and_wait_until_writer_changed()
160+
161+
# failure occurs on Cursor invocation
162+
aurora_utility.assert_first_query_throws(aws_conn, FailoverSuccessError)
163+
164+
# assert that we are connected to the new writer after failover happens and we can reuse the cursor
165+
current_connection_id = aurora_utility.query_instance_id(aws_conn)
166+
assert aurora_utility.is_db_instance_writer(current_connection_id) is True
167+
assert current_connection_id != initial_writer_id
168+
128169
def get_ip_address(self, hostname: str):
129170
return gethostbyname(hostname)
130171

0 commit comments

Comments
 (0)