Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/client/lib/tests/test-scenario/configuration.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from "./test-scenario.util";
import { createClient } from "../../..";
import { FaultInjectorClient } from "./fault-injector-client";
import { MovingEndpointType } from "../../../dist/lib/client/enterprise-maintenance-manager";
import { MovingEndpointType } from "../../../lib/client/enterprise-maintenance-manager";
import { RedisTcpSocketOptions } from "../../client/socket";

describe("Client Configuration and Handshake", () => {
Expand Down Expand Up @@ -59,7 +59,7 @@ describe("Client Configuration and Handshake", () => {
it(`clientHandshakeWithEndpointType '${endpointType}'`, async () => {
try {
client = await createTestClient(clientConfig, {
maintMovingEndpointType: endpointType,
maintEndpointType: endpointType
});
client.on("error", () => {});

Expand Down Expand Up @@ -154,7 +154,7 @@ describe("Client Configuration and Handshake", () => {
describe("Feature Enablement", () => {
it("connectionHandshakeIncludesEnablingNotifications", async () => {
client = await createTestClient(clientConfig, {
maintPushNotifications: "enabled",
maintNotifications: "enabled"
});

const { action_id } = await faultInjectorClient.migrateAndBindAction({
Expand All @@ -180,7 +180,7 @@ describe("Client Configuration and Handshake", () => {
it("disabledDontReceiveNotifications", async () => {
try {
client = await createTestClient(clientConfig, {
maintPushNotifications: "disabled",
maintNotifications: "disabled",
socket: {
reconnectStrategy: false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,25 +86,25 @@ describe("Connection Handoff", () => {
{
name: "external-ip",
clientOptions: {
maintMovingEndpointType: "external-ip",
maintEndpointType: "external-ip",
},
},
{
name: "external-fqdn",
clientOptions: {
maintMovingEndpointType: "external-fqdn",
maintEndpointType: "external-fqdn",
},
},
{
name: "auto",
clientOptions: {
maintMovingEndpointType: "auto",
maintEndpointType: "auto",
},
},
{
name: "none",
clientOptions: {
maintMovingEndpointType: "none",
maintEndpointType: "none",
},
},
];
Expand Down Expand Up @@ -156,6 +156,7 @@ describe("Connection Handoff", () => {

describe("Connection Cleanup", () => {
it("should shut down old connection", async () => {
client = await createTestClient(clientConfig);
const spyObject = spyOnTemporaryClientInstanceMethod(client, "destroy");

const { action_id: lowTimeoutBindAndMigrateActionId } =
Expand Down
23 changes: 22 additions & 1 deletion packages/client/lib/tests/test-scenario/fault-injector-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ export class FaultInjectorClient {
return this.#request<T>("POST", "/action", action);
}

// public async printStatus() {
// const action = {
// type: 'execute_rladmin_command',
// parameters: {
// rladmin_command: "status",
// bdb_id: "1"
// }
// }
// const { action_id } = await this.#request<{action_id: string}>("POST", "/action", action);
// const status = await this.waitForAction(action_id);
// //@ts-ignore
// console.log(status.output.output);
// }

/**
* Gets the status of a specific action.
* @param actionId The ID of the action to check
Expand Down Expand Up @@ -87,7 +101,13 @@ export class FaultInjectorClient {
while (Date.now() - startTime < maxWaitTime) {
const action = await this.getActionStatus<ActionStatus>(actionId);

if (["finished", "failed", "success"].includes(action.status)) {
if (action.status === "failed") {
throw new Error(
`Action id: ${actionId} failed! Error: ${action.error}`
);
}

if (["finished", "success"].includes(action.status)) {
return action;
}

Expand Down Expand Up @@ -118,6 +138,7 @@ export class FaultInjectorClient {
type: "migrate",
params: {
cluster_index: clusterIndexStr,
bdb_id: bdbIdStr,
},
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ describe("Negative tests", () => {
() =>
createClient({
RESP: 2,
maintPushNotifications: "enabled",
maintNotifications: "enabled",
}),
"Error: Graceful Maintenance is only supported with RESP3",
);
Expand Down
226 changes: 226 additions & 0 deletions packages/client/lib/tests/test-scenario/pn-failover.e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
import assert from "node:assert";
import diagnostics_channel from "node:diagnostics_channel";
import { FaultInjectorClient } from "./fault-injector-client";
import {
createTestClient,
getDatabaseConfig,
getDatabaseConfigFromEnv,
getEnvConfig,
RedisConnectionConfig,
} from "./test-scenario.util";
import { createClient } from "../../..";
import { DiagnosticsEvent } from "../../client/enterprise-maintenance-manager";
import { before } from "mocha";

describe("Push Notifications", () => {
const createNotificationMessageHandler = (
result: Record<DiagnosticsEvent["type"], number>,
notifications: Array<DiagnosticsEvent["type"]>
) => {
return (message: unknown) => {
if (notifications.includes((message as DiagnosticsEvent).type)) {
const event = message as DiagnosticsEvent;
result[event.type] = (result[event.type] ?? 0) + 1;
}
};
};

let onMessageHandler: ReturnType<typeof createNotificationMessageHandler>;
let clientConfig: RedisConnectionConfig;
let client: ReturnType<typeof createClient<any, any, any, any>>;
let faultInjectorClient: FaultInjectorClient;

before(() => {
const envConfig = getEnvConfig();
const redisConfig = getDatabaseConfigFromEnv(
envConfig.redisEndpointsConfigPath
);

faultInjectorClient = new FaultInjectorClient(envConfig.faultInjectorUrl);
clientConfig = getDatabaseConfig(redisConfig);
});

afterEach(() => {
if (onMessageHandler!) {
diagnostics_channel.unsubscribe("redis.maintenance", onMessageHandler);
}

if (client && client.isOpen) {
client.destroy();
}
});

describe("Push Notifications Enabled", () => {
beforeEach(async () => {
client = await createTestClient(clientConfig);

await client.flushAll();
});

it("should receive FAILING_OVER and FAILED_OVER push notifications", async () => {
const notifications: Array<DiagnosticsEvent["type"]> = [
"FAILING_OVER",
"FAILED_OVER",
];

const diagnosticsMap: Record<DiagnosticsEvent["type"], number> = {};

onMessageHandler = createNotificationMessageHandler(
diagnosticsMap,
notifications
);

diagnostics_channel.subscribe("redis.maintenance", onMessageHandler);

const { action_id: failoverActionId } =
await faultInjectorClient.triggerAction({
type: "failover",
parameters: {
bdb_id: clientConfig.bdbId.toString(),
cluster_index: 0,
},
});

await faultInjectorClient.waitForAction(failoverActionId);

assert.strictEqual(
diagnosticsMap.FAILING_OVER,
1,
"Should have received exactly one FAILING_OVER notification"
);
assert.strictEqual(
diagnosticsMap.FAILED_OVER,
1,
"Should have received exactly one FAILED_OVER notification"
);
});
});

describe("Push Notifications Disabled - Client", () => {
beforeEach(async () => {
client = await createTestClient(clientConfig, {
maintNotifications: "disabled",
});

client.on("error", (_err) => {
// Expect the socket to be closed
// Ignore errors
});

await client.flushAll();
});

it("should NOT receive FAILING_OVER and FAILED_OVER push notifications", async () => {
const notifications: Array<DiagnosticsEvent["type"]> = [
"FAILING_OVER",
"FAILED_OVER",
];

const diagnosticsMap: Record<DiagnosticsEvent["type"], number> = {};

onMessageHandler = createNotificationMessageHandler(
diagnosticsMap,
notifications
);

diagnostics_channel.subscribe("redis.maintenance", onMessageHandler);

const { action_id: failoverActionId } =
await faultInjectorClient.triggerAction({
type: "failover",
parameters: {
bdb_id: clientConfig.bdbId.toString(),
cluster_index: 0,
},
});

await faultInjectorClient.waitForAction(failoverActionId);

assert.strictEqual(
diagnosticsMap.FAILING_OVER,
undefined,
"Should have received exactly one FAILING_OVER notification"
);
assert.strictEqual(
diagnosticsMap.FAILED_OVER,
undefined,
"Should have received exactly one FAILED_OVER notification"
);
});
});

describe("Push Notifications Disabled - Server", () => {
beforeEach(async () => {
client = await createTestClient(clientConfig);

client.on("error", (_err) => {
// Expect the socket to be closed
// Ignore errors
});

await client.flushAll();
});

before(async () => {
const { action_id: disablePushNotificationsActionId } =
await faultInjectorClient.triggerAction({
type: "update_cluster_config",
parameters: {
config: { client_maint_notifications: false },
},
});

await faultInjectorClient.waitForAction(disablePushNotificationsActionId);
});

after(async () => {
const { action_id: enablePushNotificationsActionId } =
await faultInjectorClient.triggerAction({
type: "update_cluster_config",
parameters: {
config: { client_maint_notifications: true },
},
});

await faultInjectorClient.waitForAction(enablePushNotificationsActionId);
});

it("should NOT receive FAILING_OVER and FAILED_OVER push notifications", async () => {
const notifications: Array<DiagnosticsEvent["type"]> = [
"FAILING_OVER",
"FAILED_OVER",
];

const diagnosticsMap: Record<DiagnosticsEvent["type"], number> = {};

onMessageHandler = createNotificationMessageHandler(
diagnosticsMap,
notifications
);

diagnostics_channel.subscribe("redis.maintenance", onMessageHandler);

const { action_id: failoverActionId } =
await faultInjectorClient.triggerAction({
type: "failover",
parameters: {
bdb_id: clientConfig.bdbId.toString(),
cluster_index: 0,
},
});

await faultInjectorClient.waitForAction(failoverActionId);

assert.strictEqual(
diagnosticsMap.FAILING_OVER,
undefined,
"Should have received exactly one FAILING_OVER notification"
);
assert.strictEqual(
diagnosticsMap.FAILED_OVER,
undefined,
"Should have received exactly one FAILED_OVER notification"
);
});
});
});
Loading