rbd trash: replace cli delay option, add rbd trash purge command

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>
This commit is contained in:
Theofilos Mouratidis 2017-12-06 13:39:18 +01:00
parent cd70aa2cf3
commit b9e4aa94b7
5 changed files with 309 additions and 46 deletions

View File

@ -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

View 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;

View 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>]

View 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 = {

View 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,6 +108,32 @@ void get_remove_arguments(po::options_description *positional,
("force", po::bool_switch(), "force remove of non-expired delayed images");
}
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."
<< 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;
}
}
int execute_remove(const po::variables_map &vm,
const std::vector<std::string> &ceph_global_init_args) {
size_t arg_index = 0;
@ -112,29 +157,7 @@ int execute_remove(const po::variables_map &vm,
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;
}
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