Skip to content

Commit b988846

Browse files
author
Aaron Congo
committedJul 12, 2022
Implement integration test framework (#40)
- Added integration test framework classes and workflows so that integration tests can be run against an Aurora Postgres cluster or a standard Postgres container
1 parent 3ccffc3 commit b988846

19 files changed

+1948
-16
lines changed
 

‎.gitattributes

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* text=auto
2+
gradlew text eol=lf

‎.github/workflows/main.yml

+80-3
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@ concurrency:
1717

1818
jobs:
1919
build-driver:
20-
name: 'Run Tests'
20+
name: 'Run non-container integration tests'
2121
runs-on: ubuntu-latest
2222
steps:
23-
- name: 'Clone Repository'
23+
- name: 'Clone repository'
2424
uses: actions/checkout@v2
2525

2626
- name: 'Set up JDK 8'
@@ -34,7 +34,7 @@ jobs:
3434
- name: 'Generate code coverage report'
3535
run: ./gradlew jacocoTestReport
3636

37-
- name: 'Archive Test Results'
37+
- name: 'Archive test results'
3838
if: always()
3939
uses: actions/upload-artifact@v3
4040
with:
@@ -49,3 +49,80 @@ jobs:
4949
name: 'coverage-report'
5050
path: ./driver-proxy/build/reports/jacoco/test/
5151
retention-days: 3
52+
53+
aurora-postgres-integration-tests:
54+
concurrency: IntegrationTests
55+
name: 'Run Aurora Postgres container integration tests'
56+
runs-on: ubuntu-latest
57+
steps:
58+
- name: 'Clone repository'
59+
uses: actions/checkout@v2
60+
with:
61+
fetch-depth: 50
62+
- name: 'Set up JDK 8'
63+
uses: actions/setup-java@v1
64+
with:
65+
java-version: 8
66+
- name: 'Configure AWS credentials'
67+
uses: aws-actions/configure-aws-credentials@v1
68+
with:
69+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
70+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
71+
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
72+
- name: 'Set up temp AWS credentials'
73+
run: |
74+
creds=($(aws sts get-session-token \
75+
--duration-seconds 3600 \
76+
--query 'Credentials.[AccessKeyId, SecretAccessKey, SessionToken]' \
77+
--output text \
78+
| xargs));
79+
echo "::add-mask::${creds[0]}"
80+
echo "::add-mask::${creds[1]}"
81+
echo "::add-mask::${creds[2]}"
82+
echo "TEMP_AWS_ACCESS_KEY_ID=${creds[0]}" >> $GITHUB_ENV
83+
echo "TEMP_AWS_SECRET_ACCESS_KEY=${creds[1]}" >> $GITHUB_ENV
84+
echo "TEMP_AWS_SESSION_TOKEN=${creds[2]}" >> $GITHUB_ENV
85+
- name: 'Run integration tests'
86+
run: |
87+
./gradlew --no-parallel --no-daemon test-integration-aurora-postgres
88+
env:
89+
AURORA_POSTGRES_CLUSTER_IDENTIFIER: ${{ secrets.AURORA_POSTGRES_CLUSTER_IDENTIFIER }}-${{ github.run_id }}
90+
AURORA_POSTGRES_USERNAME: ${{ secrets.AURORA_POSTGRES_USERNAME }}
91+
AURORA_POSTGRES_PASSWORD: ${{ secrets.AURORA_POSTGRES_PASSWORD }}
92+
AWS_ACCESS_KEY_ID: ${{ env.TEMP_AWS_ACCESS_KEY_ID }}
93+
AWS_SECRET_ACCESS_KEY: ${{ env.TEMP_AWS_SECRET_ACCESS_KEY }}
94+
AWS_SESSION_TOKEN: ${{ env.TEMP_AWS_SESSION_TOKEN }}
95+
- name: 'Archive junit results'
96+
if: always()
97+
uses: actions/upload-artifact@v2
98+
with:
99+
name: 'junit-report'
100+
path: ./driver-proxy/build/reports/tests/
101+
retention-days: 5
102+
103+
standard-postgres-integration-tests:
104+
concurrency: IntegrationTests
105+
name: 'Run Standard Postgres container integration tests'
106+
runs-on: ubuntu-latest
107+
steps:
108+
- name: 'Clone repository'
109+
uses: actions/checkout@v2
110+
with:
111+
fetch-depth: 50
112+
- name: 'Set up JDK 8'
113+
uses: actions/setup-java@v1
114+
with:
115+
java-version: 8
116+
- name: 'Run integration tests'
117+
run: |
118+
./gradlew --no-parallel --no-daemon test-integration-standard-postgres
119+
env:
120+
TEST_USERNAME: ${{ secrets.STANDARD_POSTGRES_USERNAME }}
121+
TEST_PASSWORD: ${{ secrets.STANDARD_POSTGRES_PASSWORD }}
122+
- name: 'Archive junit results'
123+
if: always()
124+
uses: actions/upload-artifact@v2
125+
with:
126+
name: 'junit-report'
127+
path: ./driver-proxy/build/reports/tests/
128+
retention-days: 5

‎driver-proxy/build.gradle.kts

+58
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,38 @@ plugins {
1919
dependencies {
2020
implementation("org.checkerframework:checker-qual:3.22.+")
2121

22+
testImplementation("org.junit.platform:junit-platform-commons:1.8.+")
23+
testImplementation("org.junit.platform:junit-platform-engine:1.8.+")
24+
testImplementation("org.junit.platform:junit-platform-launcher:1.8.+")
25+
testImplementation("org.junit.platform:junit-platform-suite-engine:1.8.+")
2226
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.+")
2327
testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.+")
2428
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
2529

30+
testImplementation("org.apache.commons:commons-dbcp2:2.8.0")
2631
testImplementation("org.postgresql:postgresql:42.+")
2732
testImplementation("mysql:mysql-connector-java:8.0.+")
2833
testImplementation("com.zaxxer:HikariCP:4.+") // version 4.+ is compatible with Java 8
2934
testImplementation("org.springframework.boot:spring-boot-starter-jdbc:2.7.+")
3035
testImplementation("org.mockito:mockito-inline:4.+")
36+
testImplementation("software.amazon.awssdk:rds:2.17.+")
37+
testImplementation("software.amazon.awssdk:ec2:2.17.+")
38+
testImplementation("org.testcontainers:testcontainers:1.17.+")
39+
testImplementation("org.testcontainers:mysql:1.17.+")
40+
testImplementation("org.testcontainers:postgresql:1.17.+")
41+
testImplementation("org.testcontainers:junit-jupiter:1.17.+")
42+
testImplementation("org.testcontainers:toxiproxy:1.17.+")
3143
}
3244

3345
tasks.check {
3446
dependsOn("jacocoTestCoverageVerification")
3547
}
3648

49+
tasks.test {
50+
filter.excludeTestsMatching("integration.host.*")
51+
filter.excludeTestsMatching("integration.container.*")
52+
}
53+
3754
checkstyle {
3855
// Checkstyle versions 7.x, 8.x, and 9.x are supported by JRE version 8 and above.
3956
toolVersion = "9.3"
@@ -122,3 +139,44 @@ tasks.getByName<Test>("test") {
122139

123140
systemProperty("java.util.logging.config.file", "${project.buildDir}/resources/test/logging-test.properties")
124141
}
142+
143+
// Run Aurora Postgres integrations tests in container
144+
tasks.register<Test>("test-integration-aurora-postgres") {
145+
group = "verification"
146+
filter.includeTestsMatching("integration.host.AuroraPostgresContainerTest.runTestInContainer")
147+
}
148+
149+
tasks.register<Test>("test-performance-aurora-postgres") {
150+
group = "verification"
151+
filter.includeTestsMatching("integration.host.AuroraPostgresContainerTest.runPerformanceTestInContainer")
152+
}
153+
154+
// Run standard Postgres tests in container
155+
tasks.register<Test>("test-integration-standard-postgres") {
156+
group = "verification"
157+
filter.includeTestsMatching("integration.host.StandardPostgresContainerTest.runTestInContainer")
158+
}
159+
160+
// Run Aurora Postgres integration tests in container with debugger
161+
tasks.register<Test>("debug-integration-aurora-postgres") {
162+
group = "verification"
163+
filter.includeTestsMatching("integration.host.AuroraPostgresContainerTest.debugTestInContainer")
164+
}
165+
166+
tasks.register<Test>("debug-performance-aurora-postgres") {
167+
group = "verification"
168+
filter.includeTestsMatching("testsuite.integration.host.AuroraPostgresContainerTest.debugPerformanceTestInContainer")
169+
}
170+
171+
// Run standard Postgres integration tests in container with debugger
172+
tasks.register<Test>("debug-integration-standard-postgres") {
173+
group = "verification"
174+
filter.includeTestsMatching("integration.host.StandardPostgresContainerTest.debugTestInContainer")
175+
}
176+
177+
tasks.withType<Test> {
178+
this.testLogging {
179+
this.showStandardStreams = true
180+
}
181+
useJUnitPlatform()
182+
}

‎driver-proxy/src/main/java/software/aws/rds/jdbc/proxydriver/ConnectionPluginManager.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public void unlock() {
9898

9999
/**
100100
* Initialize a chain of {@link ConnectionPlugin} using their corresponding {@link ConnectionPluginFactory}. If
101-
* {@code PropertyKey.connectionPluginFactories} is provided by the user, initialize the chain with the given
101+
* {@code PropertyDefinition.PLUGINS} is provided by the user, initialize the chain with the given
102102
* connection plugins in the order they are specified.
103103
*
104104
* <p>The {@link DefaultConnectionPlugin} will always be initialized and attached as the last

‎driver-proxy/src/main/java/software/aws/rds/jdbc/proxydriver/PropertyDefinition.java

+27-8
Original file line numberDiff line numberDiff line change
@@ -16,28 +16,47 @@
1616

1717
public class PropertyDefinition {
1818

19-
public static final ProxyDriverProperty PLUGINS =
19+
public static final ProxyDriverProperty CLUSTER_INSTANCE_HOST_PATTERN =
2020
new ProxyDriverProperty(
21-
"proxyDriverPlugins", null, "Coma separated list of connection plugin codes");
21+
"clusterInstanceHostPattern",
22+
null,
23+
"The cluster instance DNS pattern that will be used to build a complete instance endpoint. "
24+
+ "A \"?\" character in this pattern should be used as a placeholder for cluster instance names. "
25+
+ "This pattern is required to be specified for IP address or custom domain connections to AWS RDS "
26+
+ "clusters. Otherwise, if unspecified, the pattern will be automatically created for AWS RDS clusters.");
2227

23-
public static final ProxyDriverProperty PROFILE_NAME =
28+
public static final ProxyDriverProperty ENABLE_CLUSTER_AWARE_FAILOVER =
2429
new ProxyDriverProperty(
25-
"proxyDriverProfileName", null, "Driver configuration profile name");
30+
"enableClusterAwareFailover", "true",
31+
"Enable/disable cluster-aware failover logic");
32+
33+
public static final ProxyDriverProperty LOG_UNCLOSED_CONNECTIONS =
34+
new ProxyDriverProperty(
35+
"proxyDriverLogUnclosedConnections", "false",
36+
"Allows the driver to track a point in the code where connection has been opened and never closed after");
2637

2738
public static final ProxyDriverProperty LOGGER_LEVEL =
2839
new ProxyDriverProperty(
2940
"proxyDriverLoggerLevel",
3041
null,
3142
"Logger level of the driver",
3243
false,
33-
new String[]{
44+
new String[] {
3445
"OFF", "SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", "FINEST", "ALL"
3546
});
3647

37-
public static final ProxyDriverProperty LOG_UNCLOSED_CONNECTIONS =
48+
public static final ProxyDriverProperty PLUGINS =
3849
new ProxyDriverProperty(
39-
"proxyDriverLogUnclosedConnections", "false",
40-
"Allows the driver to track a point in the code where connection has been opened and never closed after");
50+
"proxyDriverPlugins", null, "Comma separated list of connection plugin codes");
51+
52+
public static final ProxyDriverProperty PROFILE_NAME =
53+
new ProxyDriverProperty(
54+
"proxyDriverProfileName", null, "Driver configuration profile name");
55+
56+
public static final ProxyDriverProperty USE_AWS_IAM =
57+
new ProxyDriverProperty(
58+
"useAwsIam", "false", "Set to true to use AWS IAM database authentication");
59+
4160

4261
private static final Map<String, ProxyDriverProperty> PROPS_BY_NAME =
4362
new HashMap<String, ProxyDriverProperty>();
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
plugins {
8+
java
9+
}
10+
11+
repositories {
12+
mavenCentral()
13+
}
14+
15+
dependencies {
16+
testImplementation("org.junit.platform:junit-platform-commons:1.8.2")
17+
testImplementation("org.junit.platform:junit-platform-engine:1.8.2")
18+
testImplementation("org.junit.platform:junit-platform-launcher:1.8.2")
19+
testImplementation("org.junit.platform:junit-platform-suite-engine:1.8.2")
20+
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2")
21+
testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2")
22+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
23+
24+
testImplementation("org.apache.commons:commons-dbcp2:2.8.0")
25+
testImplementation("org.postgresql:postgresql:42.+")
26+
testImplementation("mysql:mysql-connector-java:8.0.+")
27+
testImplementation("com.zaxxer:HikariCP:4.+") // version 4.+ is compatible with Java 8
28+
testImplementation("org.springframework.boot:spring-boot-starter-jdbc:2.7.+")
29+
testImplementation("org.mockito:mockito-inline:4.+")
30+
testImplementation("software.amazon.awssdk:rds:2.17.165")
31+
testImplementation("software.amazon.awssdk:ec2:2.17.165")
32+
testImplementation("org.testcontainers:testcontainers:1.17.+")
33+
testImplementation("org.testcontainers:mysql:1.17.+")
34+
testImplementation("org.testcontainers:postgresql:1.17.+")
35+
testImplementation("org.testcontainers:junit-jupiter:1.17.+")
36+
testImplementation("org.testcontainers:toxiproxy:1.17.+")
37+
38+
testImplementation(fileTree("./libs") { include("*.jar") })
39+
testImplementation(files("./test"))
40+
}
41+
42+
// Integration tests are run in a specific order.
43+
// To add more tests, see integration.container.aurora.postgres.AuroraPostgresTestSuite.java
44+
tasks.register<Test>("in-container-aurora-postgres") {
45+
filter.includeTestsMatching("integration.container.aurora.postgres.AuroraPostgresTestSuite")
46+
}
47+
48+
tasks.register<Test>("in-container-aurora-postgres-performance") {
49+
filter.includeTestsMatching("integration.container.aurora.postgres.AuroraPostgresPerformanceTest")
50+
}
51+
52+
// Integration tests are run in a specific order.
53+
// To add more tests, see integration.container.standard.postgres.StandardPostgresTestSuite.java
54+
tasks.register<Test>("in-container-standard-postgres") {
55+
filter.includeTestsMatching("integration.container.standard.postgres.StandardPostgresTestSuite")
56+
}
57+
58+
tasks.withType<Test> {
59+
outputs.upToDateWhen { false }
60+
useJUnitPlatform()
61+
}

‎driver-proxy/src/test/java/integration/container/aurora/postgres/AuroraPostgresBaseTest.java

+401
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.container.aurora.postgres;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertFalse;
11+
12+
import java.io.IOException;
13+
import java.sql.Connection;
14+
import java.sql.ResultSet;
15+
import java.sql.SQLException;
16+
import java.sql.Statement;
17+
import org.junit.jupiter.api.Test;
18+
19+
public class AuroraPostgresIntegrationTest extends AuroraPostgresBaseTest {
20+
21+
@Test
22+
public void test_connect() throws SQLException, IOException {
23+
try (final Connection conn = connectToInstance(POSTGRES_INSTANCE_1_URL + PROXIED_DOMAIN_NAME_SUFFIX,
24+
POSTGRES_PROXY_PORT)) {
25+
Statement stmt = conn.createStatement();
26+
ResultSet rs = stmt.executeQuery("SELECT 1");
27+
rs.next();
28+
assertEquals(1, rs.getInt(1));
29+
30+
containerHelper.disableConnectivity(proxyInstance_1);
31+
assertFalse(conn.isValid(5));
32+
containerHelper.enableConnectivity(proxyInstance_1);
33+
}
34+
}
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.container.aurora.postgres;
8+
9+
import org.junit.platform.suite.api.SelectClasses;
10+
import org.junit.platform.suite.api.Suite;
11+
12+
// Tests will run in order of top to bottom.
13+
// To add additional tests, append it inside SelectClasses, comma-separated
14+
@Suite
15+
@SelectClasses({
16+
AuroraPostgresIntegrationTest.class
17+
})
18+
public class AuroraPostgresTestSuite {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.container.standard.postgres;
8+
9+
import eu.rekawek.toxiproxy.Proxy;
10+
import eu.rekawek.toxiproxy.ToxiproxyClient;
11+
import integration.util.ContainerHelper;
12+
import java.io.IOException;
13+
import java.sql.Connection;
14+
import java.sql.DriverManager;
15+
import java.sql.SQLException;
16+
import java.util.HashMap;
17+
import java.util.Map;
18+
import java.util.Properties;
19+
import org.junit.jupiter.api.BeforeAll;
20+
import org.junit.jupiter.api.BeforeEach;
21+
import org.postgresql.PGProperty;
22+
23+
public class StandardPostgresBaseTest {
24+
protected static final String DB_CONN_STR_PREFIX = "aws-proxy-jdbc:postgresql://";
25+
protected static final String TEST_HOST = System.getenv("TEST_HOST");
26+
protected static final String TEST_PORT = System.getenv("TEST_PORT");
27+
protected static final String TEST_DB = System.getenv("TEST_DB");
28+
protected static final String TEST_USERNAME = System.getenv("TEST_USERNAME");
29+
protected static final String TEST_PASSWORD = System.getenv("TEST_PASSWORD");
30+
31+
protected static final String TOXIPROXY_HOST = System.getenv("TOXIPROXY_HOST");
32+
protected static ToxiproxyClient toxiproxyClient;
33+
protected static final int TOXIPROXY_CONTROL_PORT = 8474;
34+
35+
protected static final String PROXIED_DOMAIN_NAME_SUFFIX = System.getenv("PROXIED_DOMAIN_NAME_SUFFIX");
36+
protected static final String PROXY_PORT = System.getenv("PROXY_PORT");
37+
protected static Proxy proxy;
38+
protected static final Map<String, Proxy> proxyMap = new HashMap<>();
39+
40+
protected final ContainerHelper containerHelper = new ContainerHelper();
41+
42+
@BeforeAll
43+
public static void setUp() throws SQLException, IOException {
44+
toxiproxyClient = new ToxiproxyClient(TOXIPROXY_HOST, TOXIPROXY_CONTROL_PORT);
45+
proxy = getProxy(toxiproxyClient, TEST_HOST, Integer.parseInt(TEST_PORT));
46+
proxyMap.put(TEST_HOST, proxy);
47+
48+
if (!org.postgresql.Driver.isRegistered()) {
49+
org.postgresql.Driver.register();
50+
}
51+
52+
if (!software.aws.rds.jdbc.proxydriver.Driver.isRegistered()) {
53+
software.aws.rds.jdbc.proxydriver.Driver.register();
54+
}
55+
}
56+
57+
@BeforeEach
58+
public void setUpEach() {
59+
proxyMap.forEach((instance, proxy) -> containerHelper.enableConnectivity(proxy));
60+
}
61+
62+
protected static Proxy getProxy(ToxiproxyClient proxyClient, String host, int port) throws IOException {
63+
final String upstream = host + ":" + port;
64+
return proxyClient.getProxy(upstream);
65+
}
66+
67+
protected Connection connect() throws SQLException {
68+
String url = DB_CONN_STR_PREFIX + TEST_HOST + ":" + TEST_PORT + "/" + TEST_DB;
69+
return DriverManager.getConnection(url, initDefaultProps());
70+
}
71+
72+
protected Connection connectToProxy() throws SQLException {
73+
String url = DB_CONN_STR_PREFIX + TEST_HOST + PROXIED_DOMAIN_NAME_SUFFIX + ":" + PROXY_PORT + "/" + TEST_DB;
74+
return DriverManager.getConnection(url, initDefaultProps());
75+
}
76+
77+
protected Properties initDefaultProps() {
78+
final Properties props = initDefaultPropsNoTimeouts();
79+
props.setProperty(PGProperty.CONNECT_TIMEOUT.getName(), "3");
80+
props.setProperty(PGProperty.SOCKET_TIMEOUT.getName(), "3");
81+
82+
return props;
83+
}
84+
85+
protected Properties initDefaultPropsNoTimeouts() {
86+
final Properties props = new Properties();
87+
props.setProperty(PGProperty.USER.getName(), TEST_USERNAME);
88+
props.setProperty(PGProperty.PASSWORD.getName(), TEST_PASSWORD);
89+
props.setProperty(PGProperty.TCP_KEEP_ALIVE.getName(), Boolean.FALSE.toString());
90+
91+
return props;
92+
}
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.container.standard.postgres;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertFalse;
11+
import static org.junit.jupiter.api.Assertions.assertTrue;
12+
13+
import java.io.IOException;
14+
import java.sql.Connection;
15+
import java.sql.ResultSet;
16+
import java.sql.SQLException;
17+
import java.sql.Statement;
18+
import org.junit.jupiter.api.Test;
19+
20+
public class StandardPostgresIntegrationTest extends StandardPostgresBaseTest {
21+
22+
@Test
23+
public void test_connect() throws SQLException, IOException {
24+
try (Connection conn = connect()) {
25+
Statement stmt = conn.createStatement();
26+
stmt.executeQuery("SELECT 1");
27+
ResultSet rs = stmt.getResultSet();
28+
rs.next();
29+
assertEquals(1, rs.getInt(1));
30+
}
31+
32+
try (Connection conn = connectToProxy()) {
33+
assertTrue(conn.isValid(5));
34+
containerHelper.disableConnectivity(proxy);
35+
assertFalse(conn.isValid(5));
36+
containerHelper.enableConnectivity(proxy);
37+
}
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.container.standard.postgres;
8+
9+
import org.junit.platform.suite.api.SelectClasses;
10+
import org.junit.platform.suite.api.Suite;
11+
12+
// Tests will run in order of top to bottom.
13+
// To add additional tests, append it inside SelectClasses, comma-separated
14+
@Suite
15+
@SelectClasses({
16+
StandardPostgresIntegrationTest.class
17+
})
18+
public class StandardPostgresTestSuite {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.host;
8+
9+
import integration.util.AuroraTestUtility;
10+
import integration.util.ContainerHelper;
11+
import java.io.IOException;
12+
import java.net.UnknownHostException;
13+
import java.sql.SQLException;
14+
import java.util.ArrayList;
15+
import java.util.List;
16+
import org.junit.jupiter.api.AfterAll;
17+
import org.junit.jupiter.api.Assertions;
18+
import org.junit.jupiter.api.BeforeAll;
19+
import org.junit.jupiter.api.Test;
20+
import org.testcontainers.containers.GenericContainer;
21+
import org.testcontainers.containers.Network;
22+
import org.testcontainers.containers.ToxiproxyContainer;
23+
import software.aws.rds.jdbc.proxydriver.util.StringUtils;
24+
25+
/**
26+
* Integration tests against RDS Aurora cluster. Uses {@link AuroraTestUtility} which requires AWS
27+
* Credentials to create/destroy clusters & set EC2 Whitelist.
28+
*
29+
* <p>The following environment variables are REQUIRED for AWS IAM tests - AWS_ACCESS_KEY_ID, AWS
30+
* access key - AWS_SECRET_ACCESS_KEY, AWS secret access key - AWS_SESSION_TOKEN, AWS Session token
31+
*
32+
* <p>The following environment variables are optional but suggested differentiating between runners
33+
* Provided values are just examples. Assuming cluster endpoint is
34+
* "database-cluster-name.XYZ.us-east-2.rds.amazonaws.com"
35+
*
36+
* <p>TEST_DB_CLUSTER_IDENTIFIER=database-cluster-name TEST_USERNAME=user-name
37+
* TEST_PASSWORD=user-secret-password
38+
*/
39+
public class AuroraPostgresContainerTest {
40+
41+
private static final int AURORA_POSTGRES_PORT = 5432;
42+
private static final String AURORA_POSTGRES_TEST_HOST_NAME = "test-container";
43+
44+
private static final String AURORA_POSTGRES_USERNAME =
45+
!StringUtils.isNullOrEmpty(System.getenv("AURORA_POSTGRES_USERNAME"))
46+
? System.getenv("AURORA_POSTGRES_USERNAME")
47+
: "my_test_username";
48+
private static final String AURORA_POSTGRES_PASSWORD =
49+
!StringUtils.isNullOrEmpty(System.getenv("AURORA_POSTGRES_PASSWORD"))
50+
? System.getenv("AURORA_POSTGRES_PASSWORD")
51+
: "my_test_password";
52+
protected static final String AURORA_POSTGRES_DB =
53+
!StringUtils.isNullOrEmpty(System.getenv("AURORA_POSTGRES_DB"))
54+
? System.getenv("AURORA_POSTGRES_DB")
55+
: "test";
56+
57+
private static final String AWS_ACCESS_KEY_ID = System.getenv("AWS_ACCESS_KEY_ID");
58+
private static final String AWS_SECRET_ACCESS_KEY = System.getenv("AWS_SECRET_ACCESS_KEY");
59+
private static final String AWS_SESSION_TOKEN = System.getenv("AWS_SESSION_TOKEN");
60+
61+
private static final String DB_CONN_STR_PREFIX = "aws-proxy-jdbc:postgresql://";
62+
private static String dbConnStrSuffix = "";
63+
private static final String DB_CONN_PROP = "?enabledTLSProtocols=TLSv1.2";
64+
65+
private static final String AURORA_POSTGRES_DB_REGION =
66+
!StringUtils.isNullOrEmpty(System.getenv("AURORA_POSTGRES_DB_REGION"))
67+
? System.getenv("AURORA_POSTGRES_DB_REGION")
68+
: "us-east-1";
69+
private static final String AURORA_POSTGRES_CLUSTER_IDENTIFIER =
70+
!StringUtils.isNullOrEmpty(System.getenv("AURORA_POSTGRES_CLUSTER_IDENTIFIER"))
71+
? System.getenv("AURORA_POSTGRES_CLUSTER_IDENTIFIER")
72+
: "test-identifier";
73+
private static final String PROXIED_DOMAIN_NAME_SUFFIX = ".proxied";
74+
private static List<ToxiproxyContainer> proxyContainers = new ArrayList<>();
75+
private static List<String> postgresInstances = new ArrayList<>();
76+
77+
private static int postgresProxyPort;
78+
private static GenericContainer<?> integrationTestContainer;
79+
private static String dbHostCluster = "";
80+
private static String dbHostClusterRo = "";
81+
private static String runnerIP = null;
82+
83+
private static Network network;
84+
85+
private static final ContainerHelper containerHelper = new ContainerHelper();
86+
private static final AuroraTestUtility auroraUtil = new AuroraTestUtility(AURORA_POSTGRES_DB_REGION);
87+
88+
@BeforeAll
89+
static void setUp() throws SQLException, InterruptedException, UnknownHostException {
90+
Assertions.assertNotNull(AWS_ACCESS_KEY_ID);
91+
Assertions.assertNotNull(AWS_SECRET_ACCESS_KEY);
92+
Assertions.assertNotNull(AWS_SESSION_TOKEN);
93+
94+
// Comment out below to not create a new cluster & instances
95+
// Note: You will need to set it to the proper DB Conn Suffix
96+
// i.e. For "database-cluster-name.XYZ.us-east-2.rds.amazonaws.com"
97+
// dbConnStrSuffix = "XYZ.us-east-2.rds.amazonaws.com"
98+
dbConnStrSuffix =
99+
auroraUtil.createCluster(AURORA_POSTGRES_USERNAME, AURORA_POSTGRES_PASSWORD, AURORA_POSTGRES_DB,
100+
AURORA_POSTGRES_CLUSTER_IDENTIFIER);
101+
102+
// Comment out getting public IP to not add & remove from EC2 whitelist
103+
runnerIP = auroraUtil.getPublicIPAddress();
104+
auroraUtil.ec2AuthorizeIP(runnerIP);
105+
106+
dbHostCluster = AURORA_POSTGRES_CLUSTER_IDENTIFIER + ".cluster-" + dbConnStrSuffix;
107+
dbHostClusterRo = AURORA_POSTGRES_CLUSTER_IDENTIFIER + ".cluster-ro-" + dbConnStrSuffix;
108+
109+
if (!org.postgresql.Driver.isRegistered()) {
110+
org.postgresql.Driver.register();
111+
}
112+
113+
if (!software.aws.rds.jdbc.proxydriver.Driver.isRegistered()) {
114+
software.aws.rds.jdbc.proxydriver.Driver.register();
115+
}
116+
117+
network = Network.newNetwork();
118+
postgresInstances =
119+
containerHelper.getAuroraInstanceEndpoints(
120+
DB_CONN_STR_PREFIX + dbHostCluster + "/" + AURORA_POSTGRES_DB + DB_CONN_PROP,
121+
AURORA_POSTGRES_USERNAME,
122+
AURORA_POSTGRES_PASSWORD,
123+
dbConnStrSuffix);
124+
proxyContainers =
125+
containerHelper.createProxyContainers(
126+
network, postgresInstances, PROXIED_DOMAIN_NAME_SUFFIX);
127+
for (ToxiproxyContainer container : proxyContainers) {
128+
container.start();
129+
}
130+
postgresProxyPort =
131+
containerHelper.createInstanceProxies(
132+
postgresInstances, proxyContainers, AURORA_POSTGRES_PORT);
133+
134+
proxyContainers.add(
135+
containerHelper.createAndStartProxyContainer(
136+
network,
137+
"toxiproxy-instance-cluster",
138+
dbHostCluster + PROXIED_DOMAIN_NAME_SUFFIX,
139+
dbHostCluster,
140+
AURORA_POSTGRES_PORT,
141+
postgresProxyPort));
142+
143+
proxyContainers.add(
144+
containerHelper.createAndStartProxyContainer(
145+
network,
146+
"toxiproxy-ro-instance-cluster",
147+
dbHostClusterRo + PROXIED_DOMAIN_NAME_SUFFIX,
148+
dbHostClusterRo,
149+
AURORA_POSTGRES_PORT,
150+
postgresProxyPort));
151+
152+
integrationTestContainer = initializeTestContainer(network, postgresInstances);
153+
}
154+
155+
@AfterAll
156+
static void tearDown() {
157+
// Comment below out if you don't want to delete cluster after tests finishes
158+
if (StringUtils.isNullOrEmpty(AURORA_POSTGRES_CLUSTER_IDENTIFIER)) {
159+
auroraUtil.deleteCluster();
160+
} else {
161+
auroraUtil.deleteCluster(AURORA_POSTGRES_CLUSTER_IDENTIFIER);
162+
}
163+
164+
auroraUtil.ec2DeauthorizesIP(runnerIP);
165+
for (ToxiproxyContainer proxy : proxyContainers) {
166+
proxy.stop();
167+
}
168+
integrationTestContainer.stop();
169+
}
170+
171+
@Test
172+
public void runTestInContainer()
173+
throws UnsupportedOperationException, IOException, InterruptedException {
174+
175+
containerHelper.runTest(integrationTestContainer, "in-container-aurora-postgres");
176+
}
177+
178+
@Test
179+
public void runPerformanceTestInContainer()
180+
throws UnsupportedOperationException, IOException, InterruptedException {
181+
182+
containerHelper.runTest(integrationTestContainer, "in-container-aurora-postgres-performance");
183+
}
184+
185+
@Test
186+
public void debugTestInContainer()
187+
throws UnsupportedOperationException, IOException, InterruptedException {
188+
189+
containerHelper.debugTest(integrationTestContainer, "in-container-aurora-postgres");
190+
}
191+
192+
@Test
193+
public void debugPerformanceTestInContainer()
194+
throws UnsupportedOperationException, IOException, InterruptedException {
195+
196+
containerHelper.debugTest(integrationTestContainer, "in-container-aurora-postgres-performance");
197+
}
198+
199+
protected static GenericContainer<?> initializeTestContainer(
200+
final Network network, List<String> postgresInstances) {
201+
202+
final GenericContainer<?> container =
203+
containerHelper
204+
.createTestContainer("aws/rds-test-container")
205+
.withNetworkAliases(AURORA_POSTGRES_TEST_HOST_NAME)
206+
.withNetwork(network)
207+
.withEnv("TEST_USERNAME", AURORA_POSTGRES_USERNAME)
208+
.withEnv("TEST_PASSWORD", AURORA_POSTGRES_PASSWORD)
209+
.withEnv("TEST_DB", AURORA_POSTGRES_DB)
210+
.withEnv("DB_REGION", AURORA_POSTGRES_DB_REGION)
211+
.withEnv("DB_CLUSTER_CONN", dbHostCluster)
212+
.withEnv("DB_RO_CLUSTER_CONN", dbHostClusterRo)
213+
.withEnv("TOXIPROXY_CLUSTER_NETWORK_ALIAS", "toxiproxy-instance-cluster")
214+
.withEnv("TOXIPROXY_RO_CLUSTER_NETWORK_ALIAS", "toxiproxy-ro-instance-cluster")
215+
.withEnv(
216+
"PROXIED_CLUSTER_TEMPLATE", "?." + dbConnStrSuffix + PROXIED_DOMAIN_NAME_SUFFIX)
217+
.withEnv("DB_CONN_STR_SUFFIX", "." + dbConnStrSuffix)
218+
.withEnv("AWS_ACCESS_KEY_ID", AWS_ACCESS_KEY_ID)
219+
.withEnv("AWS_SECRET_ACCESS_KEY", AWS_SECRET_ACCESS_KEY)
220+
.withEnv("AWS_SESSION_TOKEN", AWS_SESSION_TOKEN);
221+
222+
// Add postgres instances & proxies to container env
223+
for (int i = 0; i < postgresInstances.size(); i++) {
224+
// Add instance
225+
container.addEnv("POSTGRES_INSTANCE_" + (i + 1) + "_URL", postgresInstances.get(i));
226+
227+
// Add proxies
228+
container.addEnv(
229+
"TOXIPROXY_INSTANCE_" + (i + 1) + "_NETWORK_ALIAS", "toxiproxy-instance-" + (i + 1));
230+
}
231+
container.addEnv("POSTGRES_PORT", Integer.toString(AURORA_POSTGRES_PORT));
232+
container.addEnv("PROXIED_DOMAIN_NAME_SUFFIX", PROXIED_DOMAIN_NAME_SUFFIX);
233+
container.addEnv("POSTGRES_PROXY_PORT", Integer.toString(postgresProxyPort));
234+
235+
System.out.println("Toxiproxy Instances port: " + postgresProxyPort);
236+
System.out.println("Instances Proxied: " + postgresInstances.size());
237+
238+
container.start();
239+
240+
return container;
241+
}
242+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.host;
8+
9+
import com.mysql.cj.util.StringUtils;
10+
import integration.util.ContainerHelper;
11+
import java.io.IOException;
12+
import java.sql.SQLException;
13+
import org.junit.jupiter.api.AfterAll;
14+
import org.junit.jupiter.api.BeforeAll;
15+
import org.junit.jupiter.api.Test;
16+
import org.testcontainers.containers.GenericContainer;
17+
import org.testcontainers.containers.Network;
18+
import org.testcontainers.containers.PostgreSQLContainer;
19+
import org.testcontainers.containers.ToxiproxyContainer;
20+
21+
public class StandardPostgresContainerTest {
22+
23+
private static final String STANDARD_POSTGRES_TEST_HOST_NAME = "test-container";
24+
private static final String STANDARD_POSTGRES_CONTAINER_NAME = "standard-postgres-container";
25+
private static final String PROXIED_DOMAIN_NAME_SUFFIX = ".proxied";
26+
private static final int STANDARD_POSTGRES_PORT = 5432;
27+
28+
private static final String STANDARD_POSTGRES_DB =
29+
!StringUtils.isNullOrEmpty(System.getenv("STANDARD_POSTGRES_DB"))
30+
? System.getenv("STANDARD_POSTGRES_DB") : "test";
31+
private static final String STANDARD_POSTGRES_USERNAME =
32+
!StringUtils.isNullOrEmpty(System.getenv("STANDARD_POSTGRES_USERNAME"))
33+
? System.getenv("STANDARD_POSTGRES_USERNAME") : "test";
34+
private static final String STANDARD_POSTGRES_PASSWORD =
35+
!StringUtils.isNullOrEmpty(System.getenv("STANDARD_POSTGRES_PASSWORD"))
36+
? System.getenv("STANDARD_POSTGRES_PASSWORD") : "test";
37+
38+
private static PostgreSQLContainer<?> postgresContainer;
39+
private static GenericContainer<?> integrationTestContainer;
40+
private static ToxiproxyContainer proxyContainer;
41+
private static int postgresProxyPort;
42+
private static Network network;
43+
private static final ContainerHelper containerHelper = new ContainerHelper();
44+
45+
@BeforeAll
46+
static void setUp() throws SQLException {
47+
if (!org.postgresql.Driver.isRegistered()) {
48+
org.postgresql.Driver.register();
49+
}
50+
51+
if (!software.aws.rds.jdbc.proxydriver.Driver.isRegistered()) {
52+
software.aws.rds.jdbc.proxydriver.Driver.register();
53+
}
54+
55+
network = Network.newNetwork();
56+
57+
postgresContainer = containerHelper.createPostgresContainer(network, STANDARD_POSTGRES_CONTAINER_NAME,
58+
STANDARD_POSTGRES_DB, STANDARD_POSTGRES_USERNAME, STANDARD_POSTGRES_PASSWORD);
59+
postgresContainer.start();
60+
61+
proxyContainer =
62+
containerHelper.createProxyContainer(network, STANDARD_POSTGRES_CONTAINER_NAME, PROXIED_DOMAIN_NAME_SUFFIX);
63+
proxyContainer.start();
64+
postgresProxyPort = containerHelper.createInstanceProxy(STANDARD_POSTGRES_CONTAINER_NAME, proxyContainer,
65+
STANDARD_POSTGRES_PORT);
66+
67+
integrationTestContainer = createTestContainer();
68+
integrationTestContainer.start();
69+
}
70+
71+
@AfterAll
72+
static void tearDown() {
73+
proxyContainer.stop();
74+
postgresContainer.stop();
75+
integrationTestContainer.stop();
76+
}
77+
78+
@Test
79+
public void runTestInContainer()
80+
throws UnsupportedOperationException, IOException, InterruptedException {
81+
82+
containerHelper.runTest(integrationTestContainer, "in-container-standard-postgres");
83+
}
84+
85+
@Test
86+
public void debugTestInContainer()
87+
throws UnsupportedOperationException, IOException, InterruptedException {
88+
89+
containerHelper.debugTest(integrationTestContainer, "in-container-standard-postgres");
90+
}
91+
92+
protected static GenericContainer<?> createTestContainer() {
93+
return containerHelper.createTestContainer("aws/rds-test-container")
94+
.withNetworkAliases(STANDARD_POSTGRES_TEST_HOST_NAME)
95+
.withNetwork(network)
96+
.withEnv("TEST_HOST", STANDARD_POSTGRES_CONTAINER_NAME)
97+
.withEnv("TEST_PORT", String.valueOf(STANDARD_POSTGRES_PORT))
98+
.withEnv("TEST_DB", STANDARD_POSTGRES_DB)
99+
.withEnv("TEST_USERNAME", STANDARD_POSTGRES_USERNAME)
100+
.withEnv("TEST_PASSWORD", STANDARD_POSTGRES_PASSWORD)
101+
.withEnv("PROXY_PORT", Integer.toString(postgresProxyPort))
102+
.withEnv("PROXIED_DOMAIN_NAME_SUFFIX", PROXIED_DOMAIN_NAME_SUFFIX)
103+
.withEnv("TOXIPROXY_HOST", "toxiproxy-instance");
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.util;
8+
9+
import java.io.BufferedReader;
10+
import java.io.InputStreamReader;
11+
import java.net.URL;
12+
import java.net.UnknownHostException;
13+
import java.time.Duration;
14+
import java.util.Optional;
15+
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
16+
import software.amazon.awssdk.core.waiters.WaiterResponse;
17+
import software.amazon.awssdk.regions.Region;
18+
import software.amazon.awssdk.services.ec2.Ec2Client;
19+
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
20+
import software.amazon.awssdk.services.rds.RdsClient;
21+
import software.amazon.awssdk.services.rds.model.CreateDbClusterRequest;
22+
import software.amazon.awssdk.services.rds.model.CreateDbInstanceRequest;
23+
import software.amazon.awssdk.services.rds.model.DeleteDbInstanceRequest;
24+
import software.amazon.awssdk.services.rds.model.DescribeDbInstancesResponse;
25+
import software.amazon.awssdk.services.rds.model.Filter;
26+
import software.amazon.awssdk.services.rds.model.Tag;
27+
import software.amazon.awssdk.services.rds.waiters.RdsWaiter;
28+
import software.aws.rds.jdbc.proxydriver.util.StringUtils;
29+
30+
/**
31+
* Creates and destroys AWS RDS Clusters and Instances.
32+
* To use this functionality the following environment variables must be defined:
33+
* - AWS_ACCESS_KEY_ID
34+
* - AWS_SECRET_ACCESS_KEY
35+
*/
36+
public class AuroraTestUtility {
37+
// Default values
38+
private String dbUsername = "my_test_username";
39+
private String dbPassword = "my_test_password";
40+
private String dbName = "test";
41+
private String dbIdentifier = "test-identifier";
42+
private String dbEngine = "aurora-postgresql";
43+
private String dbInstanceClass = "db.r5.large";
44+
private final Region dbRegion;
45+
private final String dbSecGroup = "default";
46+
private int numOfInstances = 5;
47+
48+
private final RdsClient rdsClient;
49+
private final Ec2Client ec2Client;
50+
51+
private static final String DUPLICATE_IP_ERROR_CODE = "InvalidPermission.Duplicate";
52+
53+
/**
54+
* Initializes an AmazonRDS & AmazonEC2 client. RDS client used to create/destroy clusters &
55+
* instances. EC2 client used to add/remove IP from security group.
56+
*/
57+
public AuroraTestUtility() {
58+
this(Region.US_EAST_1, DefaultCredentialsProvider.create());
59+
}
60+
61+
/**
62+
* Initializes an AmazonRDS & AmazonEC2 client.
63+
*
64+
* @param region define AWS Regions, refer to
65+
* https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
66+
*/
67+
public AuroraTestUtility(Region region) {
68+
this(region, DefaultCredentialsProvider.create());
69+
}
70+
71+
/**
72+
* Initializes an AmazonRDS & AmazonEC2 client.
73+
*
74+
* @param region define AWS Regions, refer to
75+
* https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
76+
*/
77+
public AuroraTestUtility(String region) {
78+
this(getRegionInternal(region), DefaultCredentialsProvider.create());
79+
}
80+
81+
/**
82+
* Initializes an AmazonRDS & AmazonEC2 client.
83+
*
84+
* @param region define AWS Regions, refer to
85+
* https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.RegionsAndAvailabilityZones.html
86+
* @param credentials Specific AWS credential provider
87+
*/
88+
public AuroraTestUtility(Region region, DefaultCredentialsProvider credentials) {
89+
dbRegion = region;
90+
91+
rdsClient = RdsClient.builder().region(dbRegion).credentialsProvider(credentials).build();
92+
93+
ec2Client = Ec2Client.builder().region(dbRegion).credentialsProvider(credentials).build();
94+
}
95+
96+
public Region getRegion(String rdsRegion) {
97+
return getRegionInternal(rdsRegion);
98+
}
99+
100+
protected static Region getRegionInternal(String rdsRegion) {
101+
Optional<Region> regionOptional =
102+
Region.regions().stream().filter(r -> r.id().equalsIgnoreCase(rdsRegion)).findFirst();
103+
104+
if (regionOptional.isPresent()) {
105+
return regionOptional.get();
106+
}
107+
throw new IllegalArgumentException(String.format("Unknown AWS region '%s'.", rdsRegion));
108+
}
109+
110+
/**
111+
* Creates RDS Cluster/Instances and waits until they are up, and proper IP whitelisting for
112+
* databases.
113+
*
114+
* @param username Master username for access to database
115+
* @param password Master password for access to database
116+
* @param name Database name
117+
* @param identifier Database cluster identifier
118+
* @param engine Database engine to use, refer to
119+
* https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html
120+
* @param instanceClass instance class, refer to
121+
* https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.DBInstanceClass.html
122+
* @param instances number of instances to spin up
123+
* @return An endpoint for one of the instances
124+
* @throws InterruptedException when clusters have not started after 30 minutes
125+
*/
126+
public String createCluster(
127+
String username,
128+
String password,
129+
String name,
130+
String identifier,
131+
String engine,
132+
String instanceClass,
133+
int instances)
134+
throws InterruptedException {
135+
dbUsername = username;
136+
dbPassword = password;
137+
dbName = name;
138+
dbIdentifier = identifier;
139+
dbEngine = engine;
140+
dbInstanceClass = instanceClass;
141+
numOfInstances = instances;
142+
return createCluster();
143+
}
144+
145+
/**
146+
* Creates RDS Cluster/Instances and waits until they are up, and proper IP whitelisting for
147+
* databases.
148+
*
149+
* @param username Master username for access to database
150+
* @param password Master password for access to database
151+
* @param name Database name
152+
* @param identifier Database identifier
153+
* @return An endpoint for one of the instances
154+
* @throws InterruptedException when clusters have not started after 30 minutes
155+
*/
156+
public String createCluster(String username, String password, String name, String identifier)
157+
throws InterruptedException {
158+
dbUsername = username;
159+
dbPassword = password;
160+
dbName = name;
161+
dbIdentifier = identifier;
162+
return createCluster();
163+
}
164+
165+
/**
166+
* Creates RDS Cluster/Instances and waits until they are up, and proper IP whitelisting for
167+
* databases.
168+
*
169+
* @return An endpoint for one of the instances
170+
* @throws InterruptedException when clusters have not started after 30 minutes
171+
*/
172+
public String createCluster() throws InterruptedException {
173+
// Create Cluster
174+
final Tag testRunnerTag = Tag.builder().key("env").value("test-runner").build();
175+
176+
final CreateDbClusterRequest dbClusterRequest =
177+
CreateDbClusterRequest.builder()
178+
.dbClusterIdentifier(dbIdentifier)
179+
.databaseName(dbName)
180+
.masterUsername(dbUsername)
181+
.masterUserPassword(dbPassword)
182+
.sourceRegion(dbRegion.id())
183+
.enableIAMDatabaseAuthentication(true)
184+
.engine(dbEngine)
185+
.storageEncrypted(true)
186+
.tags(testRunnerTag)
187+
.build();
188+
189+
rdsClient.createDBCluster(dbClusterRequest);
190+
191+
// Create Instances
192+
for (int i = 1; i <= numOfInstances; i++) {
193+
rdsClient.createDBInstance(
194+
CreateDbInstanceRequest.builder()
195+
.dbClusterIdentifier(dbIdentifier)
196+
.dbInstanceIdentifier(dbIdentifier + "-" + i)
197+
.dbClusterIdentifier(dbIdentifier)
198+
.dbInstanceClass(dbInstanceClass)
199+
.engine(dbEngine)
200+
.publiclyAccessible(true)
201+
.tags(testRunnerTag)
202+
.build());
203+
}
204+
205+
// Wait for all instances to be up
206+
final RdsWaiter waiter = rdsClient.waiter();
207+
WaiterResponse<DescribeDbInstancesResponse> waiterResponse =
208+
waiter.waitUntilDBInstanceAvailable(
209+
(requestBuilder) ->
210+
requestBuilder.filters(
211+
Filter.builder().name("db-cluster-id").values(dbIdentifier).build()),
212+
(configurationBuilder) -> configurationBuilder.waitTimeout(Duration.ofMinutes(30)));
213+
214+
if (waiterResponse.matched().exception().isPresent()) {
215+
deleteCluster();
216+
throw new InterruptedException(
217+
"Unable to start AWS RDS Cluster & Instances after waiting for 30 minutes");
218+
}
219+
220+
final DescribeDbInstancesResponse dbInstancesResult =
221+
rdsClient.describeDBInstances(
222+
(builder) ->
223+
builder.filters(
224+
Filter.builder().name("db-cluster-id").values(dbIdentifier).build()));
225+
final String endpoint = dbInstancesResult.dbInstances().get(0).endpoint().address();
226+
return endpoint.substring(endpoint.indexOf('.') + 1);
227+
}
228+
229+
/**
230+
* Gets public IP.
231+
*
232+
* @return public IP of user
233+
* @throws UnknownHostException when checkip host isn't available
234+
*/
235+
public String getPublicIPAddress() throws UnknownHostException {
236+
String ip;
237+
try {
238+
URL ipChecker = new URL("http://checkip.amazonaws.com");
239+
BufferedReader reader = new BufferedReader(new InputStreamReader(ipChecker.openStream()));
240+
ip = reader.readLine();
241+
} catch (Exception e) {
242+
throw new UnknownHostException("Unable to get IP");
243+
}
244+
return ip;
245+
}
246+
247+
/** Authorizes IP to EC2 Security groups for RDS access. */
248+
public void ec2AuthorizeIP(String ipAddress) {
249+
if (StringUtils.isNullOrEmpty(ipAddress)) {
250+
return;
251+
}
252+
try {
253+
ec2Client.authorizeSecurityGroupIngress(
254+
(builder) ->
255+
builder
256+
.groupName(dbSecGroup)
257+
.cidrIp(ipAddress + "/32")
258+
.ipProtocol("-1") // All protocols
259+
.fromPort(0) // For all ports
260+
.toPort(65535));
261+
} catch (Ec2Exception exception) {
262+
if (!DUPLICATE_IP_ERROR_CODE.equalsIgnoreCase(exception.awsErrorDetails().errorCode())) {
263+
throw exception;
264+
}
265+
}
266+
}
267+
268+
/** De-authorizes IP from EC2 Security groups. */
269+
public void ec2DeauthorizesIP(String ipAddress) {
270+
if (StringUtils.isNullOrEmpty(ipAddress)) {
271+
return;
272+
}
273+
try {
274+
ec2Client.revokeSecurityGroupIngress(
275+
(builder) ->
276+
builder
277+
.groupName(dbSecGroup)
278+
.cidrIp(ipAddress + "/32")
279+
.ipProtocol("-1") // All protocols
280+
.fromPort(0) // For all ports
281+
.toPort(65535));
282+
} catch (Ec2Exception exception) {
283+
// Ignore
284+
}
285+
}
286+
287+
/**
288+
* Destroys all instances and clusters. Removes IP from EC2 whitelist.
289+
*
290+
* @param identifier database identifier to delete
291+
*/
292+
public void deleteCluster(String identifier) {
293+
dbIdentifier = identifier;
294+
deleteCluster();
295+
}
296+
297+
/** Destroys all instances and clusters. Removes IP from EC2 whitelist. */
298+
public void deleteCluster() {
299+
// Tear down instances
300+
for (int i = 1; i <= numOfInstances; i++) {
301+
rdsClient.deleteDBInstance(
302+
DeleteDbInstanceRequest.builder()
303+
.dbInstanceIdentifier(dbIdentifier + "-" + i)
304+
.skipFinalSnapshot(true)
305+
.build());
306+
}
307+
308+
// Tear down cluster
309+
rdsClient.deleteDBCluster(
310+
(builder -> builder.skipFinalSnapshot(true).dbClusterIdentifier(dbIdentifier)));
311+
}
312+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* AWS JDBC Proxy Driver
3+
* Copyright Amazon.com Inc. or affiliates.
4+
* See the LICENSE file in the project root for more information.
5+
*/
6+
7+
package integration.util;
8+
9+
import org.testcontainers.containers.output.BaseConsumer;
10+
import org.testcontainers.containers.output.OutputFrame;
11+
12+
public class ConsoleConsumer
13+
extends BaseConsumer<org.testcontainers.containers.output.Slf4jLogConsumer> {
14+
15+
private boolean separateOutputStreams;
16+
17+
public ConsoleConsumer() {
18+
this(false);
19+
}
20+
21+
public ConsoleConsumer(boolean separateOutputStreams) {
22+
this.separateOutputStreams = separateOutputStreams;
23+
}
24+
25+
public ConsoleConsumer withSeparateOutputStreams() {
26+
this.separateOutputStreams = true;
27+
return this;
28+
}
29+
30+
@Override
31+
public void accept(OutputFrame outputFrame) {
32+
final OutputFrame.OutputType outputType = outputFrame.getType();
33+
34+
final String utf8String = outputFrame.getUtf8String();
35+
36+
switch (outputType) {
37+
case END:
38+
break;
39+
case STDOUT:
40+
System.out.print(utf8String);
41+
break;
42+
case STDERR:
43+
if (separateOutputStreams) {
44+
System.err.print(utf8String);
45+
} else {
46+
System.out.print(utf8String);
47+
}
48+
break;
49+
default:
50+
throw new IllegalArgumentException("Unexpected outputType " + outputType);
51+
}
52+
}
53+
}

‎driver-proxy/src/test/java/integration/util/ContainerHelper.java

+395
Large diffs are not rendered by default.

‎driver-proxy/src/test/java/software/aws/rds/jdbc/proxydriver/util/ConnectionUrlParserTest.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ private static Stream<Arguments> testGetHostsFromConnectionUrlArguments() {
3939
Arguments.of("jdbc//host:3303/db?param=1", Collections.singletonList(new HostSpec("host", 3303))),
4040
Arguments.of("protocol//host2:3303", Collections.singletonList(new HostSpec("host2", 3303))),
4141
Arguments.of("foo//host:3303/?#", Collections.singletonList(new HostSpec("host", 3303))),
42-
Arguments.of("jdbc:mysql:replication://host:badInt?param=", Collections.singletonList(new HostSpec("host"))),
43-
Arguments.of(
44-
"jdbc:driver:test://source,replica1:3303,host/test",
45-
Arrays.asList(new HostSpec("source"), new HostSpec("replica1", 3303), new HostSpec("host")))
42+
Arguments.of("jdbc:mysql:replication://host:badInt?param=",
43+
Collections.singletonList(new HostSpec("host"))),
44+
Arguments.of("jdbc:driver:test://source,replica1:3303,host/test",
45+
Arrays.asList(new HostSpec("source"), new HostSpec("replica1", 3303),
46+
new HostSpec("host")))
4647
);
4748
}
4849
}

‎driver-proxy/src/test/resources/logging-test.properties

+3
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,6 @@ java.util.logging.ConsoleHandler.level=ALL
55
software.aws.rds.jdbc.proxydriver.Driver.level=ALL
66
software.aws.rds.jdbc.proxydriver.plugin.ExecutionTimeConnectionPlugin.level=ALL
77
software.aws.rds.jdbc.proxydriver.plugin.LogQueryConnectionPlugin.level=ALL
8+
9+
org.testcontainers.level=INFO
10+
com.github.dockerjava.level=WARNING

0 commit comments

Comments
 (0)
Please sign in to comment.