From 0b719cb88de241a5ecc921d94457866b46b35cb9 Mon Sep 17 00:00:00 2001 From: Adar Ovadia Date: Thu, 26 Sep 2024 12:31:18 +0000 Subject: [PATCH 1/2] add node cluster mode example Signed-off-by: Adar Ovadia --- examples/node/cluster_example.ts | 146 ++++++++++++++++++++++++++++ examples/node/standalone_example.ts | 129 ++++++++++++++++++++++++ examples/python/cluster_example.py | 1 + package.json | 20 ++-- 4 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 examples/node/cluster_example.ts create mode 100644 examples/node/standalone_example.ts diff --git a/examples/node/cluster_example.ts b/examples/node/cluster_example.ts new file mode 100644 index 0000000000..887fdd1700 --- /dev/null +++ b/examples/node/cluster_example.ts @@ -0,0 +1,146 @@ +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ + +import { + ClosingError, + ConnectionError, + GlideClusterClient, + InfoOptions, + Logger, + RequestError, + TimeoutError, +} from "@valkey/valkey-glide"; + +/** + * Creates and returns a GlideClusterClient instance. + * This function initializes a GlideClusterClient with the provided list of nodes. + * The nodesList may contain the address of one or more cluster nodes, and the + * client will automatically discover all nodes in the cluster. + * @param nodesList A list of tuples where each tuple contains a host (str) and port (int). Defaults to [("localhost", 6379)]. + * @returns An instance of GlideClusterClient connected to the discovered nodes. + */ +async function createClient(nodesList = [{ host: "localhost", port: 6379 }]) { + const addresses = nodesList.map((node) => ({ + host: node.host, + port: node.port, + })); + + // Check `GlideClusterClientConfiguration` for additional options. + return await GlideClusterClient.createClient({ + addresses: addresses, + // if the server uses TLS, you'll need to enable it. Otherwise the connection attempt will time out silently. + // useTLS: true, + }); +} + +/** + * Executes the main logic of the application, performing basic operations + * such as SET, GET, PING, and INFO REPLICATION using the provided GlideClusterClient. + * @param client An instance of GlideClusterClient. + */ +async function appLogic(client: GlideClusterClient) { + // Send SET and GET + const setResponse = await client.set("foo", "bar"); + Logger.log("info", "app", `Set response is: ${setResponse}`); + + const getResponse = await client.get("foo"); + Logger.log("info", "app", `Get response is: ${getResponse?.toString()}`); + + // Send PING to all primaries (according to Redis's PING request_policy) + const pong = await client.ping(); + Logger.log("info", "app", `PING response: ${pong}`); + + // Send INFO REPLICATION with routing option to all nodes + const infoReplResps = await client.info({ + sections: [InfoOptions.Replication], + }); + const infoReplicationValues = Object.values(infoReplResps); + + Logger.log( + "info", + "app", + `INFO REPLICATION responses from all nodes are:\n`, + ); + + infoReplicationValues.forEach((item) => + Logger.log("info", "glide", item as string), + ); +} + +/** + * Executes the application logic with exception handling. + */ +async function execAppLogic() { + // Loop through with exception handling + while (true) { + let client; + + try { + client = await createClient(); + return await appLogic(client); + } catch (error) { + switch (true) { + case error instanceof ClosingError: + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if ((error as ClosingError).message.includes("NOAUTH")) { + Logger.log( + "error", + "glide", + `Authentication error encountered: ${error}`, + ); + throw error; + } + + Logger.log( + "warn", + "glide", + `Client has closed and needs to be re-created: ${error}`, + ); + throw error; + case error instanceof TimeoutError: + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log("error", "glide", `Timeout error: ${error}`); + throw error; + case error instanceof ConnectionError: + // The client wasn't able to reestablish the connection within the given retries + Logger.log("error", "glide", `Connection error: ${error}`); + throw error; + case error instanceof RequestError: + // Other error reported during a request, such as a server response error + Logger.log( + "error", + "glide", + `RequestError encountered: ${error}`, + ); + throw error; + default: + Logger.log("error", "glide", `Unexpected error: ${error}`); + throw error; + } + } finally { + try { + if (client) { + await client.close(); + } + } catch (error) { + Logger.log( + "warn", + "glide", + `Error encountered while closing the client: ${error}`, + ); + } + } + } +} + +function main() { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig("info"); + // Optional - set the logger to write to a file + // Logger.setLoggerConfig("info", fileName); + execAppLogic(); +} + +main(); diff --git a/examples/node/standalone_example.ts b/examples/node/standalone_example.ts new file mode 100644 index 0000000000..5f1025d3bf --- /dev/null +++ b/examples/node/standalone_example.ts @@ -0,0 +1,129 @@ +/** + * Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 + */ + +import { + ClosingError, + ConnectionError, + GlideClient, + Logger, + RequestError, + TimeoutError, +} from "@valkey/valkey-glide"; + +/** + * Creates and returns a GlideClient instance. + * This function initializes a GlideClient with the provided list of nodes. + * The nodes_list may contain either only primary node or a mix of primary + * and replica nodes. The GlideClient use these nodes to connect to + * the Standalone setup servers. + * @param nodesList A list of tuples where each tuple contains a host (str) and port (int). Defaults to [("localhost", 6379)]. + * @returns An instance of GlideClient connected to the discovered nodes. + */ +async function createClient(nodesList = [{ host: "localhost", port: 6379 }]) { + const addresses = nodesList.map((node) => ({ + host: node.host, + port: node.port, + })); + + // Check `GlideClusterClientConfiguration` for additional options. + return await GlideClient.createClient({ + addresses: addresses, + // if the server uses TLS, you'll need to enable it. Otherwise the connection attempt will time out silently. + // useTLS: true, + }); +} + +/** + * Executes the main logic of the application, performing basic operations + * such as SET, GET, PING, and INFO REPLICATION using the provided GlideClusterClient. + * @param client An instance of GlideClusterClient. + */ +async function appLogic(client: GlideClient) { + // Send SET and GET + const setResponse = await client.set("foo", "bar"); + Logger.log("info", "app", `Set response is: ${setResponse}`); + + const getResponse = await client.get("foo"); + Logger.log("info", "app", `Get response is: ${getResponse?.toString()}`); + + // Send PING to all primaries (according to Redis's PING request_policy) + const pong = await client.ping(); + Logger.log("info", "app", `PING response: ${pong}`); +} + +/** + * Executes the application logic with exception handling. + */ +async function execAppLogic() { + // Loop through with exception handling + while (true) { + let client; + + try { + client = await createClient(); + return await appLogic(client); + } catch (error) { + switch (true) { + case error instanceof ClosingError: + // If the error message contains "NOAUTH", raise the exception + // because it indicates a critical authentication issue. + if ((error as ClosingError).message.includes("NOAUTH")) { + Logger.log( + "error", + "glide", + `Authentication error encountered: ${error}`, + ); + throw error; + } + Logger.log( + "warn", + "glide", + `Client has closed and needs to be re-created: ${error}`, + ); + throw error; + case error instanceof TimeoutError: + // A request timed out. You may choose to retry the execution based on your application's logic + Logger.log("error", "glide", `Timeout error: ${error}`); + throw error; + case error instanceof ConnectionError: + // The client wasn't able to reestablish the connection within the given retries + Logger.log("error", "glide", `Connection error: ${error}`); + throw error; + case error instanceof RequestError: + // Other error reported during a request, such as a server response error + Logger.log( + "error", + "glide", + `RequestError encountered: ${error}`, + ); + throw error; + default: + Logger.log("error", "glide", `Unexpected error: ${error}`); + throw error; + } + } finally { + try { + if (client) { + await client.close(); + } + } catch (error) { + Logger.log( + "warn", + "glide", + `Error encountered while closing the client: ${error}`, + ); + } + } + } +} + +function main() { + // In this example, we will utilize the client's logger for all log messages + Logger.setLoggerConfig("info"); + // Optional - set the logger to write to a file + // Logger.setLoggerConfig("info", fileName); + execAppLogic(); +} + +main(); diff --git a/examples/python/cluster_example.py b/examples/python/cluster_example.py index e26986bc87..79da83ae51 100644 --- a/examples/python/cluster_example.py +++ b/examples/python/cluster_example.py @@ -98,6 +98,7 @@ async def exec_app_logic(): "glide", f"Client has closed and needs to be re-created: {e}", ) + raise e except TimeoutError as e: # A request timed out. You may choose to retry the execution based on your application's logic Logger.log(LogLevel.ERROR, "glide", f"TimeoutError encountered: {e}") diff --git a/package.json b/package.json index 35dc96c83b..fa682d7107 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,12 @@ { - "devDependencies": { - "@eslint/js": "^9.10.0", - "@types/eslint__js": "^8.42.3", - "@types/eslint-config-prettier": "^6.11.3", - "eslint": "^9.10.0", - "eslint-config-prettier": "^9.1.0", - "prettier": "^3.3.3", - "typescript": "^5.6.2", - "typescript-eslint": "^8.5.0" - } + "devDependencies": { + "@eslint/js": "^9.10.0", + "@types/eslint__js": "^8.42.3", + "@types/eslint-config-prettier": "^6.11.3", + "eslint": "^9.10.0", + "eslint-config-prettier": "^9.1.0", + "prettier": "^3.3.3", + "typescript": "^5.6.2", + "typescript-eslint": "^8.5.0" + } } From c457eaadae05bfcb65dee36ae6b06a977a6dc76a Mon Sep 17 00:00:00 2001 From: Adar Ovadia Date: Sun, 29 Sep 2024 07:50:15 +0000 Subject: [PATCH 2/2] fix examples Signed-off-by: Adar Ovadia --- examples/node/cluster_example.ts | 16 ++++++++-------- examples/node/standalone_example.ts | 21 +++++++++++---------- examples/python/cluster_example.py | 12 ++++++------ 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/examples/node/cluster_example.ts b/examples/node/cluster_example.ts index 887fdd1700..b45fe53d49 100644 --- a/examples/node/cluster_example.ts +++ b/examples/node/cluster_example.ts @@ -51,7 +51,7 @@ async function appLogic(client: GlideClusterClient) { const pong = await client.ping(); Logger.log("info", "app", `PING response: ${pong}`); - // Send INFO REPLICATION with routing option to all nodes + // Send INFO REPLICATION to all nodes const infoReplResps = await client.info({ sections: [InfoOptions.Replication], }); @@ -90,14 +90,14 @@ async function execAppLogic() { "glide", `Authentication error encountered: ${error}`, ); - throw error; + } else { + Logger.log( + "warn", + "glide", + `Client has closed and needs to be re-created: ${error}`, + ); } - - Logger.log( - "warn", - "glide", - `Client has closed and needs to be re-created: ${error}`, - ); + throw error; case error instanceof TimeoutError: // A request timed out. You may choose to retry the execution based on your application's logic diff --git a/examples/node/standalone_example.ts b/examples/node/standalone_example.ts index 5f1025d3bf..0716cd8475 100644 --- a/examples/node/standalone_example.ts +++ b/examples/node/standalone_example.ts @@ -26,7 +26,7 @@ async function createClient(nodesList = [{ host: "localhost", port: 6379 }]) { port: node.port, })); - // Check `GlideClusterClientConfiguration` for additional options. + // Check `GlideClientConfiguration` for additional options. return await GlideClient.createClient({ addresses: addresses, // if the server uses TLS, you'll need to enable it. Otherwise the connection attempt will time out silently. @@ -36,8 +36,8 @@ async function createClient(nodesList = [{ host: "localhost", port: 6379 }]) { /** * Executes the main logic of the application, performing basic operations - * such as SET, GET, PING, and INFO REPLICATION using the provided GlideClusterClient. - * @param client An instance of GlideClusterClient. + * such as SET, GET, PING, and INFO REPLICATION using the provided GlideClient. + * @param client An instance of GlideClient. */ async function appLogic(client: GlideClient) { // Send SET and GET @@ -47,7 +47,7 @@ async function appLogic(client: GlideClient) { const getResponse = await client.get("foo"); Logger.log("info", "app", `Get response is: ${getResponse?.toString()}`); - // Send PING to all primaries (according to Redis's PING request_policy) + // Send PING to primary const pong = await client.ping(); Logger.log("info", "app", `PING response: ${pong}`); } @@ -74,13 +74,14 @@ async function execAppLogic() { "glide", `Authentication error encountered: ${error}`, ); - throw error; + } else { + Logger.log( + "warn", + "glide", + `Client has closed and needs to be re-created: ${error}`, + ); } - Logger.log( - "warn", - "glide", - `Client has closed and needs to be re-created: ${error}`, - ); + throw error; case error instanceof TimeoutError: // A request timed out. You may choose to retry the execution based on your application's logic diff --git a/examples/python/cluster_example.py b/examples/python/cluster_example.py index 79da83ae51..c3cefbd14f 100644 --- a/examples/python/cluster_example.py +++ b/examples/python/cluster_example.py @@ -92,12 +92,12 @@ async def exec_app_logic(): "glide", f"Authentication error encountered: {e}", ) - raise e - Logger.log( - LogLevel.WARN, - "glide", - f"Client has closed and needs to be re-created: {e}", - ) + else: + Logger.log( + LogLevel.WARN, + "glide", + f"Client has closed and needs to be re-created: {e}", + ) raise e except TimeoutError as e: # A request timed out. You may choose to retry the execution based on your application's logic