]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd trash: replace cli delay option, add rbd trash purge command 18323/head
authorTheofilos Mouratidis <t.mour@cern.ch>
Wed, 6 Dec 2017 12:39:18 +0000 (13:39 +0100)
committerTheofilos Mouratidis <t.mour@cern.ch>
Tue, 23 Jan 2018 10:41:54 +0000 (11:41 +0100)
Replaced the delay argument for the trash move
command with a string acceptable by /bin/date, e.g.:

$ rbd trash move --pool foo --image bar --expires-in "2 weeks"

Added a "rbd trash purge" command that deletes any expired
image from the trash, has also a command to alter the current
expiration date with the "--older-than" argument which accepts
again a valid argument for /bin/date, e.g.:

$rbd trash purge mypool --older-than "2017-08-20"

There is also the "threshold" argument which tries to remove the
oldest trashed images (by deferment end time) until the pool space
is freed up to a percentage point, e.g.:

$ rbd trash purge mypool --threshold 0.9

If mypool uses 1GB it will try to remove trashed images until the
pool usage becomes equal to or lower than 900MB.

Signed-off-by: Theofilos Mouratidis <t.mour@cern.ch>
qa/workunits/rbd/cli_generic.sh
src/include/utime.h
src/test/cli/rbd/help.t
src/tools/rbd/ArgumentTypes.h
src/tools/rbd/action/Trash.cc

index 5318f507c15c7b248e4f13a7200e0d70e987e834..17fd5061e446826a2a167a4af4904a4af0724997 100755 (executable)
@@ -172,7 +172,7 @@ test_ls() {
     rbd ls -l | grep 'test1.*1M.*2'
     rbd ls -l | grep 'test2.*1M.*1'
     remove_images
-       
+
     # test that many images can be shown by ls
     for i in $(seq -w 00 99); do
        rbd create image.$i -s 1
@@ -180,7 +180,7 @@ test_ls() {
     rbd ls | wc -l | grep 100
     rbd ls -l | grep image | wc -l | grep 100
     for i in $(seq -w 00 99); do
-       rbd rm image.$i 
+       rbd rm image.$i
     done
 
     for i in $(seq -w 00 99); do
@@ -189,7 +189,7 @@ test_ls() {
     rbd ls | wc -l | grep 100
     rbd ls -l | grep image |  wc -l | grep 100
     for i in $(seq -w 00 99); do
-       rbd rm image.$i 
+       rbd rm image.$i
     done
 }
 
@@ -417,7 +417,7 @@ test_trash() {
     rbd ls | wc -l | grep 1
     rbd ls -l | grep 'test2.*2.*'
 
-    rbd trash mv test2 --delay 3600
+    rbd trash mv test2 --expires-at "3600 sec"
     rbd trash ls | grep test2
     rbd trash ls | wc -l | grep 1
     rbd trash ls -l | grep 'test2.*USER.*protected until'
@@ -453,6 +453,35 @@ test_trash() {
     remove_images
 }
 
+test_purge(){
+    echo "testing trash purge..."
+    remove_images
+
+    for i in {1..3};
+    do
+        rbd create   "test$i" -s 4
+        rbd bench    "test$i" --io-total 4M --io-type write > /dev/null
+        rbd trash mv "test$i"
+    done
+
+    rbd trash purge --threshold 1 | grep "Nothing to do"
+
+    rbd trash purge --threshold 0
+    rbd trash ls | wc -l | grep 0
+
+    rbd create foo -s 1
+    rbd create bar -s 1
+
+    rbd trash mv foo --expires-at "10 sec"
+    rbd trash mv bar --expires-at "30 sec"
+
+    rbd trash purge --expired-before "now + 10 sec"
+    rbd trash ls | grep -v foo | wc -l | grep 1
+    rbd trash ls | grep bar
+
+    LAST_IMG=$(rbd trash ls | grep bar | awk '{print $1;}')
+    rbd trash rm $LAST_IMG --force --no-progress | grep -v '.' | wc -l | grep 0
+}
 
 test_pool_image_args
 test_rename
@@ -466,5 +495,6 @@ test_others
 test_locking
 test_clone
 test_trash
+test_purge
 
 echo OK
index 69a6429c663f74bba82d7538b49df67eeb15394b..63b4683161666b69e7b07d6fa7a3d13a639ecd4f 100644 (file)
@@ -24,6 +24,8 @@
 #include "include/timegm.h"
 #include "common/strtol.h"
 #include "common/ceph_time.h"
+#include "common/safe_io.h"
+#include "common/SubProcess.h"
 #include "include/denc.h"
 
 
@@ -335,6 +337,32 @@ public:
         bdt.tm_hour, bdt.tm_min, bdt.tm_sec);
   }
 
+  static int invoke_date(const std::string& date_str, utime_t *result) {
+     char buf[256];
+
+     SubProcess bin_date("/bin/date", SubProcess::CLOSE, SubProcess::PIPE, SubProcess::KEEP);
+     bin_date.add_cmd_args("-d", date_str.c_str(), "+%s %N", NULL);
+
+     int r = bin_date.spawn();
+     if (r < 0) return r;
+
+     ssize_t n = safe_read(bin_date.get_stdout(), buf, sizeof(buf));
+
+     r = bin_date.join();
+     if (r || n <= 0) return -EINVAL;
+
+     uint64_t epoch, nsec;
+     std::istringstream iss(buf);
+
+     iss >> epoch;
+     iss >> nsec;
+
+     *result = utime_t(epoch, nsec);
+
+     return 0;
+  }
+
+
   static int parse_date(const string& date, uint64_t *epoch, uint64_t *nsec,
                         string *out_date=NULL, string *out_time=NULL) {
     struct tm tm;
index 506a18356602e22f1225bca58ba0c35cc8e13889..adbc93eb2faf5ccc0fb9cdf0c2faf6a806606521 100644 (file)
@@ -105,6 +105,7 @@ Skip test on FreeBSD as it generates different output there.
       status                            Show the status of this image.
       trash list (trash ls)             List trash images.
       trash move (trash mv)             Move an image to the trash.
+      trash purge                       Remove all expired images from trash.
       trash remove (trash rm)           Remove an image from trash.
       trash restore                     Restore an image from trash.
       unmap                             Unmap a rbd device that was used by the
@@ -1567,19 +1568,39 @@ Skip test on FreeBSD as it generates different output there.
     --pretty-format      pretty formatting (json and xml)
   
   rbd help trash move
-  usage: rbd trash move [--pool <pool>] [--image <image>] [--delay <delay>] 
+  usage: rbd trash move [--pool <pool>] [--image <image>] 
+                        [--expires-at <expires-at>] 
                         <image-spec> 
   
   Move an image to the trash.
   
   Positional arguments
-    <image-spec>         image specification
-                         (example: [<pool-name>/]<image-name>)
+    <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
+    -p [ --pool ] arg       pool name
+    --image arg             image name
+    --expires-at arg (=now) set the expiration time of an image so it can be
+                            purged when it is stale
+  
+  rbd help trash purge
+  usage: rbd trash purge [--pool <pool>] [--no-progress] 
+                         [--expired-before <expired-before>] 
+                         [--threshold <threshold>] 
+                         <pool-name> 
+  
+  Remove all expired images from trash.
+  
+  Positional arguments
+    <pool-name>           pool name
+  
+  Optional arguments
+    -p [ --pool ] arg     pool name
+    --no-progress         disable progress output
+    --expired-before date purges images that expired before the given date
+    --threshold arg       purges images until the current pool data usage is
+                          reduced to X%, value range: 0.0-1.0
   
   rbd help trash remove
   usage: rbd trash remove [--pool <pool>] [--image-id <image-id>] 
index 2496d94ff4583b433538f1b12d1e18a48a9d8b1c..0958b14d46b65bc340e56272b1faa1aebc47eaaf 100644 (file)
@@ -81,8 +81,6 @@ 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 = {
index b3ba2c5d987183de99b99effee715ce60b0576ef..cc80bfdf176067c570e9c7480916a5727a000e79 100644 (file)
@@ -23,6 +23,7 @@
 #include <iostream>
 #include <sstream>
 #include <boost/program_options.hpp>
+#include <json_spirit/json_spirit.h>
 
 namespace rbd {
 namespace action {
@@ -31,13 +32,17 @@ namespace trash {
 namespace at = argument_types;
 namespace po = boost::program_options;
 
+//Optional arguments used only by this set of commands (rbd trash *)
+static const std::string EXPIRES_AT("expires-at");
+static const std::string EXPIRED_BEFORE("expired-before");
+static const std::string THRESHOLD("threshold");
 
 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");
+    (EXPIRES_AT.c_str(), po::value<std::string>()->default_value("now"),
+     "set the expiration time of an image so it can be purged when it is stale");
 }
 
 int execute_move(const po::variables_map &vm,
@@ -61,20 +66,34 @@ int execute_move(const po::variables_map &vm,
     return r;
   }
 
-  uint64_t delay = 0;
-  if (vm.find(at::DELAY) != vm.end()) {
-    delay = vm[at::DELAY].as<uint64_t>();
+  utime_t now = ceph_clock_now();
+  utime_t exp_time = now;
+  std::string expires_at;
+  if (vm.find(EXPIRES_AT) != vm.end()) {
+    expires_at = vm[EXPIRES_AT].as<std::string>();
+    r = utime_t::invoke_date(expires_at, &exp_time);
+    if (r < 0) {
+      std::cerr << "rbd: error calling /bin/date: " << cpp_strerror(r)
+                << std::endl;
+      return r;
+    }
+  }
+
+  time_t dt = exp_time.sec() - now.sec();
+  if(dt < 0) {
+    std::cerr << "rbd: cannot use a date in the past as an expiration date"
+              << std::endl;
+    return -EINVAL;
   }
 
   librbd::RBD rbd;
-  r = rbd.trash_move(io_ctx, image_name.c_str(), delay);
+  r = rbd.trash_move(io_ctx, image_name.c_str(), dt);
   if (r < 0) {
     std::cerr << "rbd: deferred delete error: " << cpp_strerror(r)
               << std::endl;
   }
 
   return r;
-
 }
 
 void get_remove_arguments(po::options_description *positional,
@@ -89,29 +108,7 @@ void get_remove_arguments(po::options_description *positional,
       ("force", po::bool_switch(), "force remove of non-expired delayed images");
 }
 
-int execute_remove(const po::variables_map &vm,
-                   const std::vector<std::string> &ceph_global_init_args) {
-  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) {
+void remove_error_check(int r) {
     if (r == -ENOTEMPTY) {
       std::cerr << "rbd: image has snapshots - these must be deleted"
                 << " with 'rbd snap purge' before the image can be removed."
@@ -135,6 +132,32 @@ int execute_remove(const po::variables_map &vm,
     } else {
       std::cerr << "rbd: remove error: " << cpp_strerror(r) << std::endl;
     }
+}
+
+int execute_remove(const po::variables_map &vm,
+                   const std::vector<std::string> &ceph_global_init_args) {
+  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) {
+    remove_error_check(r); 
     pc.fail();
     return r;
   }
@@ -153,6 +176,8 @@ std::string delete_status(time_t deferment_end_time) {
   std::stringstream ss;
   if (now < deferment_end_time) {
     ss << "protected until " << time_str;
+  } else {
+    ss << "expired at " << time_str;
   }
 
   return ss.str();
@@ -325,6 +350,163 @@ int execute_list(const po::variables_map &vm,
   return 0;
 }
 
+void get_purge_arguments(po::options_description *positional,
+                            po::options_description *options) {
+  at::add_pool_options(positional, options);
+  at::add_no_progress_option(options);
+  
+  options->add_options()
+      (EXPIRED_BEFORE.c_str(), po::value<std::string>()->value_name("date"), 
+       "purges images that expired before the given date");
+  options->add_options()
+      (THRESHOLD.c_str(), po::value<double>(), 
+       "purges images until the current pool data usage is reduced to X%, "
+       "value range: 0.0-1.0");
+}
+
+
+int execute_purge (const po::variables_map &vm,
+                   const std::vector<std::string> &ceph_global_init_args) {
+  size_t arg_index = 0;
+  std::string pool_name = utils::get_pool_name(vm, &arg_index);
+
+  librados::Rados rados;
+  librados::IoCtx io_ctx;
+  int r = utils::init(pool_name, &rados, &io_ctx);
+  if (r < 0) {
+    return r;
+  }
+
+  librbd::RBD rbd;
+  
+  std::vector<librbd::trash_image_info_t> trash_entries;
+  r = rbd.trash_list(io_ctx, trash_entries);
+  if (r < 0) { 
+    return r;
+  }
+
+  std::remove_if(trash_entries.begin(), trash_entries.end(),
+    [](librbd::trash_image_info_t info) {
+      return info.source != RBD_TRASH_IMAGE_SOURCE_USER;
+    }
+  );
+
+  std::vector<const char *> to_be_removed;
+
+  if (vm.find(THRESHOLD) != vm.end()) {
+    double threshold = vm[THRESHOLD].as<double>();
+    if (threshold < 0 || threshold > 1) {
+      std::cerr << "rbd: argument 'threshold' is out of valid range"
+                << std::endl;
+      return -EINVAL;
+    }
+
+    librados::bufferlist inbl;
+    librados::bufferlist outbl;
+    rados.mon_command("{\"prefix\": \"df\", \"format\": \"json\"}", inbl,
+                      &outbl, NULL);
+   
+
+    json_spirit::mValue json;
+    if(!json_spirit::read(outbl.to_str(), json)) {
+      std::cerr << "rbd: ceph df json output could not be parsed"
+                << std::endl;
+      return -EBADMSG;
+    }
+
+    json_spirit::mArray arr = json.get_obj()["pools"].get_array();
+    
+    double pool_percent_used = 0;
+    uint64_t pool_total_bytes = 0;
+    for(uint8_t i = 0; i < arr.size(); ++i) {
+      if(arr[i].get_obj()["name"] == pool_name) {
+        json_spirit::mObject stats =  arr[i].get_obj()["stats"].get_obj();
+        pool_percent_used = stats["percent_used"].get_real() / 100;
+        if(pool_percent_used <= threshold) {
+          std::cout << "rbd: pool usage is lower than or equal to "
+                    << (threshold*100)
+                    << "%" << endl;
+          std::cout << "Nothing to do" << std::endl;
+          return 0;
+        }
+            
+        pool_total_bytes = stats["max_avail"].get_uint64() +
+                           stats["bytes_used"].get_uint64();
+        break;
+      }
+    }
+    
+    std::sort(trash_entries.begin(), trash_entries.end(),
+      [](librbd::trash_image_info_t a, librbd::trash_image_info_t b) { 
+        return a.deferment_end_time < b.deferment_end_time;
+      }
+    );
+
+    uint64_t bytes_to_free = 0;
+    auto bytes_threshold = (uint64_t)(pool_total_bytes * 
+                           (pool_percent_used - threshold));
+    
+    librbd::Image curr_img;
+    for (const auto& entry : trash_entries) {
+      r = utils::open_image_by_id(io_ctx, entry.id, true, &curr_img);
+      if(r < 0) continue;
+      
+      uint64_t img_size; curr_img.size(&img_size);
+      r = curr_img.diff_iterate2(nullptr, 0, img_size, false, true, 
+        [](uint64_t offset, size_t len, int exists, void *arg) {
+          auto *to_free = reinterpret_cast<uint64_t*>(arg);
+          if (exists) (*to_free) += len;
+          return 0;
+        }, &bytes_to_free
+      );
+      if(r < 0) continue;
+      to_be_removed.push_back(entry.id.c_str());
+      if(bytes_to_free >= bytes_threshold) break;
+    }
+  } else {
+    struct timespec now;
+    clock_gettime(CLOCK_REALTIME, &now);
+
+    time_t expire_ts = now.tv_sec;
+    if (vm.find(EXPIRED_BEFORE) != vm.end()) { 
+      utime_t new_time;
+      r = utime_t::invoke_date(vm[EXPIRED_BEFORE].as<std::string>(), &new_time);
+      if (r < 0) {
+        std::cerr << "rbd: error calling /bin/date: " << cpp_strerror(r)
+                  << std::endl;
+        return r;
+      }
+      expire_ts = new_time.sec();
+    }
+
+    for(const auto &entry : trash_entries) {    
+      if (expire_ts >= entry.deferment_end_time) {
+        to_be_removed.push_back(entry.id.c_str());
+      }
+    }
+  }
+
+  uint64_t list_size = to_be_removed.size(), i = 0;
+
+  if(list_size == 0) {
+    std::cout << "rbd: nothing to remove" << std::endl;
+  } else {
+    utils::ProgressContext pc("Removing images", vm[at::NO_PROGRESS].as<bool>());
+    for(const auto &entry_id : to_be_removed) {
+      r = rbd.trash_remove(io_ctx, entry_id, true);
+      if (r < 0) {
+        remove_error_check(r);
+        pc.fail();
+        return r;
+      }
+      pc.update_progress(++i, list_size);
+    }
+    pc.finish();
+  }
+
+  return 0;
+}
+
 void get_restore_arguments(po::options_description *positional,
                             po::options_description *options) {
   positional->add_options()
@@ -377,21 +559,25 @@ int execute_restore(const po::variables_map &vm,
 
 
 Shell::Action action_move(
-    {"trash", "move"}, {"trash", "mv"}, "Move an image to the trash.", "",
-    &get_move_arguments, &execute_move);
+  {"trash", "move"}, {"trash", "mv"}, "Move an image to the trash.", "",
+  &get_move_arguments, &execute_move);
 
 Shell::Action action_remove(
   {"trash", "remove"}, {"trash", "rm"}, "Remove an image from trash.", "",
   &get_remove_arguments, &execute_remove);
 
+Shell::Action action_purge(
+  {"trash", "purge"}, {}, "Remove all expired images from trash.", "",
+  &get_purge_arguments, &execute_purge);
+
 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"}, {}, "Restore an image from trash.", "",
-    &get_restore_arguments, &execute_restore);
+  {"trash", "restore"}, {}, "Restore an image from trash.", "",
+  &get_restore_arguments, &execute_restore);
 
 } // namespace trash
 } // namespace action