From 9d8825c88c464635b0830daaaf983ff6ac90ccb9 Mon Sep 17 00:00:00 2001 From: Siddarth Raj Date: Tue, 2 Jun 2026 02:02:09 +0530 Subject: [PATCH] DAOS-18922: Unit test improvements for DAOS placement algorithm --- src/placement/tests/SConscript | 3 + src/placement/tests/layout_test.c | 324 ++++++++++++++++++++++ src/placement/tests/layout_test_helpers.c | 260 +++++++++++++++++ src/placement/tests/layout_test_helpers.h | 74 +++++ 4 files changed, 661 insertions(+) create mode 100644 src/placement/tests/layout_test.c create mode 100644 src/placement/tests/layout_test_helpers.c create mode 100644 src/placement/tests/layout_test_helpers.h diff --git a/src/placement/tests/SConscript b/src/placement/tests/SConscript index b60893f268f..f4a4a29ce90 100644 --- a/src/placement/tests/SConscript +++ b/src/placement/tests/SConscript @@ -25,6 +25,7 @@ def scons(): 'jump_map_dist.c', 'placement_test.c']) pl_bench_tgt = denv.SharedObject(['pl_bench.c', 'place_obj_common.c']) + layout_test_tgt = denv.SharedObject(['layout_test.c', 'layout_test_helpers.c']) libraries = ['daos', 'daos_common', 'gurt', 'uuid', 'cmocka', 'isal', 'm'] @@ -34,10 +35,12 @@ def scons(): jump_test_tgt + ['../../pool/srv_pool_map.c'], LIBS=libraries) pl_bench = denv.d_program('pl_bench', pl_bench_tgt, LIBS=libraries) + layout_test = denv.d_program('layout_test', layout_test_tgt, LIBS=libraries) denv.Install('$PREFIX/bin/', ring_pl_test) denv.Install('$PREFIX/bin/', jump_pl_test) denv.Install('$PREFIX/bin/', pl_bench) + denv.Install('$PREFIX/bin/', layout_test) if __name__ == "SCons.Script": diff --git a/src/placement/tests/layout_test.c b/src/placement/tests/layout_test.c new file mode 100644 index 00000000000..bc45a54b644 --- /dev/null +++ b/src/placement/tests/layout_test.c @@ -0,0 +1,324 @@ +/** + * (C) Copyright 2026 Hewlett Packard Enterprise Development LP + * + * SPDX-License-Identifier: BSD-2-Clause-Patent + */ + +#include +#include +#include +#include +#include + +#include "layout_test_helpers.h" + +static void +print_usage(const char *prog) +{ + printf("Usage: %s [options]\n", prog); + printf("\nOptions:\n"); + printf(" --nodes N\n"); + printf(" --ranks N\n"); + printf(" --targets N\n"); + printf(" --class CLASS\n"); + printf(" --obj-count N\n"); + printf(" --operation \"add_node[1] exclude_rank[0,1]\"\n"); + printf(" --help\n"); +} + + +static enum operation_type +get_operation_type(const char *name) +{ + struct op_map op_table[] = { + { "add_node", OP_ADD_NODE }, + { "exclude_node", OP_EXCLUDE_NODE }, + { "exclude_rank", OP_EXCLUDE_RANK }, + { "reintegrate_rank", OP_REINTEGRATE_RANK }, + }; + size_t i; + for (i = 0; i < sizeof(op_table) / sizeof(op_table[0]); i++) { + if (strcmp(name, op_table[i].name) == 0) + return op_table[i].type; + } + return OP_INVALID; +} + +static const char * +operation_name(enum operation_type type) +{ + switch (type) { + case OP_ADD_NODE: + return "ADD_NODE"; + case OP_EXCLUDE_NODE: + return "EXCLUDE_NODE"; + case OP_EXCLUDE_RANK: + return "EXCLUDE_RANK"; + case OP_REINTEGRATE_RANK: + return "REINTEGRATE_RANK"; + default: + return "INVALID"; + } +} + +static int +parse_operations(char *op_string, + struct operation ops[], + int *op_count) +{ + char *token; + char *saveptr = NULL; + + token = strtok_r(op_string, " ", &saveptr); + + while (token != NULL) { + + char op_name[64] = {0}; + char args[64] = {0}; + enum operation_type type; + + if (*op_count >= MAX_OPERATIONS) { + fprintf(stderr, "Too many operations (max=%d)\n", MAX_OPERATIONS); + return -1; + } + + if (sscanf(token,"%63[^[][%63[^]]", op_name, args) != 2) { + fprintf(stderr, "Invalid operation format: %s\n", token); + return -1; + } + + type = get_operation_type(op_name); + if (type == OP_INVALID) { + fprintf(stderr, "Unsupported operation: %s\n", op_name); + return -1; + } + + ops[*op_count].type = type; + snprintf(ops[*op_count].args, sizeof(ops[*op_count].args), "%s", args); + (*op_count)++; + token = strtok_r(NULL, " ", &saveptr); + } + return 0; +} + +static void +execute_operation(struct operation *op) +{ + switch (op->type) { + case OP_ADD_NODE: + printf("Applying add_node[%s]\n", op->args); + /* add node implementation */ + break; + + case OP_EXCLUDE_NODE: + printf("Applying exclude_node[%s]\n", op->args); + /* exclude node implementation */ + break; + + case OP_EXCLUDE_RANK: + printf("Applying exclude_rank[%s]\n", op->args); + /* exclude rank implementation */ + break; + + case OP_REINTEGRATE_RANK: + printf("Applying reintegrate_rank[%s]\n", op->args); + /* reintegrate rank implementation */ + break; + + default: + printf("Invalid operation type\n"); + break; + } +} + +static int +layout_test_runner(struct test_ctx *ctx, struct operation *operations, int operation_count, struct test_oid *oids) +{ + int rc; + struct oid_layout *layouts; + + D_ALLOC_ARRAY(layouts, ctx->num_oids); + if (layouts == NULL) + return -DER_NOMEM; + + rc = capture_layouts(ctx, oids, layouts); + if (rc != 0) { + printf("capture_layouts failed rc=%d\n", rc); + D_FREE(layouts); + return rc; + } + + for (int i = 0; i < operation_count; i++) { + printf("\n=== Operation %d ===\n", i + 1); + execute_operation(&operations[i]); + } + /* The actual test logic would go here, including layout validation after each operation */ + + D_FREE(layouts); + return 0; +} + +int +main(int argc, char **argv) +{ + uint32_t nodes = DEFAULT_NODES; + uint32_t ranks = DEFAULT_RANKS; + uint32_t targets = DEFAULT_TARGETS; + uint32_t obj_count = DEFAULT_OBJ_COUNT; + + char *object_class_str = DEFAULT_OBJ_CLASS; + + struct operation operations[MAX_OPERATIONS]; + int operation_count = 0; + + while (1) { + + static struct option long_options[] = { + {"nodes", required_argument, 0, 'n'}, + {"ranks", required_argument, 0, 'r'}, + {"targets", required_argument, 0, 't'}, + {"class", required_argument, 0, 'c'}, + {"obj-count", required_argument, 0, 'o'}, + {"operation", required_argument, 0, 'p'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int ret; + int opt; + + opt = getopt_long(argc, argv, "n:r:t:c:o:p:h", long_options, NULL); + if (opt == -1) + break; + + switch (opt) { + case 'n': + ret = sscanf(optarg, "%u", &nodes); + if (ret != 1) + printf("Invalid node count, using default\n"); + break; + + case 'r': + ret = sscanf(optarg, "%u", &ranks); + if (ret != 1) + printf("Invalid rank count, using default\n"); + break; + + case 't': + ret = sscanf(optarg, "%u", &targets); + if (ret != 1) + printf("Invalid target count, using default\n"); + break; + + case 'c': + object_class_str = optarg; + break; + + case 'o': + ret = sscanf(optarg, "%u", &obj_count); + if (ret != 1) + printf("Invalid object count, using default\n"); + break; + + case 'p': { + char op_buf[1024]; + strncpy(op_buf, optarg, sizeof(op_buf) - 1); + op_buf[sizeof(op_buf) - 1] = '\0'; + if (parse_operations(op_buf, operations, &operation_count) != 0) + return EXIT_FAILURE; + break; + } + + case 'h': + print_usage(argv[0]); + return 0; + default: + print_usage(argv[0]); + return EXIT_FAILURE; + } + } + + obj_class_init(); + + // Validate the initial configuration before running any operations + int ret = validate_configuration(nodes, ranks, targets, object_class_str, operations, operation_count); + if (ret != 0) { + obj_class_fini(); + return EXIT_FAILURE; + } + + // Print the initial configuration and planned operations + printf("Running layout test with the following configuration:\n"); + printf("Nodes : %u\n", nodes); + printf("Ranks per node : %u\n", ranks); + printf("Targets per rank : %u\n", targets); + printf("Object class : %s\n", object_class_str); + printf("Object count : %u\n", obj_count); + printf("\nOperations (%d)\n", operation_count); + for (int i = 0; i < operation_count; i++) { + printf(" %d. %s [%s]\n", i + 1, operation_name(operations[i].type), operations[i].args); + } + + struct test_ctx ctx = { + .nodes = nodes, + .ranks_per_node = ranks, + .targets_per_rank = targets, + .num_oids = obj_count, + .oclass = daos_oclass_name2id(object_class_str), + .pool_map = NULL, + .pl_map = NULL + }; + + int rc; + struct test_oid *oids; + + D_ALLOC_ARRAY(oids, obj_count); + if (oids == NULL) { + printf("D_ALLOC_ARRAY oids failed\n"); + obj_class_fini(); + cleanup(&ctx); + return EXIT_FAILURE; + } + + rc = pool_map_init(&ctx); + if (rc != 0) { + printf("pool_map_init failed rc=%d\n", rc); + D_FREE(oids); + obj_class_fini(); + cleanup(&ctx); + return EXIT_FAILURE; + } + rc = placement_map_init(&ctx); + if (rc != 0) { + printf("placement_map_init failed rc=%d\n", rc); + D_FREE(oids); + obj_class_fini(); + cleanup(&ctx); + return EXIT_FAILURE; + } + + rc = generate_oids(&ctx, oids); + if (rc != 0) { + printf("generate_oids failed rc=%d\n", rc); + D_FREE(oids); + obj_class_fini(); + cleanup(&ctx); + return EXIT_FAILURE; + } + + rc = layout_test_runner(&ctx, operations, operation_count, oids); + if (rc != 0) { + printf("layout_test_runner failed rc=%d\n", rc); + D_FREE(oids); + obj_class_fini(); + cleanup(&ctx); + return EXIT_FAILURE; + } + + /* Cleanup and finalize */ + + D_FREE(oids); + obj_class_fini(); + cleanup(&ctx); + return 0; +} diff --git a/src/placement/tests/layout_test_helpers.c b/src/placement/tests/layout_test_helpers.c new file mode 100644 index 00000000000..2a5561aa317 --- /dev/null +++ b/src/placement/tests/layout_test_helpers.c @@ -0,0 +1,260 @@ +/** + * (C) Copyright 2026 Hewlett Packard Enterprise Development LP + * + * SPDX-License-Identifier: BSD-2-Clause-Patent + */ + +#include "layout_test_helpers.h" + +int pool_map_init(struct test_ctx *ctx) +{ + struct pool_buf *buf; + struct pool_component *comps; + struct pool_component *comp; + + int total_ranks; + int total_targets; + int total_components; + int i; + int rc; + + total_ranks = ctx->nodes * ctx->ranks_per_node; + total_targets = total_ranks * ctx->targets_per_rank; + total_components = ctx->nodes + total_ranks + total_targets; + + buf = pool_buf_alloc(total_components); + if (buf == NULL) + return -DER_NOMEM; + + D_ALLOC_ARRAY(comps, total_components); + if (comps == NULL) { + D_FREE(buf); + return -DER_NOMEM; + } + + comp = &comps[0]; + + for (i = 0; i < ctx->nodes; i++, comp++) { + comp->co_type = PO_COMP_TP_NODE; + comp->co_status = PO_COMP_ST_UPIN; + comp->co_id = i; + comp->co_rank = 0; + comp->co_ver = 1; + comp->co_nr = ctx->ranks_per_node; + } + + for (i = 0; i < total_ranks; i++, comp++) { + comp->co_type = PO_COMP_TP_RANK; + comp->co_status = PO_COMP_ST_UPIN; + comp->co_id = i; + comp->co_rank = i; + comp->co_ver = 1; + comp->co_nr = ctx->targets_per_rank; + } + + for (i = 0; i < total_targets; i++, comp++) { + comp->co_type = PO_COMP_TP_TARGET; + comp->co_status = PO_COMP_ST_UPIN; + comp->co_id = i; + comp->co_rank = i / ctx->targets_per_rank; + comp->co_index = i % ctx->targets_per_rank; + comp->co_ver = 1; + comp->co_nr = 1; + } + + rc = pool_buf_attach(buf, comps, total_components); + if (rc != 0) { + printf("pool_buf_attach failed rc=%d\n", rc); + D_FREE(comps); + D_FREE(buf); + return rc; + } + + rc = pool_map_create(buf, 1, &ctx->pool_map); + if (rc != 0) { + printf("pool_map_create failed rc=%d\n", rc); + D_FREE(comps); + D_FREE(buf); + return rc; + } + D_FREE(comps); + D_FREE(buf); + return 0; +} + +int placement_map_init(struct test_ctx *ctx) +{ + int rc; + struct pl_map_init_attr mia; + + if (ctx->pool_map == NULL) { + printf("pool_map is NULL\n"); + return -DER_INVAL; + } + mia.ia_type = PL_TYPE_JUMP_MAP; + mia.ia_jump_map.domain = PO_COMP_TP_NODE; + rc = pl_map_create(ctx->pool_map, &mia, &ctx->pl_map); + if (rc != 0) { + printf("pl_map_create failed rc=%d\n", rc); + return rc; + } + return 0; +} + +int generate_oids(struct test_ctx *ctx, struct test_oid *oids) +{ + int i; + printf("Generating %d OIDs with class %u...\n", ctx->num_oids, ctx->oclass); + for (i = 0; i < ctx->num_oids; i++) { + oids[i].oid.hi = 0; + oids[i].oid.lo = ((uint64_t)rand() << 32) | i; + daos_obj_set_oid_by_class(&oids[i].oid, 0, ctx->oclass, 0); + } + + return 0; +} + +int capture_layouts(struct test_ctx *ctx, + struct test_oid *oids, + struct oid_layout *layouts) +{ + struct pl_obj_layout *layout; + int rc; + int i; + int j; + struct daos_obj_md md = {0}; + + printf("Capturing layouts for %d OIDs...\n", ctx->num_oids); + for (i = 0; i < ctx->num_oids; i++) { + md.omd_id = oids[i].oid; + md.omd_ver = 0; + md.omd_pda = 1; + layout = NULL; + rc = pl_obj_place(ctx->pl_map, PLT_LAYOUT_VERSION, &md, 0, NULL, &layout); + if (rc != 0 || layout == NULL) { + printf("placement failed for oid %d rc=%d\n", i, rc); + layouts[i].oid = oids[i].oid; + layouts[i].shard_nr = 0; + D_FREE(layout); + continue; + } + + layouts[i].oid = oids[i].oid; + layouts[i].shard_nr = layout->ol_nr; + + for (j = 0; j < layout->ol_nr; j++) { + layouts[i].targets[j] = layout->ol_shards[j].po_target; + } + pl_obj_layout_free(layout); + } + + return 0; +} + + +int validate_configuration(uint32_t nodes, uint32_t ranks, uint32_t targets, + const char *object_class_str, struct operation *operations, int operation_count) +{ + if (nodes == 0 || ranks == 0 || targets == 0) { + printf("ERROR: Nodes, ranks, and targets must be greater than 0\n"); + return -DER_INVAL; + } + + uint32_t total_ranks = nodes * ranks; + uint32_t total_targets = total_ranks * targets; + uint32_t required_nodes, required_targets, grp, nr_grps = 0; + + struct daos_oclass_attr *oc; + + int cid = daos_oclass_name2id(object_class_str); + if (cid == DAOS_OC_UNKNOWN) { + fprintf(stderr, "Unknown object class: %s\n", object_class_str); + return -DER_INVAL; + } + + oc = daos_oclass_id2attr(cid, &nr_grps); + if (!oc) { + D_DEBUG(DB_PL, "Unknown object class %u\n", (unsigned int)cid); + return -DER_INVAL; + } + + switch (oc->ca_resil) { + case DAOS_RES_REPL: + required_nodes = oc->u.rp.r_num; + grp = oc->ca_grp_nr; + break; + case DAOS_RES_EC: + required_nodes = oc->u.ec.e_k + oc->u.ec.e_p; + grp = oc->ca_grp_nr; + break; + default: + printf("Unsupported oclass type\n"); + return -DER_INVAL; + } + + if (nodes < required_nodes) { + fprintf(stderr, "\nERROR: Insufficient nodes for %s class\n", object_class_str); + fprintf(stderr, "Required : %u\n", required_nodes); + fprintf(stderr, "Provided : %u\n", nodes); + return -DER_INVAL; + } + + required_targets = required_nodes * grp; + if (total_targets < required_targets) { + fprintf(stderr, "\nWARNING: only %u targets available, " + "%u logical shards may be created\n", + total_targets, + required_targets); + } + if (operation_count == 0) { + fprintf(stderr, "No valid operations parsed\n"); + return -DER_INVAL; + } + + for (int i = 0; i < operation_count; i++) { + if (operations[i].type == OP_EXCLUDE_RANK || operations[i].type == OP_REINTEGRATE_RANK) { + uint32_t rank_id; + if (sscanf(operations[i].args, "%u", &rank_id) != 1) { + fprintf(stderr, "ERROR: Invalid rank ID in operation: %s\n", operations[i].args); + return -DER_INVAL; + } + if (rank_id >= total_ranks) { + fprintf(stderr, "ERROR: Rank ID %u out of range (total ranks: %u)\n", rank_id, total_ranks); + return -DER_INVAL; + } + } + if (operations[i].type == OP_EXCLUDE_NODE) { + uint32_t node_id; + if (sscanf(operations[i].args, "%u", &node_id) != 1) { + fprintf(stderr, "ERROR: Invalid node ID in operation: %s\n", operations[i].args); + return -DER_INVAL; + } + if (node_id >= nodes) { + fprintf(stderr, "ERROR: Node ID %u out of range (total nodes: %u)\n", node_id, nodes); + return -DER_INVAL; + } + } + if (operations[i].type == OP_ADD_NODE) { + uint32_t new_nodes; + if (sscanf(operations[i].args, "%u", &new_nodes) != 1) { + fprintf(stderr, "ERROR: Invalid node count in operation: %s\n", operations[i].args); + return -DER_INVAL; + } + if (new_nodes == 0) { + fprintf(stderr, "ERROR: Node count must be greater than 0 in operation: %s\n", operations[i].args); + return -DER_INVAL; + } + } + } + + return 0; +} + +void +cleanup(struct test_ctx *ctx) +{ + if (ctx->pool_map != NULL) + pool_map_decref(ctx->pool_map); + + pl_fini(); +} diff --git a/src/placement/tests/layout_test_helpers.h b/src/placement/tests/layout_test_helpers.h new file mode 100644 index 00000000000..93ee0d9e066 --- /dev/null +++ b/src/placement/tests/layout_test_helpers.h @@ -0,0 +1,74 @@ +/** + * (C) Copyright 2026 Hewlett Packard Enterprise Development LP + * + * SPDX-License-Identifier: BSD-2-Clause-Patent + */ +#define D_LOGFAC DD_FAC(tests) +#include + +#include +#include +#include "place_obj_common.h" +#include +#include + +#define DEFAULT_NODES 8 +#define DEFAULT_RANKS 2 +#define DEFAULT_TARGETS 16 +#define DEFAULT_OBJ_COUNT 1000 +#define DEFAULT_OBJ_CLASS "EC_2P1G1" + +#define MAX_OPERATIONS 32 +#define MAX_OIDS 100000 +#define MAX_SHARDS 32 + +struct test_ctx { + int nodes; + int ranks_per_node; + int targets_per_rank; + int num_oids; + daos_oclass_id_t oclass; + struct pool_map *pool_map; + struct pl_map *pl_map; +}; + +struct test_oid { daos_obj_id_t oid; }; + +struct oid_layout { + daos_obj_id_t oid; + int shard_nr; + uint32_t targets[MAX_SHARDS]; +}; + +enum operation_type { + OP_ADD_NODE, + OP_EXCLUDE_NODE, + OP_EXCLUDE_RANK, + OP_REINTEGRATE_RANK, + OP_INVALID +}; + +struct operation { + enum operation_type type; + char args[64]; +}; + +struct op_map { + const char *name; + enum operation_type type; +}; + +int pool_map_init(struct test_ctx *ctx); + +int placement_map_init(struct test_ctx *ctx); + +int generate_oids(struct test_ctx *ctx, struct test_oid *oids); + +int capture_layouts(struct test_ctx *ctx, + struct test_oid *oids, + struct oid_layout *layouts); + +int validate_configuration(uint32_t nodes, uint32_t ranks, uint32_t targets, + const char *object_class_str, struct operation *operations, int operation_count); + +void cleanup(struct test_ctx *ctx);