mirror of
https://github.com/ceph/ceph
synced 2024-12-17 08:57:28 +00:00
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:
parent
cd70aa2cf3
commit
b9e4aa94b7
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>]
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user