]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd: added CLI support for trash operations
authorRicardo Dias <rdias@suse.com>
Fri, 24 Feb 2017 18:37:43 +0000 (18:37 +0000)
committerRicardo Dias <rdias@suse.com>
Tue, 11 Apr 2017 11:09:41 +0000 (12:09 +0100)
Signed-off-by: Ricardo Dias <rdias@suse.com>
src/test/cli/rbd/help.t
src/tools/rbd/ArgumentTypes.cc
src/tools/rbd/ArgumentTypes.h
src/tools/rbd/CMakeLists.txt
src/tools/rbd/Utils.cc
src/tools/rbd/Utils.h
src/tools/rbd/action/Trash.cc [new file with mode: 0644]

index e9b716ad73605c2b12cecf7dd8fea225911e64a3..ab89eeff306430946b712bb5686ac42f1462a199 100644 (file)
       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.
   
     --format arg         output format [plain, json, or xml]
     --pretty-format      pretty formatting (json and xml)
   
+  rbd help trash list
+  usage: rbd trash list [--pool <pool>] [--all] [--long] [--format <format>] 
+                        [--pretty-format] 
+                        <pool-name> 
+  
+  List trash images.
+  
+  Positional arguments
+    <pool-name>          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 <pool>] [--image <image>] [--delay <delay>] 
+                        <image-spec> 
+  
+  Moves an image to the trash.
+  
+  Positional arguments
+    <image-spec>         image specification
+                         (example: [<pool-name>/]<image-name>)
+  
+  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 <pool>] [--image-id <image-id>] 
+                          [--no-progress] [--force] 
+                          <image-id> 
+  
+  Removes an image from trash.
+  
+  Positional arguments
+    <image-id>           image id
+                         (example: [<pool-name>/]<image-id>)
+  
+  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 <pool>] [--image-id <image-id>] 
+                           [--image <image>] 
+                           <image-id> 
+  
+  Restores an image from trash.
+  
+  Positional arguments
+    <image-id>           image id
+                         (example: [<pool-name>/]<image-id>)
+  
+  Optional arguments
+    -p [ --pool ] arg    pool name
+    --image-id arg       image id
+    --image arg          image name
+  
   rbd help unmap
   usage: rbd unmap [--pool <pool>] [--image <image>] [--snap <snap>] 
                    [--options <options>] 
index b04fcc1da32bdb9fc73afec224f422c4d1d9a632..63cba27af8fe95e6d466fead0325fcf951d18768 100644 (file)
@@ -114,6 +114,17 @@ void add_image_option(po::options_description *opt,
     (name.c_str(), po::value<std::string>(), 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<std::string>(), 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
index d0ea09df900915b79261321e89730e953c8f684d..f8a2cd1d8edb9472232bfdfd58755a51e4638b29 100644 (file)
@@ -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<std::string> 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 = "");
index 2a265329aae47d1bb94792ae5795e64b06cfd3e0..c0264b61f8c62d8def58787c963f8733f7330184 100644 (file)
@@ -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}
   $<TARGET_OBJECTS:common_util_obj>
index 96ddbb5bb57d09ee50a0ef11bfc77d469fdb90b4..9f9913fbad306c6694ff7f5368cbb66301558879 100644 (file)
@@ -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<std::string>();
+  }
+  if (vm.count(at::IMAGE_ID) && image_id != nullptr) {
+    *image_id = vm[at::IMAGE_ID].as<std::string>();
+  }
+
+  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,
index 89ef83405efaa566492dcd453bbb6c84efbe8ab3..4f338a23ac49ff4b300a194781a1db42b1a3ebd5 100644 (file)
@@ -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 (file)
index 0000000..2c381dc
--- /dev/null
@@ -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 <iostream>
+#include <sstream>
+#include <boost/program_options.hpp>
+
+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<uint64_t>(),
+     "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<uint64_t>();
+  }
+
+  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: [<pool-name>/]<image-id>)");
+  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<bool>());
+  r = rbd.trash_remove_with_progress(io_ctx, image_id.c_str(),
+                                     vm["force"].as<bool>(), 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<librbd::trash_image_info_t> 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<bool>(), vm["all"].as<bool>(),
+              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: [<pool-name>/]<image-id>)");
+  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<std::string>();
+  }
+
+  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