fragmap: implement bdev_lvol_get_fragmap

Get a fragmap for a specific segment of a logical volume using the provided offset and size.
A fragmap is a bitmap that records the allocation status of clusters. A value of "1" indicates
that a cluster is allocated, whereas "0" signifies that a cluster is unallocated.

Longhorn 6138

Signed-off-by: Derek Su <derek.su@suse.com>
This commit is contained in:
Derek Su 2023-09-07 13:38:24 +08:00
parent 271915912f
commit 0168515003
12 changed files with 524 additions and 0 deletions

View File

@ -496,6 +496,7 @@ Example response:
"bdev_lvol_rename_lvstore",
"bdev_lvol_create_lvstore",
"bdev_lvol_shallow_copy",
"bdev_lvol_get_fragmap",
"bdev_daos_delete",
"bdev_daos_create",
"bdev_daos_resize"
@ -10084,6 +10085,52 @@ Example response:
}
~~~
### bdev_lvol_get_fragmap {#bdev_lvol_get_fragmap}
Get a fragmap for a specific segment of a logical volume using the provided offset and size.
A fragmap is a bitmap that records the allocation status of clusters. A value of "1" indicates
that a cluster is allocated, whereas "0" signifies that a cluster is unallocated.
#### Parameters
Name | Optional | Type | Description
----------------------- | -------- | ----------- | -----------
name | Required | string | UUID or alias of the logical volume
offset | Optional | number | Offset in bytes of the specific segment of the logical volume (Default: 0)
size | Optional | number | Size in bytes of the specific segment of the logical volume (Default: 0 for representing the entire file)
#### Example
Example request:
~~~json
{
"jsonrpc": "2.0",
"method": "bdev_lvol_get_fragmap",
"id": 1,
"params": {
"name": "8a47421a-20cf-444f-845c-d97ad0b0bd8e",
"offset": 0,
"size": 41943040
}
}
~~~
Example response:
~~~json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"cluster_size": 4194304,
"num_clusters": 10,
"num_allocated_clusters": 0,
"fragmap": "AAA="
}
}
~~~
## RAID
### bdev_raid_get_bdevs {#rpc_bdev_raid_get_bdevs}

View File

@ -168,6 +168,14 @@ void spdk_bit_array_load_mask(struct spdk_bit_array *ba, const void *mask);
*/
void spdk_bit_array_clear_mask(struct spdk_bit_array *ba);
/**
* Encode a bit array into a base64 string.
*
* @param array Bit array to encode.
* @return base64 string.
*/
char *spdk_bit_array_to_base64_string(const struct spdk_bit_array *array);
#ifdef __cplusplus
}
#endif

View File

@ -22,6 +22,7 @@ extern "C" {
struct spdk_bs_dev;
struct spdk_lvol_store;
struct spdk_lvol;
struct spdk_fragmap;
enum lvol_clear_method {
LVOL_CLEAR_WITH_DEFAULT = BLOB_CLEAR_WITH_DEFAULT,
@ -116,6 +117,16 @@ typedef void (*spdk_lvol_op_with_handle_complete)(void *cb_arg, struct spdk_lvol
*/
typedef void (*spdk_lvol_op_complete)(void *cb_arg, int lvolerrno);
/**
* Callback definition for lvol operations with handle to fragmap
*
* @param cb_arg Custom arguments
* @param fragmap Handle to fragmap or NULL when lvolerrno is set
* @param lvolerrno Error
*/
typedef void (*spdk_lvol_op_with_fragmap_handle_complete)(void *cb_arg,
struct spdk_fragmap *fragmap, int lvolerrno);
/**
* Callback definition for spdk_lvol_iter_clones.
*

View File

@ -308,6 +308,20 @@ spdk_memset_s(void *data, size_t data_size, int ch, size_t count)
#endif
}
/**
* @brief Check if \b dividend is divisible by \b divisor
*
* @param dividend Dividend
* @param divisor Divisor which is a power of 2
* @return true
* @return false
*/
static inline bool
spdk_is_divisible_by(uint64_t dividend, uint64_t divisor)
{
return (dividend & (divisor - 1)) == 0;
}
#ifdef __cplusplus
}
#endif

View File

@ -115,6 +115,30 @@ struct spdk_lvol {
TAILQ_ENTRY(spdk_lvol) degraded_link;
};
struct spdk_fragmap {
struct spdk_bit_array *map;
uint64_t cluster_size;
uint64_t block_size;
uint64_t num_clusters;
uint64_t num_allocated_clusters;
};
struct spdk_fragmap_req {
struct spdk_bdev *bdev;
struct spdk_bdev_desc *bdev_desc;
struct spdk_io_channel *bdev_io_channel;
struct spdk_fragmap fragmap;
uint64_t offset;
uint64_t size;
uint64_t current_offset;
spdk_lvol_op_with_fragmap_handle_complete cb_fn;
void *cb_arg;
};
struct lvol_store_bdev *vbdev_lvol_store_first(void);
struct lvol_store_bdev *vbdev_lvol_store_next(struct lvol_store_bdev *prev);

View File

@ -11,6 +11,7 @@
#include "spdk/likely.h"
#include "spdk/util.h"
#include "spdk/base64.h"
typedef uint64_t spdk_bit_array_word;
#define SPDK_BIT_ARRAY_WORD_TZCNT(x) (__builtin_ctzll(x))
@ -491,3 +492,49 @@ spdk_bit_pool_free_all_bits(struct spdk_bit_pool *pool)
pool->lowest_free_bit = 0;
pool->free_count = spdk_bit_array_capacity(pool->array);
}
static int
bits_to_bytes(const int bits)
{
return ((bits + 7) >> 3);
}
char *
spdk_bit_array_to_base64_string(const struct spdk_bit_array *array)
{
uint32_t bit_count = spdk_bit_array_capacity(array);
size_t byte_count = bits_to_bytes(bit_count);
void *bytes;
char *encoded;
size_t total_size;
int rc;
bytes = calloc(byte_count, sizeof(char));
if (bytes == NULL) {
return NULL;
}
for (uint32_t i = 0; i < bit_count; i++) {
if (spdk_bit_array_get(array, i)) {
// Set the bit in bytes's correct position
((uint8_t *)bytes)[i / 8] |= 1 << (i % 8);
}
}
total_size = spdk_base64_get_encoded_strlen(byte_count) + 1;
encoded = calloc(total_size, sizeof(char));
if (encoded == NULL) {
free(bytes);
return NULL;
}
rc = spdk_base64_encode(encoded, bytes, byte_count);
if (rc != 0) {
free(bytes);
free(encoded);
return NULL;
}
free(bytes);
return encoded;
}

View File

@ -11,6 +11,8 @@
#include "spdk/string.h"
#include "spdk/uuid.h"
#include "spdk/blob.h"
#include "spdk/bit_array.h"
#include "spdk/base64.h"
#include "vbdev_lvol.h"
@ -2087,4 +2089,185 @@ vbdev_lvol_shallow_copy(struct spdk_lvol *lvol, const char *bdev_name,
spdk_lvol_shallow_copy(lvol, ext_dev, _vbdev_lvol_shallow_copy_cb, req);
}
static void seek_hole_done_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg);
static void
get_fragmap_done(struct spdk_fragmap_req *req, int error_code, const char *error_msg)
{
req->cb_fn(req->cb_arg, &req->fragmap, error_code);
spdk_bit_array_free(&req->fragmap.map);
spdk_put_io_channel(req->bdev_io_channel);
spdk_bdev_close(req->bdev_desc);
free(req);
}
static void
seek_data_done_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
{
struct spdk_fragmap_req *req = cb_arg;
uint64_t next_data_offset_blocks;
int rc;
next_data_offset_blocks = spdk_bdev_io_get_seek_offset(bdev_io);
spdk_bdev_free_io(bdev_io);
req->current_offset = next_data_offset_blocks * req->fragmap.block_size;
if (next_data_offset_blocks == UINT64_MAX || req->current_offset >= req->offset + req->size) {
get_fragmap_done(req, 0, NULL);
return;
}
rc = spdk_bdev_seek_hole(req->bdev_desc, req->bdev_io_channel,
spdk_divide_round_up(req->current_offset, req->fragmap.block_size),
seek_hole_done_cb, req);
if (rc != 0) {
get_fragmap_done(req, rc, "failed to seek hole");
}
}
static void
seek_hole_done_cb(struct spdk_bdev_io *bdev_io, bool success, void *cb_arg)
{
struct spdk_fragmap_req *req = cb_arg;
uint64_t next_offset;
uint64_t start_cluster;
uint64_t num_clusters;
int rc;
next_offset = spdk_bdev_io_get_seek_offset(bdev_io) * req->fragmap.block_size;
spdk_bdev_free_io(bdev_io);
next_offset = spdk_min(next_offset, req->offset + req->size);
start_cluster = spdk_divide_round_up(req->current_offset - req->offset, req->fragmap.cluster_size);
num_clusters = spdk_divide_round_up(next_offset - req->current_offset, req->fragmap.cluster_size);
for (uint64_t i = 0; i < num_clusters; i++) {
spdk_bit_array_set(req->fragmap.map, start_cluster + i);
}
req->fragmap.num_allocated_clusters += num_clusters;
req->current_offset = next_offset;
if (req->current_offset == req->offset + req->size) {
get_fragmap_done(req, 0, NULL);
return;
}
rc = spdk_bdev_seek_data(req->bdev_desc, req->bdev_io_channel,
spdk_divide_round_up(req->current_offset, req->fragmap.block_size),
seek_data_done_cb, req);
if (rc != 0) {
get_fragmap_done(req, rc, "failed to seek data");
}
}
static void
dummy_bdev_event_cb(enum spdk_bdev_event_type type, struct spdk_bdev *bdev, void *ctx)
{
}
void
vbdev_lvol_get_fragmap(struct spdk_lvol *lvol, uint64_t offset, uint64_t size,
spdk_lvol_op_with_fragmap_handle_complete cb_fn, void *cb_arg)
{
struct spdk_bdev_desc *desc;
struct spdk_io_channel *channel;
struct spdk_bit_array *fragmap;
struct spdk_fragmap_req *req;
uint64_t cluster_size, num_clusters, block_size, num_blocks, lvol_size, segment_size;
int rc;
// Create a bitmap recording the allocated clusters
cluster_size = spdk_bs_get_cluster_size(lvol->lvol_store->blobstore);
block_size = spdk_bdev_get_block_size(lvol->bdev);
num_blocks = spdk_bdev_get_num_blocks(lvol->bdev);
lvol_size = num_blocks * block_size;
if (offset + size > lvol_size) {
SPDK_ERRLOG("offset %lu and size %lu exceed lvol size %lu\n",
offset, size, lvol_size);
cb_fn(cb_arg, NULL, -EINVAL);
return;
}
segment_size = size;
if (size == 0) {
segment_size = lvol_size;
}
if (!spdk_is_divisible_by(offset, cluster_size) ||
!spdk_is_divisible_by(segment_size, cluster_size)) {
SPDK_ERRLOG("offset %lu and size %lu must be a multiple of cluster size %lu\n",
offset, segment_size, cluster_size);
cb_fn(cb_arg, NULL, -EINVAL);
return;
}
num_clusters = spdk_divide_round_up(segment_size, cluster_size);
fragmap = spdk_bit_array_create(num_clusters);
if (fragmap == NULL) {
SPDK_ERRLOG("failed to allocate fragmap with num_clusters %lu\n", num_clusters);
cb_fn(cb_arg, NULL, -ENOMEM);
return;
}
// Construct a fragmap of the lvol
rc = spdk_bdev_open_ext(lvol->bdev->name, false,
dummy_bdev_event_cb, NULL, &desc);
if (rc != 0) {
spdk_bit_array_free(&fragmap);
SPDK_ERRLOG("could not open bdev %s\n", lvol->bdev->name);
cb_fn(cb_arg, NULL, rc);
return;
}
channel = spdk_bdev_get_io_channel(desc);
if (channel == NULL) {
spdk_bit_array_free(&fragmap);
spdk_bdev_close(desc);
SPDK_ERRLOG("could not allocate I/O channel.\n");
cb_fn(cb_arg, NULL, -ENOMEM);
return;
}
req = calloc(1, sizeof(struct spdk_fragmap_req));
if (req == NULL) {
SPDK_ERRLOG("could not allocate fragmap_io\n");
spdk_put_io_channel(channel);
spdk_bdev_close(desc);
spdk_bit_array_free(&fragmap);
cb_fn(cb_arg, NULL, -ENOMEM);
return;
}
req->bdev = lvol->bdev;
req->bdev_desc = desc;
req->bdev_io_channel = channel;
req->offset = offset;
req->size = segment_size;
req->current_offset = offset;
req->cb_fn = cb_fn;
req->cb_arg = cb_arg;
req->fragmap.map = fragmap;
req->fragmap.num_clusters = num_clusters;
req->fragmap.block_size = block_size;
req->fragmap.cluster_size = cluster_size;
req->fragmap.num_allocated_clusters = 0;
rc = spdk_bdev_seek_data(desc, channel,
spdk_divide_round_up(offset, block_size),
seek_data_done_cb, req);
if (rc != 0) {
SPDK_ERRLOG("failed to seek data\n");
spdk_put_io_channel(channel);
spdk_bdev_close(desc);
spdk_bit_array_free(&fragmap);
free(req);
cb_fn(cb_arg, NULL, rc);
}
}
SPDK_LOG_REGISTER_COMPONENT(vbdev_lvol)

View File

@ -136,4 +136,17 @@ int vbdev_lvol_esnap_dev_create(void *bs_ctx, void *blob_ctx, struct spdk_blob *
void vbdev_lvol_shallow_copy(struct spdk_lvol *lvol, const char *bdev_name,
spdk_lvol_op_complete cb_fn, void *cb_arg);
/**
* @brief Get a fragmap for a specific segment of a logical volume using the provided offset and size
*
* @param lvol Handle to lvol
* @param offset Offset in bytes of the specific segment of the logical volume
* @param size Size in bytes of the specific segment of the logical volume
* @param cb_fn Completion callback
* @param cb_arg Completion callback custom arguments
*/
void
vbdev_lvol_get_fragmap(struct spdk_lvol *lvol, uint64_t offset, uint64_t size,
spdk_lvol_op_with_fragmap_handle_complete cb_fn, void *cb_arg);
#endif /* SPDK_VBDEV_LVOL_H */

View File

@ -10,6 +10,8 @@
#include "vbdev_lvol.h"
#include "spdk/string.h"
#include "spdk/log.h"
#include "spdk/bdev_module.h"
#include "spdk/bit_array.h"
SPDK_LOG_REGISTER_COMPONENT(lvol_rpc)
@ -1505,3 +1507,96 @@ cleanup:
SPDK_RPC_REGISTER("bdev_lvol_shallow_copy_status", rpc_bdev_lvol_shallow_copy_status,
SPDK_RPC_RUNTIME)
struct rpc_bdev_lvol_get_fragmap {
char *name;
uint64_t offset;
uint64_t size;
};
static void
free_rpc_bdev_lvol_get_fragmap(struct rpc_bdev_lvol_get_fragmap *r)
{
free(r->name);
}
static const struct spdk_json_object_decoder rpc_bdev_lvol_get_fragmap_decoders[] = {
{"name", offsetof(struct rpc_bdev_lvol_get_fragmap, name), spdk_json_decode_string, true},
{"offset", offsetof(struct rpc_bdev_lvol_get_fragmap, offset), spdk_json_decode_uint64, true},
{"size", offsetof(struct rpc_bdev_lvol_get_fragmap, size), spdk_json_decode_uint64, true},
};
static void
rpc_bdev_lvol_get_fragmap_cb(void *cb_arg, struct spdk_fragmap *fragmap, int lvolerrno)
{
struct spdk_json_write_ctx *w;
struct spdk_jsonrpc_request *request = cb_arg;
char *encoded;
if (lvolerrno != 0) {
goto invalid;
}
encoded = spdk_bit_array_to_base64_string(fragmap->map);
if (encoded == NULL) {
SPDK_ERRLOG("Failed to encode fragmap to base64 string\n");
lvolerrno = -EINVAL;
goto invalid;
}
w = spdk_jsonrpc_begin_result(request);
spdk_json_write_object_begin(w);
spdk_json_write_named_uint64(w, "cluster_size", fragmap->cluster_size);
spdk_json_write_named_uint64(w, "num_clusters", fragmap->num_clusters);
spdk_json_write_named_uint64(w, "num_allocated_clusters", fragmap->num_allocated_clusters);
spdk_json_write_named_string(w, "fragmap", encoded);
spdk_json_write_object_end(w);
spdk_jsonrpc_end_result(request, w);
free(encoded);
return;
invalid:
spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INVALID_PARAMS,
spdk_strerror(-lvolerrno));
}
static void
rpc_bdev_lvol_get_fragmap(struct spdk_jsonrpc_request *request, const struct spdk_json_val *params)
{
struct rpc_bdev_lvol_get_fragmap req = {};
struct spdk_bdev *bdev;
struct spdk_lvol *lvol;
if (spdk_json_decode_object(params, rpc_bdev_lvol_get_fragmap_decoders,
SPDK_COUNTOF(rpc_bdev_lvol_get_fragmap_decoders),
&req)) {
SPDK_ERRLOG("spdk_json_decode_object failed\n");
spdk_jsonrpc_send_error_response(request, SPDK_JSONRPC_ERROR_INTERNAL_ERROR,
"spdk_json_decode_object failed");
goto cleanup;
}
bdev = spdk_bdev_get_by_name(req.name);
if (bdev == NULL) {
SPDK_ERRLOG("bdev '%s' does not exist\n", req.name);
spdk_jsonrpc_send_error_response(request, -ENODEV, spdk_strerror(ENODEV));
goto cleanup;
}
lvol = vbdev_lvol_get_from_bdev(bdev);
if (lvol == NULL) {
SPDK_ERRLOG("lvol does not exist\n");
spdk_jsonrpc_send_error_response(request, -ENODEV, spdk_strerror(ENODEV));
goto cleanup;
}
vbdev_lvol_get_fragmap(lvol, req.offset, req.size, rpc_bdev_lvol_get_fragmap_cb, request);
cleanup:
free_rpc_bdev_lvol_get_fragmap(&req);
}
SPDK_RPC_REGISTER("bdev_lvol_get_fragmap", rpc_bdev_lvol_get_fragmap, SPDK_RPC_RUNTIME)

View File

@ -249,6 +249,25 @@ def bdev_lvol_shallow_copy_status(client, src_lvol_name):
return client.call('bdev_lvol_shallow_copy_status', params)
def bdev_lvol_get_fragmap(client, name, offset=0, size=0):
"""Get a fragmap for a specific segment of a logical volume using the provided offset and size
Args:
name: lvol bdev name
offset: offset in bytes of the specific segment of the logical volume
size: size in bytes of the specific segment of the logical volume
"""
params = {
'name': name,
}
if offset:
params['offset'] = offset
if size:
params['size'] = size
return client.call('bdev_lvol_get_fragmap', params)
def bdev_lvol_delete_lvstore(client, uuid=None, lvs_name=None):
"""Destroy a logical volume store.

View File

@ -2059,6 +2059,17 @@ Format: 'user:u1 secret:s1 muser:mu1 msecret:ms1,user:u2 secret:s2 muser:mu2 mse
p.add_argument('src_lvol_name', help='source lvol name')
p.set_defaults(func=bdev_lvol_shallow_copy_status)
def bdev_lvol_get_fragmap(args):
print_json(rpc.lvol.bdev_lvol_get_fragmap(args.client,
name=args.name,
offset=args.offset,
size=args.size))
p = subparsers.add_parser('bdev_lvol_get_fragmap', help='Get a fragmap for a specific segment of a logical volume using the provided offset and size.')
p.add_argument('name', help='lvol bdev name')
p.add_argument('--offset', help='offset in bytes of the specific segment of the logical volume', type=int, required=False)
p.add_argument('--size', help='size in bytes of the specific segment of the logical volume', type=int, required=False)
p.set_defaults(func=bdev_lvol_get_fragmap)
def bdev_lvol_delete_lvstore(args):
rpc.lvol.bdev_lvol_delete_lvstore(args.client,
uuid=args.uuid,

52
test/lvol/fragmap.sh Executable file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: BSD-3-Clause
# Copyright (C) 2023 SUSE LLC.
# All rights reserved.
#
testdir=$(readlink -f $(dirname $0))
rootdir=$(readlink -f $testdir/../..)
source $rootdir/test/common/autotest_common.sh
source $rootdir/test/lvol/common.sh
source $rootdir/test/bdev/nbd_common.sh
function test_fragmap() {
# Create lvs
bs_malloc_name=$(rpc_cmd bdev_malloc_create 20 $MALLOC_BS)
lvs_uuid=$(rpc_cmd bdev_lvol_create_lvstore "$bs_malloc_name" lvs_test)
# Create lvol with 4 cluster
lvol_size=$((LVS_DEFAULT_CLUSTER_SIZE_MB * 4))
lvol_uuid=$(rpc_cmd bdev_lvol_create -u "$lvs_uuid" lvol_test "$lvol_size" -t)
# Fill second and fourth cluster of lvol
nbd_start_disks "$DEFAULT_RPC_ADDR" "$lvol_uuid" /dev/nbd0
dd if=/dev/urandom of=/dev/nbd0 oflag=direct bs="$LVS_DEFAULT_CLUSTER_SIZE" count=1 seek=1
dd if=/dev/urandom of=/dev/nbd0 oflag=direct bs="$LVS_DEFAULT_CLUSTER_SIZE" count=1 seek=3
nbd_stop_disks "$DEFAULT_RPC_ADDR" /dev/nbd0
# Create snapshots of lvol bdev
snapshot_uuid=$(rpc_cmd bdev_lvol_snapshot lvs_test/lvol_test lvol_snapshot)
# Fill first and third cluster of lvol
nbd_start_disks "$DEFAULT_RPC_ADDR" "$lvol_uuid" /dev/nbd0
dd if=/dev/urandom of=/dev/nbd0 oflag=direct bs="$LVS_DEFAULT_CLUSTER_SIZE" count=1
dd if=/dev/urandom of=/dev/nbd0 oflag=direct bs="$LVS_DEFAULT_CLUSTER_SIZE" count=1 seek=2
nbd_stop_disks "$DEFAULT_RPC_ADDR" /dev/nbd0
# Stop nbd devices
nbd_stop_disks "$DEFAULT_RPC_ADDR" /dev/nbd1
nbd_stop_disks "$DEFAULT_RPC_ADDR" /dev/nbd0
}
$SPDK_BIN_DIR/spdk_tgt &
spdk_pid=$!
trap 'killprocess "$spdk_pid"; exit 1' SIGINT SIGTERM EXIT
waitforlisten $spdk_pid
modprobe nbd
run_test "test_shallow_copy_compare" test_shallow_copy_compare
trap - SIGINT SIGTERM EXIT
killprocess $spdk_pid