diff --git a/src/utils/ddb/ddb_vos.c b/src/utils/ddb/ddb_vos.c index 15a5e9c62ee..718e2f3e6c0 100644 --- a/src/utils/ddb/ddb_vos.c +++ b/src/utils/ddb/ddb_vos.c @@ -1093,6 +1093,117 @@ dv_dump_value(daos_handle_t poh, struct dv_tree_path *path, dv_dump_value_cb dum return rc; } +static int +dump_csum_sv(daos_handle_t coh, daos_key_t *dkey, daos_unit_oid_t *oid, daos_iod_t *iod, + daos_epoch_t epoch, dv_dump_csum_cb dump_cb, void *cb_arg) +{ + daos_handle_t ioh; + struct dcs_ci_list *cil; + int cb_rc = 0; + int rc; + + rc = vos_fetch_begin(coh, *oid, epoch, dkey, 1, iod, VOS_OF_FETCH_CSUM, NULL, &ioh, NULL); + if (!SUCCESS(rc)) { + D_ERROR("vos_fetch_begin for csum dump of " DF_UOID " failed: " DF_RC "\n", + DP_UOID(*oid), DP_RC(rc)); + goto out; + } + + cil = vos_ioh2ci(ioh); + + cb_rc = dump_cb(cb_arg, NULL, cil); + if (!SUCCESS(cb_rc)) + D_DEBUG(DB_IO, "Csum dump callback for " DF_UOID " returned: " DF_RC "\n", + DP_UOID(*oid), DP_RC(cb_rc)); + + rc = vos_fetch_end(ioh, NULL, cb_rc); + if (!SUCCESS(rc) && rc != cb_rc) + D_ERROR("vos_fetch_end for csum dump of " DF_UOID " failed: " DF_RC "\n", + DP_UOID(*oid), DP_RC(rc)); + +out: + if (!SUCCESS(cb_rc)) + rc = cb_rc; + return rc; +} + +static int +dump_csum_recx(daos_handle_t coh, daos_key_t *dkey, daos_unit_oid_t *oid, daos_iod_t *iod, + daos_epoch_t epoch, dv_dump_csum_cb dump_cb, void *cb_arg) +{ + daos_handle_t ioh; + struct dcs_ci_list *cil; + struct daos_recx_ep_list *rel; + int cb_rc = 0; + int rc; + + rc = vos_fetch_begin(coh, *oid, epoch, dkey, 1, iod, VOS_OF_FETCH_CSUM, NULL, &ioh, NULL); + if (!SUCCESS(rc)) { + D_ERROR("vos_fetch_begin for csum dump of " DF_UOID " failed: " DF_RC "\n", + DP_UOID(*oid), DP_RC(rc)); + goto out; + } + + cil = vos_ioh2ci(ioh); + rel = vos_ioh2recx_list(ioh); + + cb_rc = dump_cb(cb_arg, rel, cil); + if (!SUCCESS(cb_rc)) + D_DEBUG(DB_IO, "Csum dump callback for " DF_UOID " returned: " DF_RC "\n", + DP_UOID(*oid), DP_RC(cb_rc)); + + /* rel ownership is transferred by vos_ioh2recx_list(); free before vos_fetch_end. */ + daos_recx_ep_list_free(rel, iod->iod_nr); + rc = vos_fetch_end(ioh, NULL, cb_rc); + if (!SUCCESS(rc) && rc != cb_rc) + D_ERROR("vos_fetch_end for csum dump of " DF_UOID " failed: " DF_RC "\n", + DP_UOID(*oid), DP_RC(rc)); + +out: + if (!SUCCESS(cb_rc)) + rc = cb_rc; + return rc; +} + +int +dv_dump_csum(daos_handle_t poh, struct dv_tree_path *path, daos_epoch_t epoch, + dv_dump_csum_cb dump_cb, void *cb_arg) +{ + daos_handle_t coh; + daos_iod_t iod = {0}; + int rc = 0; + + /* No-op when no callback is provided; the caller controls whether to consume results. */ + if (dump_cb == NULL) + goto out; + + rc = vos_cont_open(poh, path->vtp_cont, &coh); + if (!SUCCESS(rc)) { + D_ERROR("Opening container for csum dump of " DF_UOID " failed: " DF_RC "\n", + DP_UOID(path->vtp_oid), DP_RC(rc)); + goto out; + } + + iod.iod_name = path->vtp_akey; + iod.iod_recxs = &path->vtp_recx; + iod.iod_nr = 1; + iod.iod_size = 0; + if (path->vtp_is_recx) { + iod.iod_type = DAOS_IOD_ARRAY; + rc = dump_csum_recx(coh, &path->vtp_dkey, &path->vtp_oid, &iod, epoch, dump_cb, + cb_arg); + } else { + iod.iod_type = DAOS_IOD_SINGLE; + rc = dump_csum_sv(coh, &path->vtp_dkey, &path->vtp_oid, &iod, epoch, dump_cb, + cb_arg); + } + + vos_cont_close(coh); + +out: + return rc; +} + static void ilog_entry_status(enum ilog_status status, char *status_str, uint32_t status_str_len) { diff --git a/src/utils/ddb/ddb_vos.h b/src/utils/ddb/ddb_vos.h index 09e51382924..7ade0fa462b 100644 --- a/src/utils/ddb/ddb_vos.h +++ b/src/utils/ddb/ddb_vos.h @@ -155,6 +155,37 @@ typedef int (*dv_dump_value_cb)(void *cb_arg, d_iov_t *value); int dv_dump_value(daos_handle_t poh, struct dv_tree_path *path, dv_dump_value_cb dump_cb, void *cb_arg); +/** + * Callback invoked by dv_dump_csum() with the fetched checksum information. + * + * @param cb_arg User-provided argument passed through from dv_dump_csum(). + * @param rel Recx/epoch list describing the stored extents. NULL for single-value akeys; + * non-NULL for array akeys. The caller must not free this pointer. + * @param cil Checksum info list. Valid only for the duration of the callback. + * @return 0 on success; a negative error code is propagated back to the caller of + * dv_dump_csum(). + */ +typedef int (*dv_dump_csum_cb)(void *cb_arg, struct daos_recx_ep_list *rel, + struct dcs_ci_list *cil); + +/** + * Fetch and dump the checksum information for the akey identified by \a path. + * + * @param poh Open pool handle. + * @param path VOS tree path identifying the container, object, dkey, and akey. + * For array akeys, path->vtp_recx selects the extent to inspect. + * @param epoch Epoch for the fetch. For single-value akeys, controls which version is + * returned — pass DAOS_EPOCH_MAX to get the latest, or a snapshot epoch to + * access an earlier version. For array akeys, selects the visible extent set. + * @param dump_cb Callback invoked with the result. If NULL, the function returns 0 + * without opening the container or calling VOS. + * @param cb_arg Opaque argument forwarded to \a dump_cb. + * @return 0 on success, or a negative error code. + */ +int +dv_dump_csum(daos_handle_t poh, struct dv_tree_path *path, daos_epoch_t epoch, + dv_dump_csum_cb dump_cb, void *cb_arg); + struct ddb_ilog_entry { uint32_t die_idx; int32_t die_status; diff --git a/src/utils/ddb/tests/ddb_test_driver.c b/src/utils/ddb/tests/ddb_test_driver.c index bbaa3ae8c5f..61ab707f306 100644 --- a/src/utils/ddb/tests/ddb_test_driver.c +++ b/src/utils/ddb/tests/ddb_test_driver.c @@ -58,14 +58,14 @@ char *g_akeys_str[] = { char *g_invalid_key_str = "invalid key"; -daos_unit_oid_t g_oids[10]; -uuid_t g_uuids[10]; -daos_key_t g_dkeys[10]; -daos_key_t g_akeys[10]; -daos_recx_t g_recxs[10]; -daos_key_t g_invalid_key; -daos_recx_t g_invalid_recx = {.rx_nr = 9999, .rx_idx = 9999}; - +daos_unit_oid_t g_oids[10]; +uuid_t g_uuids[10]; +daos_key_t g_dkeys[10]; +daos_key_t g_akeys[10]; +daos_recx_t g_recxs[10]; +daos_key_t g_invalid_key; +daos_recx_t g_invalid_recx = {.rx_nr = 9999, .rx_idx = 9999}; +const char *g_csum_uuid_str = "12345678-1234-1234-1234-123456789101"; daos_unit_oid_t dvt_gen_uoid(uint32_t i) @@ -624,6 +624,269 @@ char_in_tests(char a, char *str, uint32_t str_len) return false; } +static int +csum_test_sv_setup(struct dt_vos_pool_ctx *tctx, daos_handle_t coh, d_sg_list_t *sgl) +{ + struct dt_csum_ctx *csum_ctx; + daos_iod_t iod; + char *buf; + char filler; + int sv_idx; + daos_epoch_t epoch; + int rc; + + csum_ctx = tctx->dvt_extra; + + D_ALLOC(buf, csum_ctx->dct_sv_size); + if (buf == NULL) { + rc = -DER_NOMEM; + goto out; + } + + d_iov_set(&iod.iod_name, g_akeys_str[0], strlen(g_akeys_str[0])); + iod.iod_nr = 1; + iod.iod_type = DAOS_IOD_SINGLE; + iod.iod_size = csum_ctx->dct_sv_size; + iod.iod_recxs = NULL; + + /* g_oids[0]: single value written without checksum */ + memset(buf, 'a', csum_ctx->dct_sv_size); + d_iov_set(sgl->sg_iovs, &buf[0], csum_ctx->dct_sv_size); + rc = vos_obj_update(coh, g_oids[0], 1, 0, 0, &g_dkeys[0], 1, &iod, NULL, sgl); + if (rc != 0) + goto out_buf; + + /* + * g_oids[1]: DVT_FAKE_SV_COUNT successive overwrites with distinct content and checksum. + * sv_idx 0: epoch 1, filler 'b' + * sv_idx 1: epoch 2, filler 'c' + * Fetching at epoch N returns dct_sv_ics[N-1]; DAOS_EPOCH_MAX returns the last entry. + */ + epoch = 1; + filler = 'b'; + for (sv_idx = 0; sv_idx < DVT_FAKE_SV_COUNT; sv_idx++) { + memset(buf, filler, csum_ctx->dct_sv_size); + d_iov_set(sgl->sg_iovs, &buf[0], csum_ctx->dct_sv_size); + + rc = daos_csummer_calc_iods(csum_ctx->dct_csummer, sgl, &iod, NULL, 1, false, NULL, + 0, &csum_ctx->dct_sv_ics[sv_idx]); + if (rc != 0) + goto out_ics; + rc = vos_obj_update(coh, g_oids[1], epoch, 0, 0, &g_dkeys[0], 1, &iod, + csum_ctx->dct_sv_ics[sv_idx], sgl); + if (rc != 0) + goto out_ics; + + epoch++; + filler++; + } + +out_ics: + if (rc != 0) { + for (sv_idx = 0; sv_idx < DVT_FAKE_SV_COUNT; sv_idx++) { + if (csum_ctx->dct_sv_ics[sv_idx] == NULL) + break; + daos_csummer_free_ic(csum_ctx->dct_csummer, &csum_ctx->dct_sv_ics[sv_idx]); + } + } +out_buf: + D_FREE(buf); +out: + return rc; +} + +static int +csum_test_recx_setup(struct dt_vos_pool_ctx *tctx, daos_handle_t coh, d_sg_list_t *sgl) +{ + struct dt_csum_ctx *csum_ctx; + daos_iod_t iod; + char *buf; + char filler; + daos_recx_t recx; + int recx_idx; + daos_epoch_t epoch; + int rc; + + csum_ctx = tctx->dvt_extra; + + D_ALLOC(buf, csum_ctx->dct_recx_size); + if (buf == NULL) { + rc = -DER_NOMEM; + goto out; + } + + d_iov_set(&iod.iod_name, g_akeys_str[1], strlen(g_akeys_str[1])); + iod.iod_nr = 1; + iod.iod_type = DAOS_IOD_ARRAY; + iod.iod_size = 1; + + recx.rx_nr = csum_ctx->dct_recx_size; + + /* + * Two recxs are written per object, with overlapping extents: + * recx 0: rx_idx=0, rx_nr=dct_recx_size (epoch 1) + * recx 1: rx_idx=dct_recx_size/2, rx_nr=dct_recx_size (epoch 2) + * The overlap means that a fetch covering [0, dct_recx_size) intersects recx 1 only + * partially, but VOS_OF_FETCH_CSUM records the full stored extent of recx 1. + */ + + /* g_oids[0]: recxs written without checksum */ + epoch = 1; + filler = 'c'; + for (recx_idx = 0; recx_idx < DVT_FAKE_RECX_COUNT; recx_idx++) { + recx.rx_idx = recx_idx * csum_ctx->dct_recx_size / 2; + iod.iod_recxs = &recx; + + memset(buf, filler, csum_ctx->dct_recx_size); + d_iov_set(sgl->sg_iovs, &buf[0], csum_ctx->dct_recx_size); + + rc = vos_obj_update(coh, g_oids[0], epoch, 0, 0, &g_dkeys[0], 1, &iod, NULL, sgl); + if (rc != 0) + goto out_buf; + + epoch++; + filler++; + } + + /* g_oids[1]: recxs written with checksum */ + epoch = 1; + for (recx_idx = 0; recx_idx < DVT_FAKE_RECX_COUNT; recx_idx++) { + recx.rx_idx = recx_idx * csum_ctx->dct_recx_size / 2; + iod.iod_recxs = &recx; + + memset(buf, filler, csum_ctx->dct_recx_size); + d_iov_set(sgl->sg_iovs, &buf[0], csum_ctx->dct_recx_size); + + rc = daos_csummer_calc_iods(csum_ctx->dct_csummer, sgl, &iod, NULL, 1, false, NULL, + 0, &csum_ctx->dct_recx_ics[recx_idx]); + if (rc) + goto out_ics; + rc = vos_obj_update(coh, g_oids[1], epoch, 0, 0, &g_dkeys[0], 1, &iod, + csum_ctx->dct_recx_ics[recx_idx], sgl); + if (rc != 0) + goto out_ics; + + epoch++; + filler++; + } + +out_ics: + if (rc != 0) { + for (recx_idx = 0; recx_idx < DVT_FAKE_RECX_COUNT; recx_idx++) { + if (csum_ctx->dct_recx_ics[recx_idx] == NULL) + break; + daos_csummer_free_ic(csum_ctx->dct_csummer, + &csum_ctx->dct_recx_ics[recx_idx]); + } + } +out_buf: + D_FREE(buf); +out: + return rc; +} + +/* + * Populate the VOS pool with test data for checksum tests. + * + * Requires tctx->dvt_poh to be a valid open pool handle. The caller is responsible for opening and + * closing that handle. + */ +int +ddb_test_csum_setup(void **state) +{ + struct dt_vos_pool_ctx *tctx; + struct dt_csum_ctx *csum_ctx; + daos_handle_t coh; + d_sg_list_t sgl; + int rc; + + tctx = *state; + tctx->dvt_extra = NULL; + + D_ALLOC_PTR(csum_ctx); + if (csum_ctx == NULL) { + rc = -DER_NOMEM; + goto out; + } + csum_ctx->dct_sv_size = DVT_FAKE_SV_SIZE; + csum_ctx->dct_recx_size = DVT_FAKE_RECX_SIZE; + csum_ctx->dct_chunk_size = DVT_FAKE_CHUNK_SIZE; + csum_ctx->dct_csum_type = DVT_FAKE_CSUM_TYPE; + memset(&csum_ctx->dct_sv_ics[0], 0, sizeof(csum_ctx->dct_sv_ics)); + memset(&csum_ctx->dct_recx_ics[0], 0, sizeof(csum_ctx->dct_recx_ics)); + rc = daos_csummer_init_with_type(&csum_ctx->dct_csummer, csum_ctx->dct_csum_type, + csum_ctx->dct_chunk_size, 0); + if (rc != 0) + goto out_csum_ctx; + tctx->dvt_extra = csum_ctx; + + rc = uuid_parse(g_csum_uuid_str, csum_ctx->dct_cont_uuid); + if (rc != 0) + goto out_csummer; + rc = vos_cont_create(tctx->dvt_poh, csum_ctx->dct_cont_uuid); + if (rc != 0) + goto out_csummer; + rc = vos_cont_open(tctx->dvt_poh, csum_ctx->dct_cont_uuid, &coh); + if (rc != 0) + goto out_cont_destroy; + + rc = d_sgl_init(&sgl, 1); + if (rc != 0) + goto out_cont_close; + + rc = csum_test_sv_setup(tctx, coh, &sgl); + if (rc != 0) + goto out_sgl; + rc = csum_test_recx_setup(tctx, coh, &sgl); + +out_sgl: + d_sgl_fini(&sgl, false); +out_cont_close: + vos_cont_close(coh); +out_cont_destroy: + if (rc != 0) + vos_cont_destroy(tctx->dvt_poh, csum_ctx->dct_cont_uuid); +out_csummer: + if (rc != 0) + daos_csummer_destroy(&csum_ctx->dct_csummer); +out_csum_ctx: + if (rc != 0) { + D_FREE(csum_ctx); + tctx->dvt_extra = NULL; + } +out: + return rc; +} + +/* + * Clean up the VOS pool state created by ddb_test_csum_setup(). + * + * Requires tctx->dvt_poh to be the same valid open pool handle that was provided to + * ddb_test_csum_setup(). The caller retains ownership of the handle. + */ +void +ddb_test_csum_teardown(void **state) +{ + struct dt_vos_pool_ctx *tctx; + struct dt_csum_ctx *csum_ctx; + int ci_idx; + + tctx = *state; + csum_ctx = tctx->dvt_extra; + if (csum_ctx == NULL) + return; + + for (ci_idx = 0; ci_idx < DVT_FAKE_SV_COUNT; ci_idx++) + daos_csummer_free_ic(csum_ctx->dct_csummer, &csum_ctx->dct_sv_ics[ci_idx]); + for (ci_idx = 0; ci_idx < DVT_FAKE_RECX_COUNT; ci_idx++) + daos_csummer_free_ic(csum_ctx->dct_csummer, &csum_ctx->dct_recx_ics[ci_idx]); + daos_csummer_destroy(&csum_ctx->dct_csummer); + + vos_cont_destroy(tctx->dvt_poh, csum_ctx->dct_cont_uuid); + + D_FREE(csum_ctx); +} + /* * ----------------------------------------------- * Execute diff --git a/src/utils/ddb/tests/ddb_test_driver.h b/src/utils/ddb/tests/ddb_test_driver.h index 833303ec16d..13457c9a9f1 100644 --- a/src/utils/ddb/tests/ddb_test_driver.h +++ b/src/utils/ddb/tests/ddb_test_driver.h @@ -18,30 +18,50 @@ #include #include -extern bool g_verbose; -extern const char *g_uuids_str[10]; -extern const char *g_invalid_uuid_str; -extern uuid_t g_uuids[10]; -extern daos_unit_oid_t g_oids[10]; -extern daos_unit_oid_t g_invalid_oid; -extern char *g_dkeys_str[10]; -extern char *g_akeys_str[10]; -extern daos_key_t g_dkeys[10]; -extern daos_key_t g_akeys[10]; -extern daos_key_t g_invalid_key; -extern daos_recx_t g_recxs[10]; -extern daos_recx_t g_invalid_recx; +extern bool g_verbose; +extern const char *g_uuids_str[10]; +extern const char *g_invalid_uuid_str; +extern uuid_t g_uuids[10]; +extern daos_unit_oid_t g_oids[10]; +extern daos_unit_oid_t g_invalid_oid; +extern char *g_dkeys_str[10]; +extern char *g_akeys_str[10]; +extern daos_key_t g_dkeys[10]; +extern daos_key_t g_akeys[10]; +extern daos_key_t g_invalid_key; +extern daos_recx_t g_recxs[10]; +extern daos_recx_t g_invalid_recx; +extern const char *g_csum_uuid_str; struct dt_vos_pool_ctx { - daos_handle_t dvt_poh; - uuid_t dvt_pool_uuid; - int dvt_fd; - char dvt_pmem_file[128]; - uint32_t dvt_cont_count; - uint32_t dvt_obj_count; - uint32_t dvt_dkey_count; - uint32_t dvt_akey_count; - bool dvt_special_pool_destroy; + daos_handle_t dvt_poh; + uuid_t dvt_pool_uuid; + int dvt_fd; + char dvt_pmem_file[128]; + uint32_t dvt_cont_count; + uint32_t dvt_obj_count; + uint32_t dvt_dkey_count; + uint32_t dvt_akey_count; + bool dvt_special_pool_destroy; + void *dvt_extra; +}; + +#define DVT_FAKE_SV_COUNT (2) +#define DVT_FAKE_RECX_COUNT (2) +#define DVT_FAKE_SV_SIZE (1u << 10) +#define DVT_FAKE_RECX_SIZE (1u << 13) +#define DVT_FAKE_CHUNK_SIZE (1u << 12) +#define DVT_FAKE_CSUM_TYPE (HASH_TYPE_CRC64) + +struct dt_csum_ctx { + uuid_t dct_cont_uuid; + size_t dct_sv_size; + size_t dct_recx_size; + enum DAOS_HASH_TYPE dct_csum_type; + size_t dct_chunk_size; + struct daos_csummer *dct_csummer; + struct dcs_iod_csums *dct_sv_ics[DVT_FAKE_SV_COUNT]; + struct dcs_iod_csums *dct_recx_ics[DVT_FAKE_RECX_COUNT]; }; daos_unit_oid_t dvt_gen_uoid(uint32_t i); @@ -58,6 +78,14 @@ void dvt_iov_alloc_str(d_iov_t *iov, const char *str); int ddb_test_setup_vos(void **state); int ddb_teardown_vos(void **state); +/* Requires tctx->dvt_poh to be a valid open pool handle (caller-owned). */ +int +ddb_test_csum_setup(void **state); +/* Requires tctx->dvt_poh to be the same valid open pool handle as provided to + * ddb_test_csum_setup(). */ +void + ddb_test_csum_teardown(void **state); + int ddb_parse_tests_run(void); int ddb_vos_tests_run(void); int diff --git a/src/utils/ddb/tests/ddb_vos_tests.c b/src/utils/ddb/tests/ddb_vos_tests.c index 52fabaa572b..63dc15dd323 100644 --- a/src/utils/ddb/tests/ddb_vos_tests.c +++ b/src/utils/ddb/tests/ddb_vos_tests.c @@ -1104,6 +1104,24 @@ dv_test_teardown(void **state) return 0; } +static int +dv_test_csum_setup(void **state) +{ + assert_success(dv_test_setup(state)); + assert_success(ddb_test_csum_setup(state)); + + return 0; +} + +static int +dv_test_csum_teardown(void **state) +{ + ddb_test_csum_teardown(state); + assert_success(dv_test_teardown(state)); + + return 0; +} + static void pool_flags_tests(void **state) { @@ -1209,11 +1227,243 @@ read_only_vs_write_mode_test(void **state) SHA256_DIGEST_LEN); } +/* Callback that returns *(int *)cb_args, or 0 if cb_args is NULL. */ +static int +csum_cb_return_rc(void *cb_args, struct daos_recx_ep_list *rel, struct dcs_ci_list *cil) +{ + return (cb_args != NULL) ? (*(int *)cb_args) : (0); +} + +static void +dump_csum_error_tests(void **state) +{ + struct dt_vos_pool_ctx *tctx = *state; + struct dt_csum_ctx *csum_ctx = tctx->dvt_extra; + struct dv_tree_path path = {0}; + int rc; + + uuid_copy(path.vtp_cont, csum_ctx->dct_cont_uuid); + path.vtp_dkey = g_dkeys[0]; + path.vtp_akey = g_akeys[0]; /* single value type */ + path.vtp_is_recx = false; + + /* invalid poh: error comes from vos_cont_open, not the callback (which would return 0) */ + rc = dv_dump_csum(DAOS_HDL_INVAL, &path, DAOS_EPOCH_MAX, csum_cb_return_rc, NULL); + assert_rc_equal(-DER_INVAL, rc); +} + +static int +check_csum_sv_cb_001(void *cb_args, struct daos_recx_ep_list *rel, struct dcs_ci_list *cil) +{ + assert_null(cb_args); + assert_null(rel); + assert_non_null(cil); + + assert_int_equal(cil->dcl_csum_infos_nr, 0); + + return 0; +} + +static int +check_csum_sv_cb_002(void *cb_args, struct daos_recx_ep_list *rel, struct dcs_ci_list *cil) +{ + struct dt_csum_ctx *csum_ctx; + struct dcs_csum_info *ci; + + assert_non_null(cb_args); + assert_null(rel); + assert_non_null(cil); + + csum_ctx = cb_args; + assert_int_equal(csum_ctx->dct_sv_size, DVT_FAKE_SV_SIZE); + assert_int_equal(csum_ctx->dct_chunk_size, DVT_FAKE_CHUNK_SIZE); + assert_int_equal(csum_ctx->dct_csum_type, DVT_FAKE_CSUM_TYPE); + + assert_int_equal(cil->dcl_csum_infos_nr, 1); + + ci = dcs_csum_info_get(cil, 0); + assert_true(ci_is_valid(ci)); + assert_int_equal(ci->cs_nr, 1); + assert_true(daos_csummer_compare_csum_info(csum_ctx->dct_csummer, ci, + csum_ctx->dct_sv_ics[0]->ic_data)); + + return 0; +} + +static int +check_csum_sv_cb_003(void *cb_args, struct daos_recx_ep_list *rel, struct dcs_ci_list *cil) +{ + struct dt_csum_ctx *csum_ctx; + struct dcs_csum_info *ci; + + assert_non_null(cb_args); + assert_null(rel); + assert_non_null(cil); + + csum_ctx = cb_args; + assert_int_equal(cil->dcl_csum_infos_nr, 1); + + ci = dcs_csum_info_get(cil, 0); + assert_true(ci_is_valid(ci)); + assert_int_equal(ci->cs_nr, 1); + assert_true(daos_csummer_compare_csum_info(csum_ctx->dct_csummer, ci, + csum_ctx->dct_sv_ics[1]->ic_data)); + + return 0; +} + +static void +dump_csum_sv_tests(void **state) +{ + struct dt_vos_pool_ctx *tctx = *state; + struct dt_csum_ctx *csum_ctx = tctx->dvt_extra; + struct dv_tree_path path = {0}; + int rc; + + uuid_copy(path.vtp_cont, csum_ctx->dct_cont_uuid); + path.vtp_dkey = g_dkeys[0]; + path.vtp_akey = g_akeys[0]; /* single value type */ + path.vtp_is_recx = false; + + /* no csum info */ + path.vtp_oid = g_oids[0]; + rc = dv_dump_csum(tctx->dvt_poh, &path, DAOS_EPOCH_MAX, check_csum_sv_cb_001, NULL); + assert_success(rc); + + /* with csum info, epoch 1 returns the epoch-1 checksum */ + path.vtp_oid = g_oids[1]; + rc = dv_dump_csum(tctx->dvt_poh, &path, 1, check_csum_sv_cb_002, csum_ctx); + assert_success(rc); + + /* with csum info, EPOCH_MAX returns the epoch-2 (latest) checksum */ + path.vtp_oid = g_oids[1]; + rc = dv_dump_csum(tctx->dvt_poh, &path, DAOS_EPOCH_MAX, check_csum_sv_cb_003, csum_ctx); + assert_success(rc); + + /* with csum info, without callback */ + path.vtp_oid = g_oids[1]; + rc = dv_dump_csum(tctx->dvt_poh, &path, DAOS_EPOCH_MAX, NULL, csum_ctx); + assert_success(rc); + + /* callback failure is propagated */ + path.vtp_oid = g_oids[1]; + rc = dv_dump_csum(tctx->dvt_poh, &path, DAOS_EPOCH_MAX, csum_cb_return_rc, + &(int){-DER_INVAL}); + assert_rc_equal(-DER_INVAL, rc); +} + +static int +check_csum_recx_cb_001(void *cb_args, struct daos_recx_ep_list *rel, struct dcs_ci_list *cil) +{ + assert_null(cb_args); + assert_non_null(rel); + assert_non_null(cil); + + assert_int_equal(rel->re_nr, DVT_FAKE_RECX_COUNT); + assert_int_equal(rel->re_items[0].re_recx.rx_idx, 0); + assert_int_equal(rel->re_items[0].re_recx.rx_nr, DVT_FAKE_RECX_SIZE); + assert_int_equal(rel->re_items[0].re_ep, 1); + assert_int_equal(rel->re_items[1].re_recx.rx_idx, DVT_FAKE_RECX_SIZE / 2); + /* + * VOS_OF_FETCH_CSUM records the full stored extent, not the IOD intersection: + * recx 1 starts at rx_idx=DVT_FAKE_RECX_SIZE/2 but its rx_nr is DVT_FAKE_RECX_SIZE. + */ + assert_int_equal(rel->re_items[1].re_recx.rx_nr, DVT_FAKE_RECX_SIZE); + assert_int_equal(rel->re_items[1].re_ep, 2); + + /* No csum was stored for g_oids[0], so the checksum info list is empty. */ + assert_int_equal(cil->dcl_csum_infos_nr, 0); + + return 0; +} + +static int +check_csum_recx_cb_002(void *cb_args, struct daos_recx_ep_list *rel, struct dcs_ci_list *cil) +{ + struct dt_csum_ctx *csum_ctx; + struct dcs_csum_info *ci; + + assert_non_null(cb_args); + assert_non_null(rel); + assert_non_null(cil); + + csum_ctx = cb_args; + assert_int_equal(csum_ctx->dct_recx_size, DVT_FAKE_RECX_SIZE); + assert_int_equal(csum_ctx->dct_chunk_size, DVT_FAKE_CHUNK_SIZE); + assert_int_equal(csum_ctx->dct_csum_type, DVT_FAKE_CSUM_TYPE); + + assert_int_equal(rel->re_nr, DVT_FAKE_RECX_COUNT); + assert_int_equal(rel->re_items[0].re_recx.rx_idx, 0); + assert_int_equal(rel->re_items[0].re_recx.rx_nr, DVT_FAKE_RECX_SIZE); + assert_int_equal(rel->re_items[0].re_ep, 1); + assert_int_equal(rel->re_items[1].re_recx.rx_idx, DVT_FAKE_RECX_SIZE / 2); + assert_int_equal(rel->re_items[1].re_recx.rx_nr, DVT_FAKE_RECX_SIZE); + assert_int_equal(rel->re_items[1].re_ep, 2); + + assert_non_null(cil); + assert_int_equal(cil->dcl_csum_infos_nr, DVT_FAKE_RECX_COUNT); + + ci = dcs_csum_info_get(cil, 0); + assert_true(ci_is_valid(ci)); + assert_int_equal(ci->cs_nr, 2); + assert_true(daos_csummer_compare_csum_info(csum_ctx->dct_csummer, ci, + csum_ctx->dct_recx_ics[0]->ic_data)); + + ci = dcs_csum_info_get(cil, 1); + assert_true(ci_is_valid(ci)); + assert_int_equal(ci->cs_nr, 2); + assert_true(daos_csummer_compare_csum_info(csum_ctx->dct_csummer, ci, + csum_ctx->dct_recx_ics[1]->ic_data)); + + return 0; +} + +static void +dump_csum_recx_tests(void **state) +{ + struct dt_vos_pool_ctx *tctx = *state; + struct dt_csum_ctx *csum_ctx = tctx->dvt_extra; + struct dv_tree_path path = {0}; + int rc; + + uuid_copy(path.vtp_cont, csum_ctx->dct_cont_uuid); + path.vtp_dkey = g_dkeys[0]; + path.vtp_akey = g_akeys[1]; /* array value type */ + path.vtp_is_recx = true; + path.vtp_recx.rx_idx = 0; + path.vtp_recx.rx_nr = csum_ctx->dct_recx_size; + + /* no csum info */ + path.vtp_oid = g_oids[0]; + rc = dv_dump_csum(tctx->dvt_poh, &path, DAOS_EPOCH_MAX, check_csum_recx_cb_001, NULL); + assert_success(rc); + + /* with csum info */ + path.vtp_oid = g_oids[1]; + rc = dv_dump_csum(tctx->dvt_poh, &path, DAOS_EPOCH_MAX, check_csum_recx_cb_002, csum_ctx); + assert_success(rc); + + /* with csum info, without callback */ + path.vtp_oid = g_oids[1]; + rc = dv_dump_csum(tctx->dvt_poh, &path, DAOS_EPOCH_MAX, NULL, csum_ctx); + assert_success(rc); + + /* callback failure is propagated */ + path.vtp_oid = g_oids[1]; + rc = dv_dump_csum(tctx->dvt_poh, &path, DAOS_EPOCH_MAX, csum_cb_return_rc, + &(int){-DER_INVAL}); + assert_rc_equal(-DER_INVAL, rc); +} + /* * All these tests use the same VOS tree that is created at suit_setup. Therefore, tests * that modify the state of the tree (delete, add, etc) should be run after all others. */ #define TEST(x) { #x, x, dv_test_setup, dv_test_teardown } + +/* Checksum tests need special setup/teardown */ +#define TEST_CSUM(test) {#test, test, dv_test_csum_setup, dv_test_csum_teardown} + const struct CMUnitTest dv_test_cases[] = { {"open_pool", open_pool_test, NULL, NULL}, /* don't want this test to run with setup */ TEST(list_items_test), @@ -1240,6 +1490,9 @@ const struct CMUnitTest dv_test_cases[] = { {"pool_flag_update", pool_flags_tests, NULL, NULL}, {"read_only_vs_write_mode", read_only_vs_write_mode_test, NULL, NULL}, /* don't want this test to run with setup */ + TEST_CSUM(dump_csum_error_tests), + TEST_CSUM(dump_csum_sv_tests), + TEST_CSUM(dump_csum_recx_tests), }; int