Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

librbd: deferred image deletion #13105

Merged
merged 12 commits into from
Apr 11, 2017
Merged
72 changes: 72 additions & 0 deletions qa/workunits/rbd/cli_generic.sh
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,77 @@ test_clone() {
rados rmpool rbd2 rbd2 --yes-i-really-really-mean-it
}

test_trash() {
echo "testing trash..."
remove_images

rbd create --image-format 2 -s 1 test1
rbd create --image-format 2 -s 1 test2
rbd ls | grep test1
rbd ls | grep test2
rbd ls | wc -l | grep 2
rbd ls -l | grep 'test1.*2.*'
rbd ls -l | grep 'test2.*2.*'

rbd trash mv test1
rbd ls | grep test2
rbd ls | wc -l | grep 1
rbd ls -l | grep 'test2.*2.*'

rbd trash ls | grep test1
rbd trash ls | wc -l | grep 1
rbd trash ls -l | grep 'test1.*USER.*'
rbd trash ls -l | grep -v 'protected until'

ID=`rbd trash ls | cut -d ' ' -f 1`
rbd trash rm $ID

rbd trash mv test2
ID=`rbd trash ls | cut -d ' ' -f 1`
rbd info --image-id $ID | grep "rbd image '$ID'"

rbd trash restore $ID
rbd ls | grep test2
rbd ls | wc -l | grep 1
rbd ls -l | grep 'test2.*2.*'

rbd trash mv test2 --delay 10
rbd trash ls | grep test2
rbd trash ls | wc -l | grep 1
rbd trash ls -l | grep 'test2.*USER.*protected until'

rbd trash rm $ID 2>&1 | grep 'Deferment time has not expired'
rbd trash rm --image-id $ID --force

rbd create --image-format 2 -s 1 test1
rbd snap create test1@snap1
rbd snap protect test1@snap1
rbd trash mv test1

rbd trash ls | grep test1
rbd trash ls | wc -l | grep 1
rbd trash ls -l | grep 'test1.*USER.*'
rbd trash ls -l | grep -v 'protected until'

ID=`rbd trash ls | cut -d ' ' -f 1`
rbd snap ls --image-id $ID | grep -v 'SNAPID' | wc -l | grep 1
rbd snap ls --image-id $ID | grep '.*snap1.*'

rbd snap unprotect --image-id $ID --snap snap1
rbd snap rm --image-id $ID --snap snap1
rbd snap ls --image-id $ID | grep -v 'SNAPID' | wc -l | grep 0

rbd trash restore $ID
rbd snap create test1@snap1
rbd snap create test1@snap2
rbd snap ls --image-id $ID | grep -v 'SNAPID' | wc -l | grep 2
rbd snap purge --image-id $ID
rbd snap ls --image-id $ID | grep -v 'SNAPID' | wc -l | grep 0

remove_images
}


test_pool_image_args
test_rename
test_ls
Expand All @@ -384,5 +455,6 @@ RBD_CREATE_ARGS="--image-format 2"
test_others
test_locking
test_clone
test_trash

echo OK
210 changes: 210 additions & 0 deletions src/cls/rbd/cls_rbd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4872,6 +4872,197 @@ int image_get_group(cls_method_context_t hctx,
return 0;
}

namespace trash {

static const std::string IMAGE_KEY_PREFIX("id_");

std::string image_key(const std::string &image_id) {
return IMAGE_KEY_PREFIX + image_id;
}

std::string image_id_from_key(const std::string &key) {
return key.substr(IMAGE_KEY_PREFIX.size());
}

} // namespace trash

/**
* Add an image entry to the rbd trash. Creates the trash object if
* needed, and stores the trash spec information of the deleted image.
*
* Input:
* @param id the id of the image
* @param trash_spec the spec info of the deleted image
*
* Output:
* @returns -EEXIST if the image id is already in the trash
* @returns 0 on success, negative error code on failure
*/
int trash_add(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
{
int r = cls_cxx_create(hctx, false);
if (r < 0) {
CLS_ERR("could not create trash: %s", cpp_strerror(r).c_str());
return r;
}

string id;
cls::rbd::TrashImageSpec trash_spec;
try {
bufferlist::iterator iter = in->begin();
::decode(id, iter);
::decode(trash_spec, iter);
} catch (const buffer::error &err) {
return -EINVAL;
}

if (!is_valid_id(id)) {
CLS_ERR("trash_add: invalid id '%s'", id.c_str());
return -EINVAL;
}

CLS_LOG(20, "trash_add id=%s", id.c_str());

string key = trash::image_key(id);
cls::rbd::TrashImageSpec tmp;
r = read_key(hctx, key, &tmp);
if (r < 0 && r != -ENOENT) {
CLS_ERR("could not read key %s entry from trash: %s", key.c_str(),
cpp_strerror(r).c_str());
return r;
} else if (r == 0) {
CLS_LOG(10, "id already exists");
return -EEXIST;
}

map<string, bufferlist> omap_vals;
::encode(trash_spec, omap_vals[key]);
return cls_cxx_map_set_vals(hctx, &omap_vals);
}

/**
* Removes an image entry from the rbd trash object.
* image.
*
* Input:
* @param id the id of the image
*
* Output:
* @returns -ENOENT if the image id does not exist in the trash
* @returns 0 on success, negative error code on failure
*/
int trash_remove(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
{
string id;
try {
bufferlist::iterator iter = in->begin();
::decode(id, iter);
} catch (const buffer::error &err) {
return -EINVAL;
}

CLS_LOG(20, "trash_remove id=%s", id.c_str());

string key = trash::image_key(id);
bufferlist tmp;
int r = cls_cxx_map_get_val(hctx, key, &tmp);
if (r < 0) {
if (r != -ENOENT) {
CLS_ERR("error reading entry key %s: %s", key.c_str(), cpp_strerror(r).c_str());
}
return r;
}

r = cls_cxx_map_remove_key(hctx, key);
if (r < 0) {
CLS_ERR("error removing entry: %s", cpp_strerror(r).c_str());
return r;
}

return 0;
}

/**
* Returns the list of trash spec entries registered in the rbd_trash
* object.
*
* Output:
* @param data the map between image id and trash spec info
*
* @returns 0 on success, negative error code on failure
*/
int trash_list(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
{
map<string, cls::rbd::TrashImageSpec> data;
string last_read = trash::image_key("");
int max_read = RBD_MAX_KEYS_READ;

CLS_LOG(20, "trash_get_images");

do {
map<string, bufferlist> raw_data;
int r = cls_cxx_map_get_vals(hctx, last_read, trash::IMAGE_KEY_PREFIX,
max_read, &raw_data);
if (r < 0) {
CLS_ERR("failed to read the vals off of disk: %s", cpp_strerror(r).c_str());
return r;
}
if (raw_data.empty()) {
break;
}

map<string, bufferlist>::iterator it = raw_data.begin();
for (; it != raw_data.end(); ++it) {
::decode(data[trash::image_id_from_key(it->first)], it->second);
}

if (r < max_read) {
break;
}

last_read = raw_data.rbegin()->first;
} while (max_read);

::encode(data, *out);

return 0;
}

/**
* Returns the trash spec entry of an image registered in the rbd_trash
* object.
*
* Input:
* @param id the id of the image
*
* Output:
* @param out the trash spec entry
*
* @returns 0 on success, negative error code on failure
*/
int trash_get(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
{
string id;
try {
bufferlist::iterator iter = in->begin();
::decode(id, iter);
} catch (const buffer::error &err) {
return -EINVAL;
}

CLS_LOG(20, "trash_get_image id=%s", id.c_str());


string key = trash::image_key(id);
bufferlist bl;
int r = cls_cxx_map_get_val(hctx, key, out);
if (r != -ENOENT) {
CLS_ERR("error reading image from trash '%s': '%s'", id.c_str(),
cpp_strerror(r).c_str());
}
return r;
}

CLS_INIT(rbd)
{
CLS_LOG(20, "Loaded rbd class!");
Expand Down Expand Up @@ -4962,6 +5153,10 @@ CLS_INIT(rbd)
cls_method_handle_t h_image_add_group;
cls_method_handle_t h_image_remove_group;
cls_method_handle_t h_image_get_group;
cls_method_handle_t h_trash_add;
cls_method_handle_t h_trash_remove;
cls_method_handle_t h_trash_list;
cls_method_handle_t h_trash_get;

cls_register("rbd", &h_class);
cls_register_cxx_method(h_class, "create",
Expand Down Expand Up @@ -5227,5 +5422,20 @@ CLS_INIT(rbd)
cls_register_cxx_method(h_class, "image_get_group",
CLS_METHOD_RD,
image_get_group, &h_image_get_group);

/* rbd_trash object methods */
cls_register_cxx_method(h_class, "trash_add",
CLS_METHOD_RD | CLS_METHOD_WR,
trash_add, &h_trash_add);
cls_register_cxx_method(h_class, "trash_remove",
CLS_METHOD_RD | CLS_METHOD_WR,
trash_remove, &h_trash_remove);
cls_register_cxx_method(h_class, "trash_list",
CLS_METHOD_RD,
trash_list, &h_trash_list);
cls_register_cxx_method(h_class, "trash_get",
CLS_METHOD_RD,
trash_get, &h_trash_get);

return;
}
Loading