Skip to content

Commit 3dbfcf8

Browse files
committed
feat(python): add CLUSTER_INFO INFO section and test Valkey 9.2 INFO fields
Add `InfoSection.CLUSTER_INFO` for the `cluster_info` INFO section (Valkey 9.1.0+), which reports the same fields as `CLUSTER INFO`, and sort the `InfoSection` members alphabetically. Extend `test_info_default` and `test_sync_info_default`: - Default INFO contains one field from each default section. - Valkey 9.2.0+ only: - INFO persistence: `rdb_current_bgsave_type` and `rdb_last_bgsave_type` are one of fork, forkless, or none, and `forkless_estimated_seconds_remaining` is at least -1. - INFO cluster_info (cluster mode only): `cluster_io_main_thread_fallbacks` is at least 0. Add `get_info` and `sync_get_info` test helpers that return the parsed INFO fields, sending the command to a random node for cluster clients. `parse_info_response` becomes the private `_parse_info_response`, and now splits each line on the first colon only. Add the same `getInfo` test helper to Java and Node, replacing `parseInfoResponseToMap` in Java and the `parseInfoResponse` test usages in Node. Part of #7199. Signed-off-by: currantw <taylor.curran@improving.com>
1 parent 6e49a1d commit 3dbfcf8

9 files changed

Lines changed: 272 additions & 112 deletions

File tree

‎java/integTest/src/test/java/glide/TestUtilities.java‎

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -227,22 +227,21 @@ public static String getRandomString(int length) {
227227
}
228228

229229
/**
230-
* Asserts that value is an integer and returns it.
230+
* Returns the fields and values from the given INFO sections.
231+
* <p>For cluster clients, the command is sent to a random node.
231232
*
232-
* @param value The value to parse.
233-
* @param name The name that identifies the value in the failure message.
233+
* @param client A client that is connected to the server.
234+
* @param sections The INFO sections to request. Defaults to the default sections.
235+
* @return A map from each INFO field name to its value.
234236
*/
235-
public static long parseLong(String value, String name) {
236-
return assertDoesNotThrow(
237-
() -> Long.parseLong(value), name + " is not an integer: \"" + value + "\"");
238-
}
237+
@SneakyThrows
238+
public static Map<String, String> getInfo(BaseClient client, Section... sections) {
239+
String info =
240+
client instanceof GlideClusterClient
241+
? ((GlideClusterClient) client).info(sections, RANDOM).get().getSingleValue()
242+
: ((GlideClient) client).info(sections).get();
239243

240-
/**
241-
* Transforms server info string into a Map, using lines with ":" to create key-value pairs,
242-
* replacing duplicates with the last encountered value.
243-
*/
244-
public static Map<String, String> parseInfoResponseToMap(String serverInfo) {
245-
return Arrays.stream(serverInfo.split("\n"))
244+
return Arrays.stream(info.split("\n"))
246245
.filter(line -> line.contains(":"))
247246
.map(line -> line.split(":", 2))
248247
.collect(
@@ -253,6 +252,17 @@ public static Map<String, String> parseInfoResponseToMap(String serverInfo) {
253252
LinkedHashMap::new));
254253
}
255254

255+
/**
256+
* Asserts that value is an integer and returns it.
257+
*
258+
* @param value The value to parse.
259+
* @param name The name that identifies the value in the failure message.
260+
*/
261+
public static long parseLong(String value, String name) {
262+
return assertDoesNotThrow(
263+
() -> Long.parseLong(value), name + " is not an integer: \"" + value + "\"");
264+
}
265+
256266
// copied from glide.utils.ArrayTransformUtils.concatenateArrays, because it is not exported
257267
/**
258268
* Concatenates multiple arrays of type T and returns a single concatenated array.
@@ -687,14 +697,7 @@ public static void waitForNotBusy(Supplier<CompletableFuture<?>> lambda) {
687697
*/
688698
@SneakyThrows
689699
public static String getServerVersion(@NonNull final BaseClient client) {
690-
String infoResponse =
691-
client instanceof GlideClient
692-
? ((GlideClient) client).info(new Section[] {Section.SERVER}).get()
693-
: ((GlideClusterClient) client)
694-
.info(new Section[] {Section.SERVER}, RANDOM)
695-
.get()
696-
.getSingleValue();
697-
Map<String, String> infoResponseMap = parseInfoResponseToMap(infoResponse);
700+
Map<String, String> infoResponseMap = getInfo(client, Section.SERVER);
698701
if (infoResponseMap.containsKey(VALKEY_VERSION_KEY)) {
699702
return infoResponseMap.get(VALKEY_VERSION_KEY);
700703
} else if (infoResponseMap.containsKey(REDIS_VERSION_KEY)) {

‎java/integTest/src/test/java/glide/cluster/CommandTests.java‎

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@
2323
import static glide.TestUtilities.generateLuaLibCodeBinary;
2424
import static glide.TestUtilities.getFirstEntryFromMultiValue;
2525
import static glide.TestUtilities.getFirstKeyFromMultiValue;
26+
import static glide.TestUtilities.getInfo;
2627
import static glide.TestUtilities.getReplicaCount;
2728
import static glide.TestUtilities.getUnixSeconds;
2829
import static glide.TestUtilities.getValueFromInfo;
2930
import static glide.TestUtilities.isWindows;
30-
import static glide.TestUtilities.parseInfoResponseToMap;
3131
import static glide.TestUtilities.parseLong;
3232
import static glide.TestUtilities.waitFor;
3333
import static glide.TestUtilities.waitForNotBusy;
@@ -501,9 +501,7 @@ public void info_valkey_9_2_fields(GlideClusterClient clusterClient) {
501501
assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("9.2.0"), "This feature added in version 9.2");
502502

503503
// Persistence fields
504-
Map<String, String> persistence =
505-
parseInfoResponseToMap(
506-
clusterClient.info(new Section[] {PERSISTENCE}, RANDOM).get().getSingleValue());
504+
Map<String, String> persistence = getInfo(clusterClient, PERSISTENCE);
507505

508506
assertTrue(BGSAVE_TYPES.contains(persistence.get("rdb_current_bgsave_type")));
509507
assertTrue(BGSAVE_TYPES.contains(persistence.get("rdb_last_bgsave_type")));
@@ -513,9 +511,7 @@ public void info_valkey_9_2_fields(GlideClusterClient clusterClient) {
513511
assertTrue(remainingValue >= -1);
514512

515513
// Cluster info fields
516-
Map<String, String> clusterInfo =
517-
parseInfoResponseToMap(
518-
clusterClient.info(new Section[] {CLUSTER_INFO}, RANDOM).get().getSingleValue());
514+
Map<String, String> clusterInfo = getInfo(clusterClient, CLUSTER_INFO);
519515

520516
String fallbacksName = "cluster_io_main_thread_fallbacks";
521517
long fallbacksValue = parseLong(clusterInfo.get(fallbacksName), fallbacksName);
@@ -723,8 +719,7 @@ public void config_reset_stat(GlideClusterClient clusterClient) {
723719
@MethodSource("getClients")
724720
@SneakyThrows
725721
public void config_rewrite_non_existent_config_file(GlideClusterClient clusterClient) {
726-
ClusterValue<String> info = clusterClient.info(new Section[] {SERVER}, RANDOM).get();
727-
String configFile = parseInfoResponseToMap(info.getSingleValue()).get("config_file");
722+
String configFile = getInfo(clusterClient, SERVER).get("config_file");
728723

729724
if (configFile == null || configFile.isEmpty()) {
730725
ExecutionException executionException =

‎java/integTest/src/test/java/glide/standalone/CommandTests.java‎

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
import static glide.TestUtilities.createLuaLibWithLongRunningFunction;
2020
import static glide.TestUtilities.generateLuaLibCode;
2121
import static glide.TestUtilities.generateLuaLibCodeBinary;
22+
import static glide.TestUtilities.getInfo;
2223
import static glide.TestUtilities.getUnixSeconds;
2324
import static glide.TestUtilities.getValueFromInfo;
24-
import static glide.TestUtilities.parseInfoResponseToMap;
2525
import static glide.TestUtilities.parseLong;
2626
import static glide.TestUtilities.waitFor;
2727
import static glide.TestUtilities.waitForNotBusy;
@@ -250,8 +250,7 @@ public void info_with_everything_option(GlideClient regularClient) {
250250
public void info_valkey_9_2_fields(GlideClient regularClient) {
251251
assumeTrue(SERVER_VERSION.isGreaterThanOrEqualTo("9.2.0"), "This feature added in version 9.2");
252252

253-
Map<String, String> persistence =
254-
parseInfoResponseToMap(regularClient.info(new Section[] {PERSISTENCE}).get());
253+
Map<String, String> persistence = getInfo(regularClient, PERSISTENCE);
255254

256255
assertTrue(BGSAVE_TYPES.contains(persistence.get("rdb_current_bgsave_type")));
257256
assertTrue(BGSAVE_TYPES.contains(persistence.get("rdb_last_bgsave_type")));
@@ -438,8 +437,7 @@ public void config_reset_stat(GlideClient regularClient) {
438437
@MethodSource("getClients")
439438
@SneakyThrows
440439
public void config_rewrite_non_existent_config_file(GlideClient regularClient) {
441-
String info = regularClient.info(new Section[] {SERVER}).get();
442-
String configFile = parseInfoResponseToMap(info).get("config_file");
440+
String configFile = getInfo(regularClient, SERVER).get("config_file");
443441

444442
if (configFile.isEmpty()) {
445443
ExecutionException executionException =

‎node/tests/SharedTests.ts‎

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ import {
5959
convertElementsAndScores,
6060
convertGlideRecordToRecord,
6161
MemoryStats,
62-
parseInfoResponse,
6362
} from "../build-ts";
6463
import {
6564
BGSAVE_TYPES,
@@ -69,6 +68,7 @@ import {
6968
flattenClusterResponseArrays,
7069
GetAndSetRandomValue,
7170
getFirstResult,
71+
getInfo,
7272
getRandomKey,
7373
getUnixSeconds,
7474
INFO_DEFAULT_FIELDS,
@@ -325,10 +325,8 @@ export function runBaseTests(config: {
325325
`test config rewrite_%p`,
326326
async (protocol) => {
327327
await runTest(async (client: BaseClient) => {
328-
const serverInfo = await client.info([InfoOptions.Server]);
329-
const conf_file = parseInfoResponse(
330-
getFirstResult(serverInfo).toString(),
331-
)["config_file"];
328+
const info = await getInfo(client, [InfoOptions.Server]);
329+
const conf_file = info["config_file"];
332330

333331
if (conf_file.length > 0) {
334332
expect(await client.configRewrite()).toEqual("OK");
@@ -380,18 +378,8 @@ export function runBaseTests(config: {
380378
async (protocol) => {
381379
await runTest(
382380
async (client: BaseClient, cluster: ValkeyCluster) => {
383-
const getInfo = async (sections?: InfoOptions[]) =>
384-
parseInfoResponse(
385-
getFirstResult(
386-
client instanceof GlideClient
387-
? await client.info(sections)
388-
: await client.info({ sections }),
389-
).toString(),
390-
);
391-
392381
// Default fields
393-
const info = await getInfo();
394-
382+
const info = await getInfo(client);
395383
for (const field of INFO_DEFAULT_FIELDS) {
396384
expect(info).toHaveProperty(field);
397385
}
@@ -404,7 +392,7 @@ export function runBaseTests(config: {
404392
}
405393

406394
// Persistence fields
407-
const persistence = await getInfo([
395+
const persistence = await getInfo(client, [
408396
InfoOptions.Persistence,
409397
]);
410398

@@ -425,7 +413,7 @@ export function runBaseTests(config: {
425413

426414
// Cluster info fields
427415
if (client instanceof GlideClusterClient) {
428-
const clusterInfo = await getInfo([
416+
const clusterInfo = await getInfo(client, [
429417
InfoOptions.ClusterInfo,
430418
]);
431419

‎node/tests/TestUtilities.ts‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2801,6 +2801,40 @@ export function assertMemoryStatsFields(
28012801
}
28022802
}
28032803

2804+
/**
2805+
* Returns the fields and values from the given INFO sections.
2806+
* For cluster clients, the command is sent to a random node.
2807+
*
2808+
* @param client - A client that is connected to the server.
2809+
* @param sections - The INFO sections to request. Defaults to the default sections.
2810+
* @returns A record mapping each INFO field name to its value.
2811+
*/
2812+
export async function getInfo(
2813+
client: BaseClient,
2814+
sections?: InfoOptions[],
2815+
): Promise<Record<string, string>> {
2816+
const info =
2817+
client instanceof GlideClusterClient
2818+
? await client.info({ sections, route: "randomNode" })
2819+
: await (client as GlideClient).info(sections);
2820+
2821+
const fields: Record<string, string> = {};
2822+
2823+
for (const line of info.toString().split("\n")) {
2824+
const trimmed = line.trim();
2825+
if (trimmed === "" || trimmed.startsWith("#")) {
2826+
continue;
2827+
}
2828+
2829+
const separator = trimmed.indexOf(":");
2830+
if (separator !== -1) {
2831+
fields[trimmed.slice(0, separator)] = trimmed.slice(separator + 1);
2832+
}
2833+
}
2834+
2835+
return fields;
2836+
}
2837+
28042838
/**
28052839
* Asserts that value is an integer and returns it.
28062840
*

‎python/glide-shared/glide_shared/commands/core_options.py‎

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -142,56 +142,63 @@ class InfoSection(Enum):
142142
When no parameter is provided, the default option is assumed.
143143
"""
144144

145-
SERVER = "server"
146-
""" General information about the server """
145+
ALL = "all"
146+
""" Return all sections (excluding module generated ones) """
147147

148148
CLIENTS = "clients"
149149
""" Client connections section """
150150

151-
MEMORY = "memory"
152-
""" Memory consumption related information """
151+
CLUSTER = "cluster"
152+
""" Valkey Cluster section """
153153

154-
PERSISTENCE = "persistence"
155-
""" RDB and AOF related information """
154+
CLUSTER_INFO = "cluster_info"
155+
"""
156+
Valkey Cluster information.
156157
157-
STATS = "stats"
158-
""" General statistics """
158+
Since: Valkey version 9.1.0.
159+
"""
159160

160-
REPLICATION = "replication"
161-
""" Master/replica replication information """
161+
COMMAND_STATS = "commandstats"
162+
""" Valkey command statistics """
162163

163164
CPU = "cpu"
164165
""" CPU consumption statistics """
165166

166-
COMMAND_STATS = "commandstats"
167-
""" Valkey command statistics """
167+
DEFAULT = "default"
168+
""" Return only the default set of sections """
169+
170+
ERROR_STATS = "errorstats"
171+
""" Valkey error statistics """
172+
173+
EVERYTHING = "everything"
174+
""" Includes all and modules """
175+
176+
KEYSPACE = "keyspace"
177+
""" Database related statistics """
168178

169179
LATENCY_STATS = "latencystats"
170180
""" Valkey command latency percentile distribution statistics """
171181

172-
SENTINEL = "sentinel"
173-
""" Valkey Sentinel section (only applicable to Sentinel instances) """
174-
175-
CLUSTER = "cluster"
176-
""" Valkey Cluster section """
182+
MEMORY = "memory"
183+
""" Memory consumption related information """
177184

178185
MODULES = "modules"
179186
""" Modules section """
180187

181-
KEYSPACE = "keyspace"
182-
""" Database related statistics """
188+
PERSISTENCE = "persistence"
189+
""" RDB and AOF related information """
183190

184-
ERROR_STATS = "errorstats"
185-
""" Valkey error statistics """
191+
REPLICATION = "replication"
192+
""" Master/replica replication information """
186193

187-
ALL = "all"
188-
""" Return all sections (excluding module generated ones) """
194+
SENTINEL = "sentinel"
195+
""" Valkey Sentinel section (only applicable to Sentinel instances) """
189196

190-
DEFAULT = "default"
191-
""" Return only the default set of sections """
197+
SERVER = "server"
198+
""" General information about the server """
192199

193-
EVERYTHING = "everything"
194-
""" Includes all and modules """
200+
STATS = "stats"
201+
""" General statistics """
195202

196203

197204
class ExpireOptions(Enum):

0 commit comments

Comments
 (0)