From 17cf320651cd7f6e23d7b6be7aecbe46837bb1ad Mon Sep 17 00:00:00 2001 From: Leibale Date: Tue, 12 Sep 2023 16:02:12 -0400 Subject: [PATCH] CLUSERT [INFO|NODES|REPLICAS] --- .../client/lib/commands/CLUSTER_INFO.spec.ts | 61 ++----- packages/client/lib/commands/CLUSTER_INFO.ts | 55 +------ .../client/lib/commands/CLUSTER_NODES.spec.ts | 155 ++---------------- packages/client/lib/commands/CLUSTER_NODES.ts | 113 +------------ .../lib/commands/CLUSTER_REPLICAS.spec.ts | 23 ++- .../client/lib/commands/CLUSTER_REPLICAS.ts | 13 +- packages/client/lib/commands/index.ts | 18 +- 7 files changed, 80 insertions(+), 358 deletions(-) diff --git a/packages/client/lib/commands/CLUSTER_INFO.spec.ts b/packages/client/lib/commands/CLUSTER_INFO.spec.ts index 69d5c4a8c5..53c5b91092 100644 --- a/packages/client/lib/commands/CLUSTER_INFO.spec.ts +++ b/packages/client/lib/commands/CLUSTER_INFO.spec.ts @@ -3,53 +3,18 @@ import testUtils, { GLOBAL } from '../test-utils'; import { transformArguments, transformReply } from './CLUSTER_INFO'; describe('CLUSTER INFO', () => { - it('transformArguments', () => { - assert.deepEqual( - transformArguments(), - ['CLUSTER', 'INFO'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + transformArguments(), + ['CLUSTER', 'INFO'] + ); + }); - it('transformReply', () => { - assert.deepEqual( - transformReply([ - 'cluster_state:ok', - 'cluster_slots_assigned:16384', - 'cluster_slots_ok:16384', - 'cluster_slots_pfail:0', - 'cluster_slots_fail:0', - 'cluster_known_nodes:6', - 'cluster_size:3', - 'cluster_current_epoch:6', - 'cluster_my_epoch:2', - 'cluster_stats_messages_sent:1483972', - 'cluster_stats_messages_received:1483968' - ].join('\r\n')), - { - state: 'ok', - slots: { - assigned: 16384, - ok: 16384, - pfail: 0, - fail: 0 - }, - knownNodes: 6, - size: 3, - currentEpoch: 6, - myEpoch: 2, - stats: { - messagesSent: 1483972, - messagesReceived: 1483968 - } - } - ); - }); - - testUtils.testWithCluster('clusterNode.clusterInfo', async cluster => { - const client = await cluster.nodeClient(cluster.masters[0]); - assert.notEqual( - await client.clusterInfo(), - null - ); - }, GLOBAL.CLUSTERS.OPEN); + testUtils.testWithCluster('clusterNode.clusterInfo', async cluster => { + const client = await cluster.nodeClient(cluster.masters[0]); + assert.equal( + typeof await client.clusterInfo(), + 'string' + ); + }, GLOBAL.CLUSTERS.OPEN); }); diff --git a/packages/client/lib/commands/CLUSTER_INFO.ts b/packages/client/lib/commands/CLUSTER_INFO.ts index 634515f927..c541de1729 100644 --- a/packages/client/lib/commands/CLUSTER_INFO.ts +++ b/packages/client/lib/commands/CLUSTER_INFO.ts @@ -1,47 +1,10 @@ -export function transformArguments(): Array { +import { VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types'; + +export default { + FIRST_KEY_INDEX: undefined, + IS_READ_ONLY: true, + transformArguments() { return ['CLUSTER', 'INFO']; -} - -interface ClusterInfoReply { - state: string; - slots: { - assigned: number; - ok: number; - pfail: number; - fail: number; - }; - knownNodes: number; - size: number; - currentEpoch: number; - myEpoch: number; - stats: { - messagesSent: number; - messagesReceived: number; - }; -} - -export function transformReply(reply: string): ClusterInfoReply { - const lines = reply.split('\r\n'); - - return { - state: extractLineValue(lines[0]), - slots: { - assigned: Number(extractLineValue(lines[1])), - ok: Number(extractLineValue(lines[2])), - pfail: Number(extractLineValue(lines[3])), - fail: Number(extractLineValue(lines[4])) - }, - knownNodes: Number(extractLineValue(lines[5])), - size: Number(extractLineValue(lines[6])), - currentEpoch: Number(extractLineValue(lines[7])), - myEpoch: Number(extractLineValue(lines[8])), - stats: { - messagesSent: Number(extractLineValue(lines[9])), - messagesReceived: Number(extractLineValue(lines[10])) - } - }; -} - -export function extractLineValue(line: string): string { - return line.substring(line.indexOf(':') + 1); -} + }, + transformReply: undefined as unknown as () => VerbatimStringReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/CLUSTER_NODES.spec.ts b/packages/client/lib/commands/CLUSTER_NODES.spec.ts index 5c6cb74d6c..5c25ab209b 100644 --- a/packages/client/lib/commands/CLUSTER_NODES.spec.ts +++ b/packages/client/lib/commands/CLUSTER_NODES.spec.ts @@ -1,145 +1,20 @@ import { strict as assert } from 'assert'; -import { RedisClusterNodeLinkStates, transformArguments, transformReply } from './CLUSTER_NODES'; +import testUtils, { GLOBAL } from '../test-utils'; +import CLUSTER_NODES from './CLUSTER_NODES'; describe('CLUSTER NODES', () => { - it('transformArguments', () => { - assert.deepEqual( - transformArguments(), - ['CLUSTER', 'NODES'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLUSTER_NODES.transformArguments(), + ['CLUSTER', 'NODES'] + ); + }); - describe('transformReply', () => { - it('simple', () => { - assert.deepEqual( - transformReply([ - 'master 127.0.0.1:30001@31001 myself,master - 0 0 1 connected 0-16384', - 'slave 127.0.0.1:30002@31002 slave master 0 0 1 connected', - '' - ].join('\n')), - [{ - id: 'master', - address: '127.0.0.1:30001@31001', - host: '127.0.0.1', - port: 30001, - cport: 31001, - flags: ['myself', 'master'], - pingSent: 0, - pongRecv: 0, - configEpoch: 1, - linkState: RedisClusterNodeLinkStates.CONNECTED, - slots: [{ - from: 0, - to: 16384 - }], - replicas: [{ - id: 'slave', - address: '127.0.0.1:30002@31002', - host: '127.0.0.1', - port: 30002, - cport: 31002, - flags: ['slave'], - pingSent: 0, - pongRecv: 0, - configEpoch: 1, - linkState: RedisClusterNodeLinkStates.CONNECTED - }] - }] - ); - }); - - it('should support addresses without cport', () => { - assert.deepEqual( - transformReply( - 'id 127.0.0.1:30001 master - 0 0 0 connected 0-16384\n' - ), - [{ - id: 'id', - address: '127.0.0.1:30001', - host: '127.0.0.1', - port: 30001, - cport: null, - flags: ['master'], - pingSent: 0, - pongRecv: 0, - configEpoch: 0, - linkState: RedisClusterNodeLinkStates.CONNECTED, - slots: [{ - from: 0, - to: 16384 - }], - replicas: [] - }] - ); - }); - - it('should support ipv6 addresses', () => { - assert.deepEqual( - transformReply( - 'id 2a02:6b8:c21:330d:0:1589:ebbe:b1a0:6379@16379 master - 0 0 0 connected 0-549\n' - ), - [{ - id: 'id', - address: '2a02:6b8:c21:330d:0:1589:ebbe:b1a0:6379@16379', - host: '2a02:6b8:c21:330d:0:1589:ebbe:b1a0', - port: 6379, - cport: 16379, - flags: ['master'], - pingSent: 0, - pongRecv: 0, - configEpoch: 0, - linkState: RedisClusterNodeLinkStates.CONNECTED, - slots: [{ - from: 0, - to: 549 - }], - replicas: [] - }] - ); - }); - - it.skip('with importing slots', () => { - assert.deepEqual( - transformReply( - 'id 127.0.0.1:30001@31001 master - 0 0 0 connected 0-<-16384\n' - ), - [{ - id: 'id', - address: '127.0.0.1:30001@31001', - host: '127.0.0.1', - port: 30001, - cport: 31001, - flags: ['master'], - pingSent: 0, - pongRecv: 0, - configEpoch: 0, - linkState: RedisClusterNodeLinkStates.CONNECTED, - slots: [], // TODO - replicas: [] - }] - ); - }); - - it.skip('with migrating slots', () => { - assert.deepEqual( - transformReply( - 'id 127.0.0.1:30001@31001 master - 0 0 0 connected 0->-16384\n' - ), - [{ - id: 'id', - address: '127.0.0.1:30001@31001', - host: '127.0.0.1', - port: 30001, - cport: 31001, - flags: ['master'], - pingSent: 0, - pongRecv: 0, - configEpoch: 0, - linkState: RedisClusterNodeLinkStates.CONNECTED, - slots: [], // TODO - replicas: [] - }] - ); - }); - }); + testUtils.testWithCluster('clusterNode.clusterNodes', async cluster => { + const client = await cluster.nodeClient(cluster.masters[0]); + assert.equal( + typeof await client.clusterNodes(), + 'string' + ); + }, GLOBAL.CLUSTERS.OPEN); }); diff --git a/packages/client/lib/commands/CLUSTER_NODES.ts b/packages/client/lib/commands/CLUSTER_NODES.ts index 7c433da5f1..9166ce52f0 100644 --- a/packages/client/lib/commands/CLUSTER_NODES.ts +++ b/packages/client/lib/commands/CLUSTER_NODES.ts @@ -1,105 +1,10 @@ -export function transformArguments(): Array { +import { VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types'; + +export default { + FIRST_KEY_INDEX: undefined, + IS_READ_ONLY: true, + transformArguments() { return ['CLUSTER', 'NODES']; -} - -export enum RedisClusterNodeLinkStates { - CONNECTED = 'connected', - DISCONNECTED = 'disconnected' -} - -interface RedisClusterNodeAddress { - host: string; - port: number; - cport: number | null; -} - -export interface RedisClusterReplicaNode extends RedisClusterNodeAddress { - id: string; - address: string; - flags: Array; - pingSent: number; - pongRecv: number; - configEpoch: number; - linkState: RedisClusterNodeLinkStates; -} - -export interface RedisClusterMasterNode extends RedisClusterReplicaNode { - slots: Array<{ - from: number; - to: number; - }>; - replicas: Array; -} - -export function transformReply(reply: string): Array { - const lines = reply.split('\n'); - lines.pop(); // last line is empty - - const mastersMap = new Map(), - replicasMap = new Map>(); - - for (const line of lines) { - const [id, address, flags, masterId, pingSent, pongRecv, configEpoch, linkState, ...slots] = line.split(' '), - node = { - id, - address, - ...transformNodeAddress(address), - flags: flags.split(','), - pingSent: Number(pingSent), - pongRecv: Number(pongRecv), - configEpoch: Number(configEpoch), - linkState: (linkState as RedisClusterNodeLinkStates) - }; - - if (masterId === '-') { - let replicas = replicasMap.get(id); - if (!replicas) { - replicas = []; - replicasMap.set(id, replicas); - } - - mastersMap.set(id, { - ...node, - slots: slots.map(slot => { - // TODO: importing & exporting (https://redis.io/commands/cluster-nodes#special-slot-entries) - const [fromString, toString] = slot.split('-', 2), - from = Number(fromString); - return { - from, - to: toString ? Number(toString) : from - }; - }), - replicas - }); - } else { - const replicas = replicasMap.get(masterId); - if (!replicas) { - replicasMap.set(masterId, [node]); - } else { - replicas.push(node); - } - } - } - - return [...mastersMap.values()]; -} - -function transformNodeAddress(address: string): RedisClusterNodeAddress { - const indexOfColon = address.lastIndexOf(':'), - indexOfAt = address.indexOf('@', indexOfColon), - host = address.substring(0, indexOfColon); - - if (indexOfAt === -1) { - return { - host, - port: Number(address.substring(indexOfColon + 1)), - cport: null - }; - } - - return { - host: address.substring(0, indexOfColon), - port: Number(address.substring(indexOfColon + 1, indexOfAt)), - cport: Number(address.substring(indexOfAt + 1)) - }; -} + }, + transformReply: undefined as unknown as () => VerbatimStringReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/CLUSTER_REPLICAS.spec.ts b/packages/client/lib/commands/CLUSTER_REPLICAS.spec.ts index 6c902dc0d8..18b9fed5e4 100644 --- a/packages/client/lib/commands/CLUSTER_REPLICAS.spec.ts +++ b/packages/client/lib/commands/CLUSTER_REPLICAS.spec.ts @@ -1,11 +1,20 @@ import { strict as assert } from 'assert'; -import { transformArguments } from './CLUSTER_REPLICAS'; +import testUtils, { GLOBAL } from '../test-utils'; +import CLUSTER_REPLICAS from './CLUSTER_REPLICAS'; describe('CLUSTER REPLICAS', () => { - it('transformArguments', () => { - assert.deepEqual( - transformArguments('0'), - ['CLUSTER', 'REPLICAS', '0'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLUSTER_REPLICAS.transformArguments('0'), + ['CLUSTER', 'REPLICAS', '0'] + ); + }); + + testUtils.testWithCluster('clusterNode.clusterReplicas', async cluster => { + const client = await cluster.nodeClient(cluster.masters[0]); + assert.equal( + typeof await client.clusterReplicas(cluster.masters[0].id), + 'string' + ); + }, GLOBAL.CLUSTERS.OPEN); }); diff --git a/packages/client/lib/commands/CLUSTER_REPLICAS.ts b/packages/client/lib/commands/CLUSTER_REPLICAS.ts index a4130125fb..7cb0eaae43 100644 --- a/packages/client/lib/commands/CLUSTER_REPLICAS.ts +++ b/packages/client/lib/commands/CLUSTER_REPLICAS.ts @@ -1,5 +1,10 @@ -export function transformArguments(nodeId: string): Array { - return ['CLUSTER', 'REPLICAS', nodeId]; -} +import { RedisArgument, VerbatimStringReply, Command } from '@redis/client/dist/lib/RESP/types'; -export { transformReply } from './CLUSTER_NODES'; +export default { + FIRST_KEY_INDEX: undefined, + IS_READ_ONLY: true, + transformArguments(nodeId: RedisArgument) { + return ['CLUSTER', 'REPLICAS', nodeId]; + }, + transformReply: undefined as unknown as () => VerbatimStringReply +} as const satisfies Command; diff --git a/packages/client/lib/commands/index.ts b/packages/client/lib/commands/index.ts index d8eb66b66a..ab3711ab3a 100644 --- a/packages/client/lib/commands/index.ts +++ b/packages/client/lib/commands/index.ts @@ -54,13 +54,13 @@ import CLUSTER_FAILOVER from './CLUSTER_FAILOVER'; import CLUSTER_FLUSHSLOTS from './CLUSTER_FLUSHSLOTS'; import CLUSTER_FORGET from './CLUSTER_FORGET'; import CLUSTER_GETKEYSINSLOT from './CLUSTER_GETKEYSINSLOT'; -// import CLUSTER_INFO from './CLUSTER_INFO'; +import CLUSTER_INFO from './CLUSTER_INFO'; import CLUSTER_KEYSLOT from './CLUSTER_KEYSLOT'; import CLUSTER_LINKS from './CLUSTER_LINKS'; import CLUSTER_MEET from './CLUSTER_MEET'; import CLUSTER_MYID from './CLUSTER_MYID'; -// import CLUSTER_NODES from './CLUSTER_NODES'; -// import CLUSTER_REPLICAS from './CLUSTER_REPLICAS'; +import CLUSTER_NODES from './CLUSTER_NODES'; +import CLUSTER_REPLICAS from './CLUSTER_REPLICAS'; import CLUSTER_REPLICATE from './CLUSTER_REPLICATE'; import CLUSTER_RESET from './CLUSTER_RESET'; import CLUSTER_SAVECONFIG from './CLUSTER_SAVECONFIG'; @@ -437,8 +437,8 @@ export default { clusterForget: CLUSTER_FORGET, CLUSTER_GETKEYSINSLOT, clusterGetKeysInSlot: CLUSTER_GETKEYSINSLOT, - // CLUSTER_INFO, - // clusterInfo: CLUSTER_INFO, + CLUSTER_INFO, + clusterInfo: CLUSTER_INFO, CLUSTER_KEYSLOT, clusterKeySlot: CLUSTER_KEYSLOT, CLUSTER_LINKS, @@ -447,10 +447,10 @@ export default { clusterMeet: CLUSTER_MEET, CLUSTER_MYID, clusterMyId: CLUSTER_MYID, - // CLUSTER_NODES, - // clusterNodes: CLUSTER_NODES, - // CLUSTER_REPLICAS, - // clusterReplicas: CLUSTER_REPLICAS, + CLUSTER_NODES, + clusterNodes: CLUSTER_NODES, + CLUSTER_REPLICAS, + clusterReplicas: CLUSTER_REPLICAS, CLUSTER_REPLICATE, clusterReplicate: CLUSTER_REPLICATE, CLUSTER_RESET,