lvol: keep track of missing external snapshots
If an lvol is opened in degraded mode, keep track of the missing esnap IDs and which lvols need them. A future commit will make use of this information to bring lvols out of degraded mode when their external snapshot device appears. Change-Id: I55c16ad042a73e46e225369bfff2631958a2ed46 Signed-off-by: Mike Gerdts <mgerdts@nvidia.com> Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/16427 Reviewed-by: Jim Harris <james.r.harris@intel.com> Reviewed-by: Ben Walker <benjamin.walker@intel.com> Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
This commit is contained in:
parent
87666f5286
commit
f2dbb50516
@ -10,6 +10,7 @@
|
||||
#include "spdk/blob.h"
|
||||
#include "spdk/lvol.h"
|
||||
#include "spdk/queue.h"
|
||||
#include "spdk/tree.h"
|
||||
#include "spdk/uuid.h"
|
||||
|
||||
/* Default size of blobstore cluster */
|
||||
@ -38,6 +39,8 @@ struct spdk_lvol_req {
|
||||
spdk_lvol_op_complete cb_fn;
|
||||
void *cb_arg;
|
||||
struct spdk_lvol *lvol;
|
||||
/* Only set while lvol is being deleted and has a clone. */
|
||||
struct spdk_lvol *clone_lvol;
|
||||
size_t sz;
|
||||
struct spdk_io_channel *channel;
|
||||
char name[SPDK_LVOL_NAME_MAX];
|
||||
@ -62,8 +65,11 @@ struct spdk_lvol_with_handle_req {
|
||||
spdk_lvol_op_with_handle_complete cb_fn;
|
||||
void *cb_arg;
|
||||
struct spdk_lvol *lvol;
|
||||
struct spdk_lvol *origlvol;
|
||||
};
|
||||
|
||||
struct spdk_lvs_degraded_lvol_set;
|
||||
|
||||
struct spdk_lvol_store {
|
||||
struct spdk_bs_dev *bs_dev;
|
||||
struct spdk_blob_store *blobstore;
|
||||
@ -81,6 +87,8 @@ struct spdk_lvol_store {
|
||||
char name[SPDK_LVS_NAME_MAX];
|
||||
char new_name[SPDK_LVS_NAME_MAX];
|
||||
spdk_bs_esnap_dev_create esnap_bs_dev_create;
|
||||
RB_HEAD(degraded_lvol_sets_tree, spdk_lvs_degraded_lvol_set) degraded_lvol_sets_tree;
|
||||
struct spdk_thread *thread;
|
||||
};
|
||||
|
||||
struct spdk_lvol {
|
||||
@ -95,7 +103,9 @@ struct spdk_lvol {
|
||||
int ref_count;
|
||||
bool action_in_progress;
|
||||
enum blob_clear_method clear_method;
|
||||
TAILQ_ENTRY(spdk_lvol) link;
|
||||
TAILQ_ENTRY(spdk_lvol) link;
|
||||
struct spdk_lvs_degraded_lvol_set *degraded_set;
|
||||
TAILQ_ENTRY(spdk_lvol) degraded_link;
|
||||
};
|
||||
|
||||
struct lvol_store_bdev *vbdev_lvol_store_first(void);
|
||||
@ -107,4 +117,8 @@ void spdk_lvol_resize(struct spdk_lvol *lvol, uint64_t sz, spdk_lvol_op_complete
|
||||
void spdk_lvol_set_read_only(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn,
|
||||
void *cb_arg);
|
||||
|
||||
int spdk_lvs_esnap_missing_add(struct spdk_lvol_store *lvs, struct spdk_lvol *lvol,
|
||||
const void *esnap_id, uint32_t id_len);
|
||||
void spdk_lvs_esnap_missing_remove(struct spdk_lvol *lvol);
|
||||
|
||||
#endif /* SPDK_INTERNAL_LVOLSTORE_H */
|
||||
|
181
lib/lvol/lvol.c
181
lib/lvol/lvol.c
@ -9,6 +9,7 @@
|
||||
#include "spdk/string.h"
|
||||
#include "spdk/thread.h"
|
||||
#include "spdk/blob_bdev.h"
|
||||
#include "spdk/tree.h"
|
||||
#include "spdk/util.h"
|
||||
|
||||
/* Default blob channel opts for lvol */
|
||||
@ -18,6 +19,14 @@
|
||||
|
||||
SPDK_LOG_REGISTER_COMPONENT(lvol)
|
||||
|
||||
struct spdk_lvs_degraded_lvol_set {
|
||||
struct spdk_lvol_store *lvol_store;
|
||||
const void *esnap_id;
|
||||
uint32_t id_len;
|
||||
TAILQ_HEAD(degraded_lvols, spdk_lvol) lvols;
|
||||
RB_ENTRY(spdk_lvs_degraded_lvol_set) node;
|
||||
};
|
||||
|
||||
static TAILQ_HEAD(, spdk_lvol_store) g_lvol_stores = TAILQ_HEAD_INITIALIZER(g_lvol_stores);
|
||||
static pthread_mutex_t g_lvol_stores_mutex = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
@ -25,6 +34,11 @@ static inline int lvs_opts_copy(const struct spdk_lvs_opts *src, struct spdk_lvs
|
||||
static int lvs_esnap_bs_dev_create(void *bs_ctx, void *blob_ctx, struct spdk_blob *blob,
|
||||
const void *esnap_id, uint32_t id_len,
|
||||
struct spdk_bs_dev **_bs_dev);
|
||||
static struct spdk_lvol *lvs_get_lvol_by_blob_id(struct spdk_lvol_store *lvs, spdk_blob_id blob_id);
|
||||
static void lvs_degraded_lvol_set_add(struct spdk_lvs_degraded_lvol_set *degraded_set,
|
||||
struct spdk_lvol *lvol);
|
||||
static void lvs_degraded_lvol_set_remove(struct spdk_lvs_degraded_lvol_set *degraded_set,
|
||||
struct spdk_lvol *lvol);
|
||||
|
||||
static int
|
||||
add_lvs_to_list(struct spdk_lvol_store *lvs)
|
||||
@ -63,6 +77,8 @@ lvs_alloc(void)
|
||||
TAILQ_INIT(&lvs->retry_open_lvols);
|
||||
|
||||
lvs->load_esnaps = false;
|
||||
RB_INIT(&lvs->degraded_lvol_sets_tree);
|
||||
lvs->thread = spdk_get_thread();
|
||||
|
||||
return lvs;
|
||||
}
|
||||
@ -76,6 +92,8 @@ lvs_free(struct spdk_lvol_store *lvs)
|
||||
}
|
||||
pthread_mutex_unlock(&g_lvol_stores_mutex);
|
||||
|
||||
assert(RB_EMPTY(&lvs->degraded_lvol_sets_tree));
|
||||
|
||||
free(lvs);
|
||||
}
|
||||
|
||||
@ -321,7 +339,7 @@ lvs_read_uuid(void *cb_arg, struct spdk_blob *blob, int lvolerrno)
|
||||
|
||||
rc = spdk_blob_get_xattr_value(blob, "uuid", (const void **)&attr, &value_len);
|
||||
if (rc != 0 || value_len != SPDK_UUID_STRING_LEN || attr[SPDK_UUID_STRING_LEN - 1] != '\0') {
|
||||
SPDK_INFOLOG(lvol, "missing or incorrect UUID\n");
|
||||
SPDK_INFOLOG(lvol, "degraded_set or incorrect UUID\n");
|
||||
req->lvserrno = -EINVAL;
|
||||
spdk_blob_close(blob, close_super_blob_with_error_cb, req);
|
||||
return;
|
||||
@ -336,7 +354,7 @@ lvs_read_uuid(void *cb_arg, struct spdk_blob *blob, int lvolerrno)
|
||||
|
||||
rc = spdk_blob_get_xattr_value(blob, "name", (const void **)&attr, &value_len);
|
||||
if (rc != 0 || value_len > SPDK_LVS_NAME_MAX) {
|
||||
SPDK_INFOLOG(lvol, "missing or invalid name\n");
|
||||
SPDK_INFOLOG(lvol, "degraded_set or invalid name\n");
|
||||
req->lvserrno = -EINVAL;
|
||||
spdk_blob_close(blob, close_super_blob_with_error_cb, req);
|
||||
return;
|
||||
@ -878,6 +896,7 @@ spdk_lvs_unload(struct spdk_lvol_store *lvs, spdk_lvs_op_complete cb_fn,
|
||||
}
|
||||
|
||||
TAILQ_FOREACH_SAFE(lvol, &lvs->lvols, link, tmp) {
|
||||
spdk_lvs_esnap_missing_remove(lvol);
|
||||
TAILQ_REMOVE(&lvs->lvols, lvol, link);
|
||||
lvol_free(lvol);
|
||||
}
|
||||
@ -1002,6 +1021,7 @@ lvol_delete_blob_cb(void *cb_arg, int lvolerrno)
|
||||
{
|
||||
struct spdk_lvol_req *req = cb_arg;
|
||||
struct spdk_lvol *lvol = req->lvol;
|
||||
struct spdk_lvol *clone_lvol = req->clone_lvol;
|
||||
|
||||
if (lvolerrno < 0) {
|
||||
SPDK_ERRLOG("Could not remove blob on lvol gracefully - forced removal\n");
|
||||
@ -1009,6 +1029,22 @@ lvol_delete_blob_cb(void *cb_arg, int lvolerrno)
|
||||
SPDK_INFOLOG(lvol, "Lvol %s deleted\n", lvol->unique_id);
|
||||
}
|
||||
|
||||
if (lvol->degraded_set != NULL) {
|
||||
if (clone_lvol != NULL) {
|
||||
/*
|
||||
* A degraded esnap clone that has a blob clone has been deleted. clone_lvol
|
||||
* becomes an esnap clone and needs to be associated with the
|
||||
* spdk_lvs_degraded_lvol_set.
|
||||
*/
|
||||
struct spdk_lvs_degraded_lvol_set *degraded_set = lvol->degraded_set;
|
||||
|
||||
lvs_degraded_lvol_set_remove(degraded_set, lvol);
|
||||
lvs_degraded_lvol_set_add(degraded_set, clone_lvol);
|
||||
} else {
|
||||
spdk_lvs_esnap_missing_remove(lvol);
|
||||
}
|
||||
}
|
||||
|
||||
TAILQ_REMOVE(&lvol->lvol_store->lvols, lvol, link);
|
||||
lvol_free(lvol);
|
||||
req->cb_fn(req->cb_arg, lvolerrno);
|
||||
@ -1072,6 +1108,18 @@ lvol_create_cb(void *cb_arg, spdk_blob_id blobid, int lvolerrno)
|
||||
opts.esnap_ctx = req->lvol;
|
||||
bs = req->lvol->lvol_store->blobstore;
|
||||
|
||||
if (req->origlvol != NULL && req->origlvol->degraded_set != NULL) {
|
||||
/*
|
||||
* A snapshot was created from a degraded esnap clone. The new snapshot is now a
|
||||
* degraded esnap clone. The previous clone is now a regular clone of a blob. Update
|
||||
* the set of directly-related clones to the missing external snapshot.
|
||||
*/
|
||||
struct spdk_lvs_degraded_lvol_set *degraded_set = req->origlvol->degraded_set;
|
||||
|
||||
lvs_degraded_lvol_set_remove(degraded_set, req->origlvol);
|
||||
lvs_degraded_lvol_set_add(degraded_set, req->lvol);
|
||||
}
|
||||
|
||||
spdk_bs_open_blob_ext(bs, blobid, &opts, lvol_create_open_cb, req);
|
||||
}
|
||||
|
||||
@ -1290,6 +1338,7 @@ spdk_lvol_create_snapshot(struct spdk_lvol *origlvol, const char *snapshot_name,
|
||||
snapshot_xattrs.names = xattr_names;
|
||||
snapshot_xattrs.get_value = lvol_get_xattr_value;
|
||||
req->lvol = newlvol;
|
||||
req->origlvol = origlvol;
|
||||
req->cb_fn = cb_fn;
|
||||
req->cb_arg = cb_arg;
|
||||
|
||||
@ -1496,6 +1545,10 @@ spdk_lvol_destroy(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_
|
||||
{
|
||||
struct spdk_lvol_req *req;
|
||||
struct spdk_blob_store *bs;
|
||||
struct spdk_lvol_store *lvs = lvol->lvol_store;
|
||||
spdk_blob_id clone_id;
|
||||
size_t count = 1;
|
||||
int rc;
|
||||
|
||||
assert(cb_fn != NULL);
|
||||
|
||||
@ -1525,6 +1578,18 @@ spdk_lvol_destroy(struct spdk_lvol *lvol, spdk_lvol_op_complete cb_fn, void *cb_
|
||||
req->lvol = lvol;
|
||||
bs = lvol->lvol_store->blobstore;
|
||||
|
||||
rc = spdk_blob_get_clones(lvs->blobstore, lvol->blob_id, &clone_id, &count);
|
||||
if (rc == 0 && count == 1) {
|
||||
req->clone_lvol = lvs_get_lvol_by_blob_id(lvs, clone_id);
|
||||
} else if (rc == -ENOMEM) {
|
||||
SPDK_INFOLOG(lvol, "lvol %s: cannot destroy: has %" PRIu64 " clones\n",
|
||||
lvol->unique_id, count);
|
||||
free(req);
|
||||
assert(count > 1);
|
||||
cb_fn(cb_arg, -EBUSY);
|
||||
return;
|
||||
}
|
||||
|
||||
spdk_bs_delete_blob(bs, lvol->blob_id, lvol_delete_blob_cb, req);
|
||||
}
|
||||
|
||||
@ -1758,3 +1823,115 @@ lvs_esnap_bs_dev_create(void *bs_ctx, void *blob_ctx, struct spdk_blob *blob,
|
||||
|
||||
return lvs->esnap_bs_dev_create(lvs, lvol, blob, esnap_id, id_len, bs_dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* The theory of missing external snapshots
|
||||
*
|
||||
* The lvs->esnap_bs_dev_create() callback may be unable to create an external snapshot bs_dev when
|
||||
* it is called. This can happen, for instance, as when the device containing the lvolstore is
|
||||
* examined prior to spdk_bdev_register() being called on a bdev that acts as an external snapshot.
|
||||
* In such a case, the esnap_bs_dev_create() callback will call spdk_lvs_esnap_missing_add().
|
||||
*
|
||||
* Missing external snapshots are tracked in a per-lvolstore tree, lvs->degraded_lvol_sets_tree.
|
||||
* Each tree node (struct spdk_lvs_degraded_lvol_set) contains a tailq of lvols that are missing
|
||||
* that particular external snapshot.
|
||||
*/
|
||||
static int
|
||||
lvs_esnap_name_cmp(struct spdk_lvs_degraded_lvol_set *m1, struct spdk_lvs_degraded_lvol_set *m2)
|
||||
{
|
||||
if (m1->id_len == m2->id_len) {
|
||||
return memcmp(m1->esnap_id, m2->esnap_id, m1->id_len);
|
||||
}
|
||||
return (m1->id_len > m2->id_len) ? 1 : -1;
|
||||
}
|
||||
|
||||
RB_GENERATE_STATIC(degraded_lvol_sets_tree, spdk_lvs_degraded_lvol_set, node, lvs_esnap_name_cmp)
|
||||
|
||||
static void
|
||||
lvs_degraded_lvol_set_add(struct spdk_lvs_degraded_lvol_set *degraded_set, struct spdk_lvol *lvol)
|
||||
{
|
||||
assert(lvol->lvol_store->thread == spdk_get_thread());
|
||||
|
||||
lvol->degraded_set = degraded_set;
|
||||
TAILQ_INSERT_TAIL(°raded_set->lvols, lvol, degraded_link);
|
||||
}
|
||||
|
||||
static void
|
||||
lvs_degraded_lvol_set_remove(struct spdk_lvs_degraded_lvol_set *degraded_set,
|
||||
struct spdk_lvol *lvol)
|
||||
{
|
||||
assert(lvol->lvol_store->thread == spdk_get_thread());
|
||||
|
||||
lvol->degraded_set = NULL;
|
||||
TAILQ_REMOVE(°raded_set->lvols, lvol, degraded_link);
|
||||
/* degraded_set->lvols may be empty. Caller should check if not immediately adding a new
|
||||
* lvol. */
|
||||
}
|
||||
|
||||
/*
|
||||
* Record in lvs->degraded_lvol_sets_tree that a bdev of the specified name is needed by the
|
||||
* specified lvol.
|
||||
*/
|
||||
int
|
||||
spdk_lvs_esnap_missing_add(struct spdk_lvol_store *lvs, struct spdk_lvol *lvol,
|
||||
const void *esnap_id, uint32_t id_len)
|
||||
{
|
||||
struct spdk_lvs_degraded_lvol_set find, *degraded_set;
|
||||
|
||||
assert(lvs->thread == spdk_get_thread());
|
||||
|
||||
find.esnap_id = esnap_id;
|
||||
find.id_len = id_len;
|
||||
degraded_set = RB_FIND(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree, &find);
|
||||
if (degraded_set == NULL) {
|
||||
degraded_set = calloc(1, sizeof(*degraded_set));
|
||||
if (degraded_set == NULL) {
|
||||
SPDK_ERRLOG("lvol %s: cannot create degraded_set node: out of memory\n",
|
||||
lvol->unique_id);
|
||||
return -ENOMEM;
|
||||
}
|
||||
degraded_set->esnap_id = calloc(1, id_len);
|
||||
if (degraded_set->esnap_id == NULL) {
|
||||
free(degraded_set);
|
||||
SPDK_ERRLOG("lvol %s: cannot create degraded_set node: out of memory\n",
|
||||
lvol->unique_id);
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy((void *)degraded_set->esnap_id, esnap_id, id_len);
|
||||
degraded_set->id_len = id_len;
|
||||
degraded_set->lvol_store = lvs;
|
||||
TAILQ_INIT(°raded_set->lvols);
|
||||
RB_INSERT(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree, degraded_set);
|
||||
}
|
||||
|
||||
lvs_degraded_lvol_set_add(degraded_set, lvol);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove the record of the specified lvol needing a degraded_set bdev.
|
||||
*/
|
||||
void
|
||||
spdk_lvs_esnap_missing_remove(struct spdk_lvol *lvol)
|
||||
{
|
||||
struct spdk_lvol_store *lvs = lvol->lvol_store;
|
||||
struct spdk_lvs_degraded_lvol_set *degraded_set = lvol->degraded_set;
|
||||
|
||||
assert(lvs->thread == spdk_get_thread());
|
||||
|
||||
if (degraded_set == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
lvs_degraded_lvol_set_remove(degraded_set, lvol);
|
||||
|
||||
if (!TAILQ_EMPTY(°raded_set->lvols)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RB_REMOVE(degraded_lvol_sets_tree, &lvs->degraded_lvol_sets_tree, degraded_set);
|
||||
|
||||
free((char *)degraded_set->esnap_id);
|
||||
free(degraded_set);
|
||||
}
|
||||
|
@ -26,6 +26,8 @@
|
||||
# internal functions
|
||||
spdk_lvol_resize;
|
||||
spdk_lvol_set_read_only;
|
||||
spdk_lvs_esnap_missing_add;
|
||||
spdk_lvs_esnap_missing_remove;
|
||||
|
||||
local: *;
|
||||
};
|
||||
|
@ -52,7 +52,7 @@ DEPDIRS-accel := log util thread json rpc jsonrpc dma
|
||||
DEPDIRS-jsonrpc := log util json
|
||||
DEPDIRS-virtio := log util json thread vfio_user
|
||||
|
||||
DEPDIRS-lvol := log util blob
|
||||
DEPDIRS-lvol := log util blob thread
|
||||
DEPDIRS-rpc := log util json jsonrpc
|
||||
|
||||
DEPDIRS-net := log util $(JSON_LIBS)
|
||||
|
@ -734,6 +734,18 @@ vbdev_lvol_dump_info_json(void *ctx, struct spdk_json_write_ctx *w)
|
||||
|
||||
}
|
||||
|
||||
spdk_json_write_named_bool(w, "esnap_clone", spdk_blob_is_esnap_clone(blob));
|
||||
|
||||
if (spdk_blob_is_esnap_clone(blob)) {
|
||||
const char *name;
|
||||
size_t name_len;
|
||||
|
||||
rc = spdk_blob_get_esnap_id(blob, (const void **)&name, &name_len);
|
||||
if (rc == 0 && name != NULL && strnlen(name, name_len) + 1 == name_len) {
|
||||
spdk_json_write_named_string(w, "external_snapshot_name", name);
|
||||
}
|
||||
}
|
||||
|
||||
end:
|
||||
spdk_json_write_object_end(w);
|
||||
|
||||
|
@ -39,6 +39,9 @@ bool g_ext_api_called;
|
||||
DEFINE_STUB_V(spdk_bdev_module_fini_start_done, (void));
|
||||
DEFINE_STUB(spdk_bdev_get_memory_domains, int, (struct spdk_bdev *bdev,
|
||||
struct spdk_memory_domain **domains, int array_size), 0);
|
||||
DEFINE_STUB(spdk_blob_get_esnap_id, int,
|
||||
(struct spdk_blob *blob, const void **id, size_t *len), -ENOTSUP);
|
||||
DEFINE_STUB(spdk_blob_is_esnap_clone, bool, (const struct spdk_blob *blob), false);
|
||||
|
||||
const struct spdk_bdev_aliases_list *
|
||||
spdk_bdev_get_aliases(const struct spdk_bdev *bdev)
|
||||
|
@ -28,11 +28,13 @@
|
||||
|
||||
#define SPDK_BLOB_THIN_PROV (1ULL << 0)
|
||||
|
||||
|
||||
DEFINE_STUB(spdk_bdev_get_name, const char *, (const struct spdk_bdev *bdev), NULL);
|
||||
DEFINE_STUB(spdk_bdev_get_by_name, struct spdk_bdev *, (const char *name), NULL);
|
||||
DEFINE_STUB(spdk_bdev_create_bs_dev_ro, int,
|
||||
(const char *bdev_name, spdk_bdev_event_cb_t event_cb, void *event_ctx,
|
||||
struct spdk_bs_dev **bs_dev), -ENOTSUP);
|
||||
DEFINE_STUB(spdk_blob_is_esnap_clone, bool, (const struct spdk_blob *blob), false);
|
||||
|
||||
const char *uuid = "828d9766-ae50-11e7-bd8d-001e67edf350";
|
||||
|
||||
@ -235,8 +237,6 @@ spdk_blob_is_thin_provisioned(struct spdk_blob *blob)
|
||||
return blob->thin_provisioned;
|
||||
}
|
||||
|
||||
DEFINE_STUB(spdk_blob_get_clones, int, (struct spdk_blob_store *bs, spdk_blob_id blobid,
|
||||
spdk_blob_id *ids, size_t *count), 0);
|
||||
DEFINE_STUB(spdk_bs_get_page_size, uint64_t, (struct spdk_blob_store *bs), BS_PAGE_SIZE);
|
||||
|
||||
int
|
||||
@ -509,6 +509,25 @@ spdk_blob_get_esnap_id(struct spdk_blob *blob, const void **id, size_t *len)
|
||||
return g_spdk_blob_get_esnap_id_errno;
|
||||
}
|
||||
|
||||
static spdk_blob_id g_spdk_blob_get_clones_snap_id = 0xbad;
|
||||
static size_t g_spdk_blob_get_clones_count;
|
||||
static spdk_blob_id *g_spdk_blob_get_clones_ids;
|
||||
int
|
||||
spdk_blob_get_clones(struct spdk_blob_store *bs, spdk_blob_id blob_id, spdk_blob_id *ids,
|
||||
size_t *count)
|
||||
{
|
||||
if (blob_id != g_spdk_blob_get_clones_snap_id) {
|
||||
*count = 0;
|
||||
return 0;
|
||||
}
|
||||
if (ids == NULL || *count < g_spdk_blob_get_clones_count) {
|
||||
*count = g_spdk_blob_get_clones_count;
|
||||
return -ENOMEM;
|
||||
}
|
||||
memcpy(ids, g_spdk_blob_get_clones_ids, g_spdk_blob_get_clones_count * sizeof(*ids));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
lvol_store_op_with_handle_complete(void *cb_arg, struct spdk_lvol_store *lvol_store, int lvserrno)
|
||||
{
|
||||
@ -543,11 +562,21 @@ op_complete(void *cb_arg, int lvserrno)
|
||||
static struct ut_cb_res *
|
||||
ut_cb_res_clear(struct ut_cb_res *res)
|
||||
{
|
||||
memset(res, 0, sizeof(*res));
|
||||
res->data = (void *)(uintptr_t)(-1);
|
||||
res->err = 0xbad;
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool
|
||||
ut_cb_res_untouched(const struct ut_cb_res *res)
|
||||
{
|
||||
struct ut_cb_res pristine;
|
||||
|
||||
ut_cb_res_clear(&pristine);
|
||||
return !memcmp(&pristine, res, sizeof(pristine));
|
||||
}
|
||||
|
||||
static void
|
||||
lvs_init_unload_success(void)
|
||||
{
|
||||
@ -2435,6 +2464,169 @@ lvol_esnap_load_esnaps(void)
|
||||
g_esnap_bs_dev_errno = -ENOTSUP;
|
||||
}
|
||||
|
||||
struct ut_degraded_dev {
|
||||
struct spdk_bs_dev bs_dev;
|
||||
struct spdk_lvol *lvol;
|
||||
};
|
||||
|
||||
static void
|
||||
ut_destroy_degraded(struct spdk_bs_dev *ddev)
|
||||
{
|
||||
free(ddev);
|
||||
}
|
||||
|
||||
static int
|
||||
ut_create_degraded(struct spdk_lvol_store *lvs, struct spdk_lvol *lvol,
|
||||
struct spdk_blob *blob, const char *name, struct spdk_bs_dev **bs_dev)
|
||||
{
|
||||
struct ut_degraded_dev *ddev;
|
||||
|
||||
ddev = calloc(1, sizeof(*ddev));
|
||||
SPDK_CU_ASSERT_FATAL(ddev != NULL);
|
||||
|
||||
ddev->lvol = lvol;
|
||||
ddev->bs_dev.destroy = ut_destroy_degraded;
|
||||
ddev->bs_dev.blockcnt = UINT64_MAX / 512;
|
||||
ddev->bs_dev.blocklen = 512;
|
||||
*bs_dev = &ddev->bs_dev;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
lvol_esnap_missing(void)
|
||||
{
|
||||
struct lvol_ut_bs_dev dev;
|
||||
struct spdk_lvs_opts opts;
|
||||
struct spdk_blob blob = { .id = 42 };
|
||||
struct ut_cb_res cb_res;
|
||||
struct spdk_lvol_store *lvs;
|
||||
struct spdk_lvol *lvol1, *lvol2;
|
||||
struct spdk_bs_dev *bs_dev;
|
||||
struct spdk_bdev esnap_bdev;
|
||||
struct spdk_lvs_degraded_lvol_set *degraded_set;
|
||||
const char *name1 = "lvol1";
|
||||
const char *name2 = "lvol2";
|
||||
char uuid_str[SPDK_UUID_STRING_LEN];
|
||||
int rc;
|
||||
|
||||
/* Create an lvstore */
|
||||
init_dev(&dev);
|
||||
spdk_lvs_opts_init(&opts);
|
||||
snprintf(opts.name, sizeof(opts.name), "lvs");
|
||||
g_lvserrno = -1;
|
||||
rc = spdk_lvs_init(&dev.bs_dev, &opts, lvol_store_op_with_handle_complete, NULL);
|
||||
CU_ASSERT(rc == 0);
|
||||
CU_ASSERT(g_lvserrno == 0);
|
||||
SPDK_CU_ASSERT_FATAL(g_lvol_store != NULL);
|
||||
lvs = g_lvol_store;
|
||||
lvs->load_esnaps = true;
|
||||
|
||||
/* Pre-populate the lvstore with a degraded device */
|
||||
lvol1 = lvol_alloc(lvs, name1, true, LVOL_CLEAR_WITH_DEFAULT);
|
||||
SPDK_CU_ASSERT_FATAL(lvol1 != NULL);
|
||||
lvol1->blob_id = blob.id;
|
||||
TAILQ_REMOVE(&lvs->pending_lvols, lvol1, link);
|
||||
TAILQ_INSERT_TAIL(&lvs->lvols, lvol1, link);
|
||||
rc = ut_create_degraded(lvs, lvol1, &blob, name1, &bs_dev);
|
||||
CU_ASSERT(rc == 0);
|
||||
SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
|
||||
|
||||
/* A clone with a missing external snapshot prevents a conflicting clone's creation */
|
||||
init_bdev(&esnap_bdev, "bdev1", BS_CLUSTER_SIZE);
|
||||
CU_ASSERT(spdk_uuid_fmt_lower(uuid_str, sizeof(uuid_str), &esnap_bdev.uuid) == 0);
|
||||
MOCK_SET(spdk_bdev_get_by_name, &esnap_bdev);
|
||||
rc = spdk_lvol_create_esnap_clone(uuid_str, sizeof(uuid_str), 1, g_lvol_store, name1,
|
||||
lvol_op_with_handle_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(rc == -EEXIST);
|
||||
CU_ASSERT(ut_cb_res_untouched(&cb_res));
|
||||
MOCK_CLEAR(spdk_bdev_get_by_name);
|
||||
|
||||
/* A clone with a missing external snapshot prevents a conflicting lvol's creation */
|
||||
rc = spdk_lvol_create(lvs, name1, 10, false, LVOL_CLEAR_WITH_DEFAULT,
|
||||
lvol_op_with_handle_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(rc == -EEXIST);
|
||||
CU_ASSERT(ut_cb_res_untouched(&cb_res));
|
||||
|
||||
/* Using a unique lvol name allows the clone to be created. */
|
||||
MOCK_SET(spdk_bdev_get_by_name, &esnap_bdev);
|
||||
MOCK_SET(spdk_blob_is_esnap_clone, true);
|
||||
rc = spdk_lvol_create_esnap_clone(uuid_str, sizeof(uuid_str), 1, g_lvol_store, name2,
|
||||
lvol_op_with_handle_complete, ut_cb_res_clear(&cb_res));
|
||||
SPDK_CU_ASSERT_FATAL(rc == 0);
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
SPDK_CU_ASSERT_FATAL(cb_res.data != NULL);
|
||||
lvol2 = cb_res.data;
|
||||
CU_ASSERT(lvol2->degraded_set == NULL);
|
||||
spdk_lvol_close(lvol2, op_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
spdk_lvol_destroy(lvol2, op_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
MOCK_CLEAR(spdk_blob_is_esnap_clone);
|
||||
MOCK_CLEAR(spdk_bdev_get_by_name);
|
||||
|
||||
/* Destroying the esnap clone removes it from the degraded_set esnaps tree. */
|
||||
spdk_lvol_destroy(lvol1, op_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
CU_ASSERT(RB_EMPTY(&lvs->degraded_lvol_sets_tree));
|
||||
bs_dev->destroy(bs_dev);
|
||||
|
||||
/* Create a missing device again */
|
||||
lvol1 = lvol_alloc(lvs, name1, true, LVOL_CLEAR_WITH_DEFAULT);
|
||||
SPDK_CU_ASSERT_FATAL(lvol1 != NULL);
|
||||
lvol1->blob_id = blob.id;
|
||||
TAILQ_REMOVE(&lvs->pending_lvols, lvol1, link);
|
||||
TAILQ_INSERT_TAIL(&lvs->lvols, lvol1, link);
|
||||
rc = ut_create_degraded(lvs, lvol1, &blob, name1, &bs_dev);
|
||||
CU_ASSERT(rc == 0);
|
||||
SPDK_CU_ASSERT_FATAL(bs_dev != NULL);
|
||||
lvol1->blob = &blob;
|
||||
rc = spdk_lvs_esnap_missing_add(lvs, lvol1, esnap_bdev.name, strlen(esnap_bdev.name) + 1);
|
||||
CU_ASSERT(rc == 0);
|
||||
lvol1->ref_count = 1;
|
||||
|
||||
/*
|
||||
* Creating a snapshot of lvol1 makes lvol1 a clone of the new snapshot. What was a clone of
|
||||
* the external snapshot is now a clone of the snapshot. The snapshot is a clone of the
|
||||
* external snapshot. Now the snapshot is degraded_set its external snapshot.
|
||||
*/
|
||||
degraded_set = lvol1->degraded_set;
|
||||
CU_ASSERT(degraded_set != NULL);
|
||||
spdk_lvol_create_snapshot(lvol1, name2, lvol_op_with_handle_complete,
|
||||
ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
SPDK_CU_ASSERT_FATAL(cb_res.data != NULL);
|
||||
lvol2 = cb_res.data;
|
||||
CU_ASSERT(lvol1->degraded_set == NULL);
|
||||
CU_ASSERT(lvol2->degraded_set == degraded_set);
|
||||
|
||||
/*
|
||||
* Removing the snapshot (lvol2) makes the first lvol (lvol1) back into a clone of an
|
||||
* external snapshot.
|
||||
*/
|
||||
MOCK_SET(spdk_blob_is_esnap_clone, true);
|
||||
g_spdk_blob_get_clones_snap_id = lvol2->blob_id;
|
||||
g_spdk_blob_get_clones_ids = &lvol1->blob_id;
|
||||
g_spdk_blob_get_clones_count = 1;
|
||||
spdk_lvol_close(lvol2, op_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
spdk_lvol_destroy(lvol2, op_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
CU_ASSERT(lvol1->degraded_set == degraded_set);
|
||||
g_spdk_blob_get_clones_snap_id = 0xbad;
|
||||
g_spdk_blob_get_clones_ids = NULL;
|
||||
g_spdk_blob_get_clones_count = 0;
|
||||
|
||||
/* Clean up */
|
||||
spdk_lvol_close(lvol1, op_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
spdk_lvol_destroy(lvol1, op_complete, ut_cb_res_clear(&cb_res));
|
||||
CU_ASSERT(cb_res.err == 0);
|
||||
bs_dev->destroy(bs_dev);
|
||||
rc = spdk_lvs_destroy(g_lvol_store, op_complete, NULL);
|
||||
CU_ASSERT(rc == 0);
|
||||
MOCK_CLEAR(spdk_blob_is_esnap_clone);
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
@ -2477,6 +2669,7 @@ main(int argc, char **argv)
|
||||
CU_ADD_TEST(suite, lvol_esnap_create_bad_args);
|
||||
CU_ADD_TEST(suite, lvol_esnap_create_delete);
|
||||
CU_ADD_TEST(suite, lvol_esnap_load_esnaps);
|
||||
CU_ADD_TEST(suite, lvol_esnap_missing);
|
||||
|
||||
allocate_threads(1);
|
||||
set_thread(0);
|
||||
|
Loading…
Reference in New Issue
Block a user