Skip to content

Commit f86dc16

Browse files
committed
feat: failover2 integration tests
1 parent bf6da0c commit f86dc16

File tree

1 file changed

+265
-0
lines changed

1 file changed

+265
-0
lines changed
+265
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
/*
2+
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License").
5+
You may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { TestEnvironment } from "./utils/test_environment";
18+
import { DriverHelper } from "./utils/driver_helper";
19+
import { AuroraTestUtility } from "./utils/aurora_test_utility";
20+
import { FailoverSuccessError, TransactionResolutionUnknownError } from "../../../../common/lib/utils/errors";
21+
import { DatabaseEngine } from "./utils/database_engine";
22+
import { QueryResult } from "pg";
23+
import { ProxyHelper } from "./utils/proxy_helper";
24+
import { RdsUtils } from "../../../../common/lib/utils/rds_utils";
25+
import { logger } from "../../../../common/logutils";
26+
import { features, instanceCount } from "./config";
27+
import { TestEnvironmentFeatures } from "./utils/test_environment_features";
28+
import { PluginManager } from "../../../../common/lib";
29+
import { TransactionIsolationLevel } from "../../../../common/lib/utils/transaction_isolation_level";
30+
import { MonitoringRdsHostListProvider } from "../../../../common/lib/host_list_provider/monitoring/monitoring_host_list_provider";
31+
32+
const itIf =
33+
features.includes(TestEnvironmentFeatures.FAILOVER_SUPPORTED) &&
34+
!features.includes(TestEnvironmentFeatures.PERFORMANCE) &&
35+
!features.includes(TestEnvironmentFeatures.RUN_AUTOSCALING_TESTS_ONLY) &&
36+
instanceCount >= 2
37+
? it
38+
: it.skip;
39+
const itIfTwoInstance = instanceCount == 2 ? itIf : it.skip;
40+
41+
let env: TestEnvironment;
42+
let driver;
43+
let client: any;
44+
let secondaryClient: any;
45+
let initClientFunc: (props: any) => any;
46+
47+
let auroraTestUtility: AuroraTestUtility;
48+
49+
async function initDefaultConfig(host: string, port: number, connectToProxy: boolean): Promise<any> {
50+
let config: any = {
51+
user: env.databaseInfo.username,
52+
host: host,
53+
database: env.databaseInfo.defaultDbName,
54+
password: env.databaseInfo.password,
55+
port: port,
56+
plugins: "failover2",
57+
failoverTimeoutMs: 250000,
58+
enableTelemetry: true,
59+
telemetryTracesBackend: "OTLP",
60+
telemetryMetricsBackend: "OTLP"
61+
};
62+
if (connectToProxy) {
63+
config["clusterInstanceHostPattern"] = "?." + env.proxyDatabaseInfo.instanceEndpointSuffix;
64+
}
65+
config = DriverHelper.addDriverSpecificConfiguration(config, env.engine);
66+
return config;
67+
}
68+
69+
describe("aurora failover2", () => {
70+
beforeEach(async () => {
71+
logger.info(`Test started: ${expect.getState().currentTestName}`);
72+
env = await TestEnvironment.getCurrent();
73+
74+
auroraTestUtility = new AuroraTestUtility(env.region);
75+
driver = DriverHelper.getDriverForDatabaseEngine(env.engine);
76+
initClientFunc = DriverHelper.getClient(driver);
77+
await ProxyHelper.enableAllConnectivity();
78+
await TestEnvironment.verifyClusterStatus();
79+
80+
client = null;
81+
secondaryClient = null;
82+
}, 1320000);
83+
84+
afterEach(async () => {
85+
if (client !== null) {
86+
try {
87+
await client.end();
88+
} catch (error) {
89+
// pass
90+
}
91+
}
92+
93+
if (secondaryClient !== null) {
94+
try {
95+
await secondaryClient.end();
96+
} catch (error) {
97+
// pass
98+
}
99+
}
100+
await MonitoringRdsHostListProvider.clearAll();
101+
await PluginManager.releaseResources();
102+
logger.info(`Test finished: ${expect.getState().currentTestName}`);
103+
}, 1320000);
104+
105+
itIf(
106+
"fails from writer to new writer on connection invocation",
107+
async () => {
108+
const config = await initDefaultConfig(env.databaseInfo.writerInstanceEndpoint, env.databaseInfo.instanceEndpointPort, false);
109+
client = initClientFunc(config);
110+
111+
await client.connect();
112+
113+
const initialWriterId = await auroraTestUtility.queryInstanceId(client);
114+
expect(await auroraTestUtility.isDbInstanceWriter(initialWriterId)).toStrictEqual(true);
115+
116+
// Crash instance 1 and nominate a new writer
117+
await auroraTestUtility.failoverClusterAndWaitUntilWriterChanged();
118+
119+
await expect(async () => {
120+
await auroraTestUtility.queryInstanceId(client);
121+
}).rejects.toThrow(FailoverSuccessError);
122+
123+
// Assert that we are connected to the new writer after failover happens
124+
const currentConnectionId = await auroraTestUtility.queryInstanceId(client);
125+
expect(await auroraTestUtility.isDbInstanceWriter(currentConnectionId)).toBe(true);
126+
expect(currentConnectionId).not.toBe(initialWriterId);
127+
},
128+
1320000
129+
);
130+
131+
itIf(
132+
"writer fails within transaction",
133+
async () => {
134+
const config = await initDefaultConfig(env.databaseInfo.writerInstanceEndpoint, env.databaseInfo.instanceEndpointPort, false);
135+
client = initClientFunc(config);
136+
137+
await client.connect();
138+
const initialWriterId = await auroraTestUtility.queryInstanceId(client);
139+
expect(await auroraTestUtility.isDbInstanceWriter(initialWriterId)).toStrictEqual(true);
140+
141+
await DriverHelper.executeQuery(env.engine, client, "DROP TABLE IF EXISTS test3_3");
142+
await DriverHelper.executeQuery(env.engine, client, "CREATE TABLE test3_3 (id int not null primary key, test3_3_field varchar(255) not null)");
143+
144+
await DriverHelper.executeQuery(env.engine, client, "START TRANSACTION"); // start transaction
145+
await DriverHelper.executeQuery(env.engine, client, "INSERT INTO test3_3 VALUES (1, 'test field string 1')");
146+
147+
// Crash instance 1 and nominate a new writer
148+
await auroraTestUtility.failoverClusterAndWaitUntilWriterChanged();
149+
150+
await expect(async () => {
151+
await DriverHelper.executeQuery(env.engine, client, "INSERT INTO test3_3 VALUES (2, 'test field string 2')");
152+
}).rejects.toThrow(TransactionResolutionUnknownError);
153+
154+
// Attempt to query the instance id.
155+
const currentConnectionId = await auroraTestUtility.queryInstanceId(client);
156+
157+
// Assert that we are connected to the new writer after failover happens.
158+
expect(await auroraTestUtility.isDbInstanceWriter(currentConnectionId)).toBe(true);
159+
160+
const nextClusterWriterId = await auroraTestUtility.getClusterWriterInstanceId();
161+
expect(currentConnectionId).toBe(nextClusterWriterId);
162+
expect(initialWriterId).not.toBe(nextClusterWriterId);
163+
164+
// Assert that NO row has been inserted to the table.
165+
const result = await DriverHelper.executeQuery(env.engine, client, "SELECT count(*) from test3_3");
166+
if (env.engine === DatabaseEngine.PG) {
167+
expect((result as QueryResult).rows[0]["count"]).toBe("0");
168+
} else if (env.engine === DatabaseEngine.MYSQL) {
169+
expect(JSON.parse(JSON.stringify(result))[0][0]["count(*)"]).toBe(0);
170+
}
171+
172+
await DriverHelper.executeQuery(env.engine, client, "DROP TABLE IF EXISTS test3_3");
173+
},
174+
2000000
175+
);
176+
177+
itIf(
178+
"fails from writer and transfers session state",
179+
async () => {
180+
const config = await initDefaultConfig(env.databaseInfo.writerInstanceEndpoint, env.databaseInfo.instanceEndpointPort, false);
181+
client = initClientFunc(config);
182+
183+
await client.connect();
184+
const initialWriterId = await auroraTestUtility.queryInstanceId(client);
185+
expect(await auroraTestUtility.isDbInstanceWriter(initialWriterId)).toBe(true);
186+
187+
await client.setReadOnly(true);
188+
await client.setTransactionIsolation(TransactionIsolationLevel.TRANSACTION_SERIALIZABLE);
189+
190+
if (driver === DatabaseEngine.PG) {
191+
await client.setSchema(env.databaseInfo.defaultDbName);
192+
} else if (driver === DatabaseEngine.MYSQL) {
193+
await client.setAutoCommit(false);
194+
await client.setCatalog(env.databaseInfo.defaultDbName);
195+
}
196+
197+
// Failover cluster and nominate a new writer
198+
await auroraTestUtility.failoverClusterAndWaitUntilWriterChanged();
199+
200+
await expect(async () => {
201+
await auroraTestUtility.queryInstanceId(client);
202+
}).rejects.toThrow(FailoverSuccessError);
203+
204+
// Assert that we are connected to the new writer after failover happens
205+
const currentConnectionId = await auroraTestUtility.queryInstanceId(client);
206+
expect(await auroraTestUtility.isDbInstanceWriter(currentConnectionId)).toBe(true);
207+
expect(currentConnectionId).not.toBe(initialWriterId);
208+
expect(client.isReadOnly()).toBe(true);
209+
expect(client.getTransactionIsolation()).toBe(TransactionIsolationLevel.TRANSACTION_SERIALIZABLE);
210+
if (driver === DatabaseEngine.PG) {
211+
expect(client.getSchema()).toBe(env.databaseInfo.defaultDbName);
212+
} else if (driver === DatabaseEngine.MYSQL) {
213+
expect(client.getAutoCommit()).toBe(false);
214+
expect(client.getCatalog()).toBe(env.databaseInfo.defaultDbName);
215+
}
216+
},
217+
1320000
218+
);
219+
220+
itIfTwoInstance(
221+
"fails from reader to writer",
222+
async () => {
223+
// Connect to writer instance
224+
const writerConfig = await initDefaultConfig(env.proxyDatabaseInfo.writerInstanceEndpoint, env.proxyDatabaseInfo.instanceEndpointPort, true);
225+
client = initClientFunc(writerConfig);
226+
await client.connect();
227+
const initialWriterId = await auroraTestUtility.queryInstanceId(client);
228+
expect(await auroraTestUtility.isDbInstanceWriter(initialWriterId)).toStrictEqual(true);
229+
230+
// Get a reader instance
231+
let readerInstanceHost;
232+
for (const host of env.proxyDatabaseInfo.instances) {
233+
if (host.instanceId && host.instanceId !== initialWriterId) {
234+
readerInstanceHost = host.host;
235+
}
236+
}
237+
if (!readerInstanceHost) {
238+
throw new Error("Could not find a reader instance");
239+
}
240+
const readerConfig = await initDefaultConfig(readerInstanceHost, env.proxyDatabaseInfo.instanceEndpointPort, true);
241+
242+
secondaryClient = initClientFunc(readerConfig);
243+
await secondaryClient.connect();
244+
245+
// Crash the reader instance
246+
const rdsUtils = new RdsUtils();
247+
const readerInstanceId = rdsUtils.getRdsInstanceId(readerInstanceHost);
248+
if (readerInstanceId) {
249+
await ProxyHelper.disableConnectivity(env.engine, readerInstanceId);
250+
251+
await expect(async () => {
252+
await auroraTestUtility.queryInstanceId(secondaryClient);
253+
}).rejects.toThrow(FailoverSuccessError);
254+
255+
await ProxyHelper.enableConnectivity(readerInstanceId);
256+
257+
// Assert that we are currently connected to the writer instance
258+
const currentConnectionId = await auroraTestUtility.queryInstanceId(secondaryClient);
259+
expect(await auroraTestUtility.isDbInstanceWriter(currentConnectionId)).toBe(true);
260+
expect(currentConnectionId).toBe(initialWriterId);
261+
}
262+
},
263+
1320000
264+
);
265+
});

0 commit comments

Comments
 (0)