From 0a25ea0036e515bbe6198e1088ef2e903165a05f Mon Sep 17 00:00:00 2001 From: Ricardo Dias Date: Fri, 24 Feb 2017 18:37:43 +0000 Subject: [PATCH] rbd: added CLI support for trash operations Signed-off-by: Ricardo Dias --- src/test/cli/rbd/help.t | 69 ++++++ src/tools/rbd/ArgumentTypes.cc | 12 +- src/tools/rbd/ArgumentTypes.h | 6 + src/tools/rbd/CMakeLists.txt | 1 + src/tools/rbd/Utils.cc | 57 +++++ src/tools/rbd/Utils.h | 7 + src/tools/rbd/action/Trash.cc | 373 +++++++++++++++++++++++++++++++++ 7 files changed, 524 insertions(+), 1 deletion(-) create mode 100644 src/tools/rbd/action/Trash.cc diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index e9b716ad736..ab89eeff306 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -86,6 +86,10 @@ snap rollback (snap revert) Rollback image to snapshot. snap unprotect Allow a snapshot to be deleted. status Show the status of this image. + trash list (trash ls) List trash images. + trash move (trash mv) Moves an image to the trash. + trash remove (trash rm) Removes an image from trash. + trash restore Restores an image from trash. unmap Unmap a rbd device that was used by the kernel. watch Watch events on image. @@ -1426,6 +1430,71 @@ --format arg output format [plain, json, or xml] --pretty-format pretty formatting (json and xml) + rbd help trash list + usage: rbd trash list [--pool ] [--all] [--long] [--format ] + [--pretty-format] + + + List trash images. + + Positional arguments + pool name + + Optional arguments + -p [ --pool ] arg pool name + -a [ --all ] list images from all sources + -l [ --long ] long listing format + --format arg output format [plain, json, or xml] + --pretty-format pretty formatting (json and xml) + + rbd help trash move + usage: rbd trash move [--pool ] [--image ] [--delay ] + + + Moves an image to the trash. + + Positional arguments + image specification + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image arg image name + --delay arg time delay in seconds until effectively remove the image + + rbd help trash remove + usage: rbd trash remove [--pool ] [--image-id ] + [--no-progress] [--force] + + + Removes an image from trash. + + Positional arguments + image id + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image-id arg image id + --no-progress disable progress output + --force force remove of non-expired delayed images + + rbd help trash restore + usage: rbd trash restore [--pool ] [--image-id ] + [--image ] + + + Restores an image from trash. + + Positional arguments + image id + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image-id arg image id + --image arg image name + rbd help unmap usage: rbd unmap [--pool ] [--image ] [--snap ] [--options ] diff --git a/src/tools/rbd/ArgumentTypes.cc b/src/tools/rbd/ArgumentTypes.cc index b04fcc1da32..63cba27af8f 100644 --- a/src/tools/rbd/ArgumentTypes.cc +++ b/src/tools/rbd/ArgumentTypes.cc @@ -114,6 +114,17 @@ void add_image_option(po::options_description *opt, (name.c_str(), po::value(), description.c_str()); } +void add_image_id_option(po::options_description *opt, + const std::string &desc_suffix) { + std::string name = IMAGE_ID; + std::string description = "image id"; + description += desc_suffix; + + // TODO add validator + opt->add_options() + (name.c_str(), po::value(), description.c_str()); +} + void add_group_option(po::options_description *opt, ArgumentModifier modifier, const std::string &desc_suffix) { @@ -249,7 +260,6 @@ void add_journal_spec_options(po::options_description *pos, add_journal_option(opt, modifier); } - void add_create_image_options(po::options_description *opt, bool include_format) { // TODO get default image format from conf diff --git a/src/tools/rbd/ArgumentTypes.h b/src/tools/rbd/ArgumentTypes.h index d0ea09df900..f8a2cd1d8ed 100644 --- a/src/tools/rbd/ArgumentTypes.h +++ b/src/tools/rbd/ArgumentTypes.h @@ -43,6 +43,7 @@ static const std::string SNAPSHOT_SPEC("snap-spec"); static const std::string IMAGE_OR_SNAPSHOT_SPEC("image-or-snap-spec"); static const std::string JOURNAL_SPEC("journal-spec"); static const std::string PATH_NAME("path-name"); +static const std::string IMAGE_ID("image-id"); // optional arguments static const std::string CONFIG_PATH("conf"); @@ -82,6 +83,8 @@ static const std::string PRETTY_FORMAT("pretty-format"); static const std::string VERBOSE("verbose"); static const std::string NO_ERROR("no-error"); +static const std::string DELAY("delay"); + static const std::string LIMIT("limit"); static const std::set SWITCH_ARGUMENTS = { @@ -136,6 +139,9 @@ void add_image_option(boost::program_options::options_description *opt, ArgumentModifier modifier, const std::string &desc_suffix = ""); +void add_image_id_option(boost::program_options::options_description *opt, + const std::string &desc_suffix = ""); + void add_group_option(boost::program_options::options_description *opt, ArgumentModifier modifier, const std::string &desc_suffix = ""); diff --git a/src/tools/rbd/CMakeLists.txt b/src/tools/rbd/CMakeLists.txt index 2a265329aae..c0264b61f8c 100644 --- a/src/tools/rbd/CMakeLists.txt +++ b/src/tools/rbd/CMakeLists.txt @@ -33,6 +33,7 @@ set(rbd_srcs action/Resize.cc action/Snap.cc action/Status.cc + action/Trash.cc action/Watch.cc) add_executable(rbd ${rbd_srcs} $ diff --git a/src/tools/rbd/Utils.cc b/src/tools/rbd/Utils.cc index 96ddbb5bb57..9f9913fbad3 100644 --- a/src/tools/rbd/Utils.cc +++ b/src/tools/rbd/Utils.cc @@ -148,6 +148,28 @@ int extract_group_spec(const std::string &spec, return 0; } +int extract_image_id_spec(const std::string &spec, std::string *pool_name, + std::string *image_id) { + boost::regex pattern; + pattern = "^(?:([^/]+)/)?(.+)?$"; + + boost::smatch match; + if (!boost::regex_match(spec, match, pattern)) { + std::cerr << "rbd: invalid spec '" << spec << "'" << std::endl; + return -EINVAL; + } + + if (pool_name != nullptr && match[1].matched) { + *pool_name = match[1]; + } + if (image_id != nullptr) { + *image_id = match[2]; + } + + return 0; + +} + std::string get_positional_argument(const po::variables_map &vm, size_t index) { if (vm.count(at::POSITIONAL_ARGUMENTS) == 0) { return ""; @@ -275,6 +297,41 @@ int get_special_pool_image_names(const po::variables_map &vm, return 0; } +int get_pool_image_id(const po::variables_map &vm, + size_t *spec_arg_index, + std::string *pool_name, + std::string *image_id) { + + if (vm.count(at::POOL_NAME) && pool_name != nullptr) { + *pool_name = vm[at::POOL_NAME].as(); + } + if (vm.count(at::IMAGE_ID) && image_id != nullptr) { + *image_id = vm[at::IMAGE_ID].as(); + } + + int r; + if (image_id != nullptr && spec_arg_index != nullptr && image_id->empty()) { + std::string spec = get_positional_argument(vm, (*spec_arg_index)++); + if (!spec.empty()) { + r = extract_image_id_spec(spec, pool_name, image_id); + if (r < 0) { + return r; + } + } + } + + if (pool_name->empty()) { + *pool_name = at::DEFAULT_POOL_NAME; + } + + if (image_id != nullptr && image_id->empty()) { + std::cerr << "rbd: image id was not specified" << std::endl; + return -EINVAL; + } + + return 0; +} + int get_pool_group_names(const po::variables_map &vm, at::ArgumentModifier mod, size_t *spec_arg_index, diff --git a/src/tools/rbd/Utils.h b/src/tools/rbd/Utils.h index 89ef83405ef..4f338a23ac4 100644 --- a/src/tools/rbd/Utils.h +++ b/src/tools/rbd/Utils.h @@ -96,6 +96,9 @@ int extract_group_spec(const std::string &spec, std::string *pool_name, std::string *group_name); +int extract_image_id_spec(const std::string &spec, std::string *pool_name, + std::string *image_id); + std::string get_positional_argument( const boost::program_options::variables_map &vm, size_t index); @@ -119,6 +122,10 @@ int get_special_pool_image_names(const boost::program_options::variables_map &vm std::string *image_pool_name, std::string *image_name); +int get_pool_image_id(const boost::program_options::variables_map &vm, + size_t *arg_index, std::string *image_pool_name, + std::string *image_id); + int get_pool_group_names(const boost::program_options::variables_map &vm, argument_types::ArgumentModifier mod, size_t *spec_arg_index, diff --git a/src/tools/rbd/action/Trash.cc b/src/tools/rbd/action/Trash.cc new file mode 100644 index 00000000000..2c381dccc2a --- /dev/null +++ b/src/tools/rbd/action/Trash.cc @@ -0,0 +1,373 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2017 SUSE LINUX GmbH + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include "tools/rbd/ArgumentTypes.h" +#include "tools/rbd/Shell.h" +#include "tools/rbd/Utils.h" +#include "common/errno.h" +#include "include/stringify.h" +#include "common/Formatter.h" +#include "common/TextTable.h" +#include "common/Clock.h" +#include +#include +#include + +namespace rbd { +namespace action { +namespace trash { + +namespace at = argument_types; +namespace po = boost::program_options; + + +void get_move_arguments(po::options_description *positional, + po::options_description *options) { + at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); + options->add_options() + (at::DELAY.c_str(), po::value(), + "time delay in seconds until effectively remove the image"); +} + +int execute_move(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string image_name; + std::string snap_name; + + int r = utils::get_pool_image_snapshot_names( + vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &image_name, + &snap_name, utils::SNAPSHOT_PRESENCE_NONE, utils::SPEC_VALIDATION_NONE); + if (r < 0) { + return r; + } + + librados::Rados rados; + librados::IoCtx io_ctx; + r = utils::init(pool_name, &rados, &io_ctx); + if (r < 0) { + return r; + } + + uint64_t delay = 0; + if (vm.find(at::DELAY) != vm.end()) { + delay = vm[at::DELAY].as(); + } + + librbd::RBD rbd; + r = rbd.trash_move(io_ctx, image_name.c_str(), delay); + if (r < 0) { + std::cerr << "rbd: deferred delete error: " << cpp_strerror(r) + << std::endl; + } + + return r; + +} + +void get_remove_arguments(po::options_description *positional, + po::options_description *options) { + positional->add_options() + (at::IMAGE_ID.c_str(), "image id\n(example: [/])"); + at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE); + at::add_image_id_option(options); + + at::add_no_progress_option(options); + options->add_options() + ("force", po::bool_switch(), "force remove of non-expired delayed images"); +} + +int execute_remove(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string image_id; + int r = utils::get_pool_image_id(vm, &arg_index, &pool_name, &image_id); + if (r < 0) { + return r; + } + + librados::Rados rados; + librados::IoCtx io_ctx; + r = utils::init(pool_name, &rados, &io_ctx); + if (r < 0) { + return r; + } + + librbd::RBD rbd; + + utils::ProgressContext pc("Removing image", vm[at::NO_PROGRESS].as()); + r = rbd.trash_remove_with_progress(io_ctx, image_id.c_str(), + vm["force"].as(), pc); + if (r < 0) { + if (r == -ENOTEMPTY) { + std::cerr << "rbd: image has snapshots - these must be deleted" + << " with 'rbd snap purge' before the image can be removed." + << std::endl; + } else if (r == -EBUSY) { + std::cerr << "rbd: error: image still has watchers" + << std::endl + << "This means the image is still open or the client using " + << "it crashed. Try again after closing/unmapping it or " + << "waiting 30s for the crashed client to timeout." + << std::endl; + } else if (r == -EMLINK) { + std::cerr << std::endl + << "Remove the image from the consistency group and try again." + << std::endl; + } else if (r == -EPERM) { + std::cerr << std::endl + << "Deferment time has not expired, please use --force if you " + << "really want to remove the image" + << std::endl; + } else { + std::cerr << "rbd: remove error: " << cpp_strerror(r) << std::endl; + } + pc.fail(); + return r; + } + + pc.finish(); + + return r; +} + +std::string delete_status(time_t deferment_end_time) { + time_t now = ceph_clock_gettime(); + + std::string time_str = ctime(&deferment_end_time); + time_str = time_str.substr(0, time_str.length() - 1); + + std::stringstream ss; + if (now < deferment_end_time) { + ss << "protected until " << time_str; + } + + return ss.str(); +} + +int do_list(librbd::RBD &rbd, librados::IoCtx& io_ctx, bool long_flag, + bool all_flag, Formatter *f) { + std::vector trash_entries; + int r = rbd.trash_list(io_ctx, trash_entries); + if (r < 0 && r != -ENOENT) { + return r; + } + r = 0; + + if (!long_flag) { + if (f) { + f->open_array_section("trash"); + } + for (const auto& entry : trash_entries) { + if (!all_flag && + entry.source == RBD_TRASH_IMAGE_SOURCE_MIRRORING) { + continue; + } + if (f) { + f->dump_string("id", entry.id); + f->dump_string("name", entry.name); + } else { + std::cout << entry.id << " " << entry.name << std::endl; + } + } + if (f) { + f->close_section(); + f->flush(std::cout); + } + return 0; + } + + TextTable tbl; + + if (f) { + f->open_array_section("trash"); + } else { + tbl.define_column("ID", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("SOURCE", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("DELETED_AT", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("STATUS", TextTable::LEFT, TextTable::LEFT); + } + + for (const auto& entry : trash_entries) { + if (!all_flag && + entry.source == RBD_TRASH_IMAGE_SOURCE_MIRRORING) { + continue; + } + librbd::Image im; + + r = rbd.open_by_id_read_only(io_ctx, im, entry.id.c_str(), NULL); + // image might disappear between rbd.list() and rbd.open(); ignore + // that, warn about other possible errors (EPERM, say, for opening + // an old-format image, because you need execute permission for the + // class method) + if (r < 0) { + if (r != -ENOENT) { + std::cerr << "rbd: error opening " << entry.id << ": " + << cpp_strerror(r) << std::endl; + } + // in any event, continue to next image + continue; + } + + std::string del_source; + switch (entry.source) { + case RBD_TRASH_IMAGE_SOURCE_USER: + del_source = "USER"; + break; + case RBD_TRASH_IMAGE_SOURCE_MIRRORING: + del_source = "MIRRORING"; + break; + } + + std::string time_str = ctime(&entry.deletion_time); + time_str = time_str.substr(0, time_str.length() - 1); + + if (f) { + f->open_object_section("image"); + f->dump_string("id", entry.id); + f->dump_string("name", entry.name); + f->dump_string("source", del_source); + f->dump_string("deleted_at", time_str); + f->dump_string("status", + delete_status(entry.deferment_end_time)); + f->close_section(); + } else { + tbl << entry.id + << entry.name + << del_source + << time_str + << delete_status(entry.deferment_end_time) + << TextTable::endrow; + } + } + + if (f) { + f->close_section(); + f->flush(std::cout); + } else if (!trash_entries.empty()) { + std::cout << tbl; + } + + return r < 0 ? r : 0; +} + +void get_list_arguments(po::options_description *positional, + po::options_description *options) { + at::add_pool_options(positional, options); + options->add_options() + ("all,a", po::bool_switch(), "list images from all sources"); + options->add_options() + ("long,l", po::bool_switch(), "long listing format"); + at::add_format_options(options); +} + +int execute_list(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name = utils::get_pool_name(vm, &arg_index); + + at::Format::Formatter formatter; + int r = utils::get_formatter(vm, &formatter); + if (r < 0) { + return r; + } + + librados::Rados rados; + librados::IoCtx io_ctx; + r = utils::init(pool_name, &rados, &io_ctx); + if (r < 0) { + return r; + } + + librbd::RBD rbd; + r = do_list(rbd, io_ctx, vm["long"].as(), vm["all"].as(), + formatter.get()); + if (r < 0) { + std::cerr << "rbd: trash list: " << cpp_strerror(r) << std::endl; + return r; + } + + return 0; +} + +void get_restore_arguments(po::options_description *positional, + po::options_description *options) { + positional->add_options() + (at::IMAGE_ID.c_str(), "image id\n(example: [/])"); + at::add_pool_option(options, at::ARGUMENT_MODIFIER_NONE); + at::add_image_id_option(options); + at::add_image_option(options, at::ARGUMENT_MODIFIER_NONE, ""); +} + +int execute_restore(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string image_id; + int r = utils::get_pool_image_id(vm, &arg_index, &pool_name, &image_id); + if (r < 0) { + return r; + } + + librados::Rados rados; + librados::IoCtx io_ctx; + r = utils::init(pool_name, &rados, &io_ctx); + if (r < 0) { + return r; + } + + std::string name; + if (vm.find(at::IMAGE_NAME) != vm.end()) { + name = vm[at::IMAGE_NAME].as(); + } + + librbd::RBD rbd; + r = rbd.trash_restore(io_ctx, image_id.c_str(), name.c_str()); + if (r < 0) { + if (r == -ENOENT) { + std::cerr << "rbd: error: image does not exist in trash" + << std::endl; + } else if (r == -EEXIST) { + std::cerr << "rbd: error: an image with the same name already exists, " + << "try again with with a different name" + << std::endl; + } else { + std::cerr << "rbd: restore error: " << cpp_strerror(r) << std::endl; + } + return r; + } + + return r; +} + + +Shell::Action action_move( + {"trash", "move"}, {"trash", "mv"}, "Moves an image to the trash.", "", + &get_move_arguments, &execute_move); + +Shell::Action action_remove( + {"trash", "remove"}, {"trash", "rm"}, "Removes an image from trash.", "", + &get_remove_arguments, &execute_remove); + +Shell::SwitchArguments switched_arguments({"long", "l"}); +Shell::Action action_list( + {"trash", "list"}, {"trash", "ls"}, "List trash images.", "", + &get_list_arguments, &execute_list); + +Shell::Action action_restore( + {"trash", "restore"}, {}, "Restores an image from trash.", "", + &get_restore_arguments, &execute_restore); + +} // namespace trash +} // namespace action +} // namespace rbd -- 2.39.5