From e8a584ffc1ddd12e0f55a3b0263d8f29322b0ea4 Mon Sep 17 00:00:00 2001 From: Mykola Golub Date: Fri, 18 Sep 2015 09:23:53 +0300 Subject: [PATCH] rbd: add new journal rbd commands Also, extend 'rbd info' to output journal spec if journaling is enabled. Signed-off-by: Mykola Golub --- qa/workunits/rbd/journal.sh | 170 +++++ src/journal/JournalTrimmer.cc | 19 +- src/journal/JournalTrimmer.h | 2 +- src/journal/Journaler.cc | 38 +- src/journal/Journaler.h | 12 +- src/librbd/Journal.cc | 42 +- src/librbd/Journal.h | 1 + src/test/cli/rbd/help.t | 119 +++ src/test/journal/test_JournalTrimmer.cc | 5 +- src/test/run-rbd-tests | 2 + src/tools/Makefile-client.am | 4 +- src/tools/rbd/ArgumentTypes.cc | 47 ++ src/tools/rbd/ArgumentTypes.h | 20 +- src/tools/rbd/Utils.cc | 105 +++ src/tools/rbd/Utils.h | 7 + src/tools/rbd/action/Info.cc | 8 + src/tools/rbd/action/Journal.cc | 969 ++++++++++++++++++++++++ 17 files changed, 1550 insertions(+), 20 deletions(-) create mode 100755 qa/workunits/rbd/journal.sh create mode 100644 src/tools/rbd/action/Journal.cc diff --git a/qa/workunits/rbd/journal.sh b/qa/workunits/rbd/journal.sh new file mode 100755 index 0000000000000..99c647b3ddaf4 --- /dev/null +++ b/qa/workunits/rbd/journal.sh @@ -0,0 +1,170 @@ +#!/bin/bash -e + +. $(dirname $0)/../ceph-helpers.sh + +function list_tests() +{ + echo "AVAILABLE TESTS" + for i in $TESTS; do + echo " $i" + done +} + +function usage() +{ + echo "usage: $0 [-h|-l|-t [-t ...] [--no-sanity-check] [--no-cleanup]]" +} + +function expect_false() +{ + set -x + if "$@"; then return 1; else return 0; fi +} + +test_rbd_journal() +{ + local image=testrbdjournal$$ + + rbd create --image-feature exclusive-lock --image-feature journaling \ + --size 128 ${image} + local journal=$(rbd info ${image} --format=xml 2>/dev/null | + $XMLSTARLET sel -t -v "//image/journal") + test -n "${journal}" + rbd journal info ${journal} + rbd journal info --journal ${journal} + rbd journal info --image ${image} + + rbd feature disable ${image} journaling + + rbd info ${image} --format=xml 2>/dev/null | + expect_false $XMLSTARLET sel -t -v "//image/journal" + expect_false rbd journal info ${journal} + expect_false rbd journal info --image ${image} + + rbd feature enable ${image} journaling + + local journal1=$(rbd info ${image} --format=xml 2>/dev/null | + $XMLSTARLET sel -t -v "//image/journal") + test "${journal}" = "${journal1}" + + rbd journal info ${journal} + + rbd journal status ${journal} + + local count=10 + rbd bench-write ${image} --io-size 4096 --io-threads 1 \ + --io-total $((4096 * count)) --io-pattern seq + local count1=$(rbd journal inspect --verbose ${journal} | + grep -c 'event_type.*AioWrite') + test "${count}" -eq "${count1}" + + rbd journal export ${journal} $TMPDIR/journal.export + local size=$(stat -c "%s" $TMPDIR/journal.export) + test "${size}" -gt 0 + + rbd export ${image} $TMPDIR/${image}.export + + local image1=${image}1 + rbd create --image-feature exclusive-lock --image-feature journaling \ + --size 128 ${image1} + + rbd journal import --dest ${image1} $TMPDIR/journal.export + rbd snap create ${image1}@test + # check that commit position is properly updated: the journal should contain + # 12 entries (10 AioWrite + 1 SnapCreate + 1 OpFinish) and commit + # position set to tid=11 + rbd journal inspect --image ${image1} --verbose | awk ' + /AioWrite/ {w++} # match: "event_type": "AioWrite", + /SnapCreate/ {s++} # match: "event_type": "SnapCreate", + /OpFinish/ {f++} # match: "event_type": "OpFinish", + /entries inspected/ {t=$1; e=$4} # match: 12 entries inspected, 0 errors + {print} # for diagnostic + END { + if (w != 10 || s != 1 || f != 1 || t != 12 || e != 0) exit(1) + } + ' + rbd journal status --image ${image1} | grep 'tid=11' + + rbd export ${image1}@test $TMPDIR/${image1}.export + cmp $TMPDIR/${image}.export $TMPDIR/${image1}.export + + rbd journal inspect ${journal1} + + rbd journal reset ${journal} + + rbd journal inspect --verbose ${journal} | expect_false grep 'event_type' + + rbd snap purge ${image1} + rbd remove ${image1} + rbd remove ${image} +} + +TESTS+=" rbd_journal" + +# +# "main" follows +# + +tests_to_run=() + +sanity_check=true +cleanup=true + +while [[ $# -gt 0 ]]; do + opt=$1 + + case "$opt" in + "-l" ) + do_list=1 + ;; + "--no-sanity-check" ) + sanity_check=false + ;; + "--no-cleanup" ) + cleanup=false + ;; + "-t" ) + shift + if [[ -z "$1" ]]; then + echo "missing argument to '-t'" + usage ; + exit 1 + fi + tests_to_run+=" $1" + ;; + "-h" ) + usage ; + exit 0 + ;; + esac + shift +done + +if [[ $do_list -eq 1 ]]; then + list_tests ; + exit 0 +fi + +TMPDIR=/tmp/rbd_journal$$ +mkdir $TMPDIR +if $cleanup; then + trap "rm -fr $TMPDIR" 0 +fi + +if test -z "$tests_to_run" ; then + tests_to_run="$TESTS" +fi + +for i in $tests_to_run; do + if $sanity_check ; then + wait_for_clean + fi + set -x + test_${i} + set +x +done +if $sanity_check ; then + wait_for_clean +fi + +echo OK diff --git a/src/journal/JournalTrimmer.cc b/src/journal/JournalTrimmer.cc index 33e3be2c5b0d1..9e781cebd01db 100644 --- a/src/journal/JournalTrimmer.cc +++ b/src/journal/JournalTrimmer.cc @@ -29,22 +29,29 @@ JournalTrimmer::~JournalTrimmer() { m_async_op_tracker.wait_for_ops(); } -int JournalTrimmer::remove_objects() { +int JournalTrimmer::remove_objects(bool force) { ldout(m_cct, 20) << __func__ << dendl; m_async_op_tracker.wait_for_ops(); C_SaferCond ctx; { Mutex::Locker locker(m_lock); - JournalMetadata::RegisteredClients registered_clients; - m_journal_metadata->get_registered_clients(®istered_clients); - if (registered_clients.size() == 0) { - return -EINVAL; - } else if (registered_clients.size() > 1 || m_remove_set_pending) { + if (m_remove_set_pending) { return -EBUSY; } + if (!force) { + JournalMetadata::RegisteredClients registered_clients; + m_journal_metadata->get_registered_clients(®istered_clients); + + if (registered_clients.size() == 0) { + return -EINVAL; + } else if (registered_clients.size() > 1) { + return -EBUSY; + } + } + m_remove_set = std::numeric_limits::max(); m_remove_set_pending = true; m_remove_set_ctx = &ctx; diff --git a/src/journal/JournalTrimmer.h b/src/journal/JournalTrimmer.h index 9f557a7671bc9..46db1c51bab43 100644 --- a/src/journal/JournalTrimmer.h +++ b/src/journal/JournalTrimmer.h @@ -22,7 +22,7 @@ public: const JournalMetadataPtr &journal_metadata); ~JournalTrimmer(); - int remove_objects(); + int remove_objects(bool force); void committed(uint64_t commit_tid); private: diff --git a/src/journal/Journaler.cc b/src/journal/Journaler.cc index 231cc0400d764..25a50b89bed5f 100644 --- a/src/journal/Journaler.cc +++ b/src/journal/Journaler.cc @@ -42,6 +42,15 @@ struct C_DeleteRecorder : public Context { using namespace cls::journal; +std::string Journaler::header_oid(const std::string &journal_id) { + return JOURNAL_HEADER_PREFIX + journal_id; +} + +std::string Journaler::object_oid_prefix(int pool_id, + const std::string &journal_id) { + return JOURNAL_OBJECT_PREFIX + stringify(pool_id) + "." + journal_id + "."; +} + Journaler::Journaler(librados::IoCtx &header_ioctx, const std::string &journal_id, const std::string &client_id, double commit_interval) @@ -51,9 +60,8 @@ Journaler::Journaler(librados::IoCtx &header_ioctx, m_header_ioctx.dup(header_ioctx); m_cct = reinterpret_cast(m_header_ioctx.cct()); - m_header_oid = JOURNAL_HEADER_PREFIX + journal_id; - m_object_oid_prefix = JOURNAL_OBJECT_PREFIX + - stringify(m_header_ioctx.get_id()) + "." + journal_id + "."; + m_header_oid = header_oid(journal_id); + m_object_oid_prefix = object_oid_prefix(m_header_ioctx.get_id(), journal_id); m_metadata = new JournalMetadata(m_header_ioctx, m_header_oid, m_client_id, commit_interval); @@ -108,6 +116,10 @@ int Journaler::init_complete() { return 0; } +void Journaler::shutdown() { + m_metadata->shutdown(); +} + int Journaler::create(uint8_t order, uint8_t splay_width, int64_t pool_id) { if (order > 64 || order < 12) { lderr(m_cct) << "order must be in the range [12, 64]" << dendl; @@ -127,10 +139,11 @@ int Journaler::create(uint8_t order, uint8_t splay_width, int64_t pool_id) { return 0; } -int Journaler::remove() { +int Journaler::remove(bool force) { m_metadata->shutdown(); - int r = m_trimmer->remove_objects(); + ldout(m_cct, 5) << "removing journal: " << m_header_oid << dendl; + int r = m_trimmer->remove_objects(force); if (r < 0) { lderr(m_cct) << "failed to remove journal objects: " << cpp_strerror(r) << dendl; @@ -165,7 +178,8 @@ void Journaler::start_live_replay(ReplayHandler *replay_handler, m_player->prefetch_and_watch(interval); } -bool Journaler::try_pop_front(ReplayEntry *replay_entry) { +bool Journaler::try_pop_front(ReplayEntry *replay_entry, + std::string* tag) { assert(m_player != NULL); Entry entry; @@ -175,6 +189,9 @@ bool Journaler::try_pop_front(ReplayEntry *replay_entry) { } *replay_entry = ReplayEntry(entry.get_data(), commit_tid); + if (tag != NULL) { + *tag = entry.get_tag(); + } return true; } @@ -226,6 +243,15 @@ void Journaler::create_player(ReplayHandler *replay_handler) { replay_handler); } +void Journaler::get_metadata(uint8_t *order, uint8_t *splay_width, + int64_t *pool_id) { + assert(m_metadata != NULL); + + *order = m_metadata->get_order(); + *splay_width = m_metadata->get_splay_width(); + *pool_id = m_metadata->get_pool_id(); +} + std::ostream &operator<<(std::ostream &os, const Journaler &journaler) { os << "[metadata="; diff --git a/src/journal/Journaler.h b/src/journal/Journaler.h index 1230e409b2df5..48c800d407f24 100644 --- a/src/journal/Journaler.h +++ b/src/journal/Journaler.h @@ -26,22 +26,28 @@ class ReplayHandler; class Journaler { public: + + static std::string header_oid(const std::string &journal_id); + static std::string object_oid_prefix(int pool_id, + const std::string &journal_id); + Journaler(librados::IoCtx &header_ioctx, const std::string &journal_id, const std::string &client_id, double commit_interval); ~Journaler(); int exists(bool *header_exists) const; int create(uint8_t order, uint8_t splay_width, int64_t pool_id); - int remove(); + int remove(bool force); void init(Context *on_init); + void shutdown(); int register_client(const std::string &description); int unregister_client(); void start_replay(ReplayHandler *replay_handler); void start_live_replay(ReplayHandler *replay_handler, double interval); - bool try_pop_front(ReplayEntry *replay_entry); + bool try_pop_front(ReplayEntry *replay_entry, std::string* tag = NULL); void stop_replay(); void start_append(int flush_interval, uint64_t flush_bytes, double flush_age); @@ -52,6 +58,8 @@ public: void committed(const ReplayEntry &replay_entry); void committed(const Future &future); + void get_metadata(uint8_t *order, uint8_t *splay_width, int64_t *pool_id); + private: struct C_InitJournaler : public Context { Journaler *journaler; diff --git a/src/librbd/Journal.cc b/src/librbd/Journal.cc index e60f2a11bf133..1b71d0418dc7f 100644 --- a/src/librbd/Journal.cc +++ b/src/librbd/Journal.cc @@ -153,7 +153,7 @@ int Journal::remove(librados::IoCtx &io_ctx, const std::string &image_id) { return r; } - r = journaler.remove(); + r = journaler.remove(false); if (r < 0) { lderr(cct) << "failed to remove journal: " << cpp_strerror(r) << dendl; return r; @@ -161,6 +161,46 @@ int Journal::remove(librados::IoCtx &io_ctx, const std::string &image_id) { return 0; } +int Journal::reset(librados::IoCtx &io_ctx, const std::string &image_id) { + CephContext *cct = reinterpret_cast(io_ctx.cct()); + ldout(cct, 5) << __func__ << ": image=" << image_id << dendl; + + ::journal::Journaler journaler(io_ctx, image_id, "", + cct->_conf->rbd_journal_commit_age); + + C_SaferCond cond; + journaler.init(&cond); + + int r = cond.wait(); + if (r == -ENOENT) { + return 0; + } else if (r < 0) { + lderr(cct) << "failed to initialize journal: " << cpp_strerror(r) << dendl; + return r; + } + + uint8_t order, splay_width; + int64_t pool_id; + journaler.get_metadata(&order, &splay_width, &pool_id); + + r = journaler.remove(true); + if (r < 0) { + lderr(cct) << "failed to reset journal: " << cpp_strerror(r) << dendl; + return r; + } + r = journaler.create(order, splay_width, pool_id); + if (r < 0) { + lderr(cct) << "failed to create journal: " << cpp_strerror(r) << dendl; + return r; + } + r = journaler.register_client(CLIENT_DESCRIPTION); + if (r < 0) { + lderr(cct) << "failed to register client: " << cpp_strerror(r) << dendl; + return r; + } + return 0; +} + bool Journal::is_journal_ready() const { Mutex::Locker locker(m_lock); return (m_state == STATE_RECORDING); diff --git a/src/librbd/Journal.h b/src/librbd/Journal.h index eb01e4c1ccc6c..d2aae91be2e2f 100644 --- a/src/librbd/Journal.h +++ b/src/librbd/Journal.h @@ -46,6 +46,7 @@ public: double commit_age, uint8_t order, uint8_t splay_width, const std::string &object_pool); static int remove(librados::IoCtx &io_ctx, const std::string &image_id); + static int reset(librados::IoCtx &io_ctx, const std::string &image_id); bool is_journal_ready() const; bool is_journal_replaying() const; diff --git a/src/test/cli/rbd/help.t b/src/test/cli/rbd/help.t index 6fbd7eb00e10b..b459056ca7aaa 100644 --- a/src/test/cli/rbd/help.t +++ b/src/test/cli/rbd/help.t @@ -30,6 +30,12 @@ import-diff Import an incremental diff. info Show information about image size, striping, etc. + journal export Export image journal. + journal import Import image journal. + journal info Show information about image journal. + journal inspect Inspect image journal for structural errors. + journal reset Reset image journal. + journal status Show status of image journal. list (ls) List rbd images. lock add Take a lock on an image. lock list (lock ls) Show locks held on an image. @@ -470,6 +476,119 @@ --format arg output format [plain, json, or xml] --pretty-format pretty formatting (json and xml) + rbd help journal export + usage: rbd journal export [--pool ] [--image ] + [--journal ] [--path ] [--verbose] + [--no-error] + + + Export image journal. + + Positional arguments + source journal specification + (example: [/]) + export file (or '-' for stdout) + + Optional arguments + -p [ --pool ] arg source pool name + --image arg source image name + --journal arg source journal name + --path arg export file (or '-' for stdout) + --verbose be verbose + --no-error continue after error + + rbd help journal import + usage: rbd journal import [--path ] [--dest-pool ] + [--dest ] [--dest-journal ] + [--verbose] [--no-error] + + + Import image journal. + + Positional arguments + import file (or '-' for stdin) + destination journal specification + (example: [/]) + + Optional arguments + --path arg import file (or '-' for stdin) + --dest-pool arg destination pool name + --dest arg destination image name + --dest-journal arg destination journal name + --verbose be verbose + --no-error continue after error + + rbd help journal info + usage: rbd journal info [--pool ] [--image ] + [--journal ] [--format ] + [--pretty-format] + + + Show information about image journal. + + Positional arguments + journal specification + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image arg image name + --journal arg journal name + --format arg output format [plain, json, or xml] + --pretty-format pretty formatting (json and xml) + + rbd help journal inspect + usage: rbd journal inspect [--pool ] [--image ] + [--journal ] [--verbose] + + + Inspect image journal for structural errors. + + Positional arguments + journal specification + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image arg image name + --journal arg journal name + --verbose be verbose + + rbd help journal reset + usage: rbd journal reset [--pool ] [--image ] + [--journal ] + + + Reset image journal. + + Positional arguments + journal specification + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image arg image name + --journal arg journal name + + rbd help journal status + usage: rbd journal status [--pool ] [--image ] + [--journal ] [--format ] + [--pretty-format] + + + Show status of image journal. + + Positional arguments + journal specification + (example: [/]) + + Optional arguments + -p [ --pool ] arg pool name + --image arg image name + --journal arg journal name + --format arg output format [plain, json, or xml] + --pretty-format pretty formatting (json and xml) + rbd help list usage: rbd list [--long] [--pool ] [--format ] [--pretty-format] diff --git a/src/test/journal/test_JournalTrimmer.cc b/src/test/journal/test_JournalTrimmer.cc index ff1f6b82f718b..18572aa609ce1 100644 --- a/src/test/journal/test_JournalTrimmer.cc +++ b/src/test/journal/test_JournalTrimmer.cc @@ -163,7 +163,7 @@ TEST_F(TestJournalTrimmer, RemoveObjects) { journal::JournalTrimmer *trimmer = create_trimmer(oid, metadata); - ASSERT_EQ(0, trimmer->remove_objects()); + ASSERT_EQ(0, trimmer->remove_objects(false)); ASSERT_TRUE(wait_for_update(metadata)); ASSERT_EQ(-ENOENT, assert_exists(oid + ".0")); @@ -183,6 +183,7 @@ TEST_F(TestJournalTrimmer, RemoveObjectsWithOtherClient) { ASSERT_TRUE(wait_for_update(metadata)); journal::JournalTrimmer *trimmer = create_trimmer(oid, metadata); - ASSERT_EQ(-EBUSY, trimmer->remove_objects()); + ASSERT_EQ(-EBUSY, trimmer->remove_objects(false)); + ASSERT_EQ(0, trimmer->remove_objects(true)); } diff --git a/src/test/run-rbd-tests b/src/test/run-rbd-tests index 82d106e90120e..dd76ce7218564 100755 --- a/src/test/run-rbd-tests +++ b/src/test/run-rbd-tests @@ -19,6 +19,8 @@ run_cli_tests() { $CEPH_SRC/../qa/workunits/rbd/import_export.sh recreate_pool rbd $CEPH_SRC/../qa/workunits/rbd/copy.sh + recreate_pool rbd + $CEPH_SRC/../qa/workunits/rbd/journal.sh } run_api_tests() { diff --git a/src/tools/Makefile-client.am b/src/tools/Makefile-client.am index 904261bf5b450..72d105835f35d 100644 --- a/src/tools/Makefile-client.am +++ b/src/tools/Makefile-client.am @@ -45,6 +45,7 @@ rbd_SOURCES = \ tools/rbd/action/Import.cc \ tools/rbd/action/ImportDiff.cc \ tools/rbd/action/Info.cc \ + tools/rbd/action/Journal.cc \ tools/rbd/action/Kernel.cc \ tools/rbd/action/Nbd.cc \ tools/rbd/action/List.cc \ @@ -65,7 +66,8 @@ noinst_HEADERS += \ tools/rbd/Shell.h \ tools/rbd/Utils.h rbd_LDADD = \ - $(LIBKRBD) $(LIBRBD) $(LIBRADOS) $(CEPH_GLOBAL) \ + libjournal.la libcls_journal_client.la \ + $(LIBKRBD) $(LIBRBD) $(LIBRBD_TYPES) $(LIBRADOS) $(CEPH_GLOBAL) \ $(BOOST_REGEX_LIBS) $(BOOST_PROGRAM_OPTIONS_LIBS) if LINUX bin_PROGRAMS += rbd diff --git a/src/tools/rbd/ArgumentTypes.cc b/src/tools/rbd/ArgumentTypes.cc index 1618a6e51ba38..0c3dc5597f87e 100644 --- a/src/tools/rbd/ArgumentTypes.cc +++ b/src/tools/rbd/ArgumentTypes.cc @@ -124,6 +124,29 @@ void add_snap_option(po::options_description *opt, (name.c_str(), po::value(), description.c_str()); } +void add_journal_option(po::options_description *opt, + ArgumentModifier modifier, + const std::string &desc_suffix) { + std::string name = JOURNAL_NAME; + std::string description = "journal name"; + switch (modifier) { + case ARGUMENT_MODIFIER_NONE: + break; + case ARGUMENT_MODIFIER_SOURCE: + description = "source " + description; + break; + case ARGUMENT_MODIFIER_DEST: + name = DEST_JOURNAL_NAME; + description = "destination " + description; + break; + } + description += desc_suffix; + + // TODO add validator + opt->add_options() + (name.c_str(), po::value(), description.c_str()); +} + void add_pool_options(boost::program_options::options_description *pos, boost::program_options::options_description *opt) { pos->add_options() @@ -167,6 +190,20 @@ void add_image_or_snap_spec_options(po::options_description *pos, add_snap_option(opt, modifier); } +void add_journal_spec_options(po::options_description *pos, + po::options_description *opt, + ArgumentModifier modifier) { + + pos->add_options() + ((get_name_prefix(modifier) + JOURNAL_SPEC).c_str(), + (get_description_prefix(modifier) + "journal specification\n" + + "(example: [/])").c_str()); + add_pool_option(opt, modifier); + add_image_option(opt, modifier); + add_journal_option(opt, modifier); +} + + void add_create_image_options(po::options_description *opt, bool include_format) { // TODO get default image format from conf @@ -215,6 +252,16 @@ void add_format_options(boost::program_options::options_description *opt) { "pretty formatting (json and xml)"); } +void add_verbose_option(boost::program_options::options_description *opt) { + opt->add_options() + (VERBOSE.c_str(), po::bool_switch(), "be verbose"); +} + +void add_no_error_option(boost::program_options::options_description *opt) { + opt->add_options() + (NO_ERROR.c_str(), po::bool_switch(), "continue after error"); +} + std::string get_short_features_help(bool append_suffix) { std::ostringstream oss; bool first_feature = true; diff --git a/src/tools/rbd/ArgumentTypes.h b/src/tools/rbd/ArgumentTypes.h index d8ecadd521c22..e9240f27cb945 100644 --- a/src/tools/rbd/ArgumentTypes.h +++ b/src/tools/rbd/ArgumentTypes.h @@ -40,6 +40,7 @@ static const std::string POSITIONAL_ARGUMENTS("positional-arguments"); static const std::string IMAGE_SPEC("image-spec"); 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"); // optional arguments @@ -49,6 +50,8 @@ static const std::string DEST_POOL_NAME("dest-pool"); static const std::string IMAGE_NAME("image"); static const std::string DEST_IMAGE_NAME("dest"); static const std::string SNAPSHOT_NAME("snap"); +static const std::string JOURNAL_NAME("journal"); +static const std::string DEST_JOURNAL_NAME("dest-journal"); static const std::string PATH("path"); static const std::string FROM_SNAPSHOT_NAME("from-snap"); static const std::string WHOLE_OBJECT("whole-object"); @@ -65,9 +68,11 @@ static const std::string IMAGE_STRIPE_COUNT("stripe-count"); static const std::string NO_PROGRESS("no-progress"); static const std::string FORMAT("format"); 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::set SWITCH_ARGUMENTS = { - WHOLE_OBJECT, NO_PROGRESS, PRETTY_FORMAT}; + WHOLE_OBJECT, NO_PROGRESS, PRETTY_FORMAT, VERBOSE, NO_ERROR}; struct ImageSize {}; struct ImageOrder {}; @@ -108,6 +113,10 @@ void add_image_option(boost::program_options::options_description *opt, void add_snap_option(boost::program_options::options_description *opt, ArgumentModifier modifier); +void add_journal_option(boost::program_options::options_description *opt, + ArgumentModifier modifier, + const std::string &desc_suffix = ""); + void add_pool_options(boost::program_options::options_description *pos, boost::program_options::options_description *opt); @@ -124,6 +133,11 @@ void add_image_or_snap_spec_options( boost::program_options::options_description *opt, ArgumentModifier modifier); +void add_journal_spec_options( + boost::program_options::options_description *pos, + boost::program_options::options_description *opt, + ArgumentModifier modifier); + void add_create_image_options(boost::program_options::options_description *opt, bool include_format); @@ -137,6 +151,10 @@ void add_no_progress_option(boost::program_options::options_description *opt); void add_format_options(boost::program_options::options_description *opt); +void add_verbose_option(boost::program_options::options_description *opt); + +void add_no_error_option(boost::program_options::options_description *opt); + std::string get_short_features_help(bool append_suffix); std::string get_long_features_help(); diff --git a/src/tools/rbd/Utils.cc b/src/tools/rbd/Utils.cc index 4a56785e86579..cb71ebdf1e24c 100644 --- a/src/tools/rbd/Utils.cc +++ b/src/tools/rbd/Utils.cc @@ -196,6 +196,97 @@ int get_pool_image_snapshot_names(const po::variables_map &vm, return 0; } +int get_pool_journal_names(const po::variables_map &vm, + at::ArgumentModifier mod, + size_t *spec_arg_index, + std::string *pool_name, + std::string *journal_name) { + std::string pool_key = (mod == at::ARGUMENT_MODIFIER_DEST ? + at::DEST_POOL_NAME : at::POOL_NAME); + std::string image_key = (mod == at::ARGUMENT_MODIFIER_DEST ? + at::DEST_IMAGE_NAME : at::IMAGE_NAME); + std::string journal_key = (mod == at::ARGUMENT_MODIFIER_DEST ? + at::DEST_JOURNAL_NAME : at::JOURNAL_NAME); + + if (vm.count(pool_key) && pool_name != nullptr) { + *pool_name = vm[pool_key].as(); + } + if (vm.count(journal_key) && journal_name != nullptr) { + *journal_name = vm[journal_key].as(); + } + + std::string image_name; + if (vm.count(image_key)) { + image_name = vm[image_key].as(); + } + + if (journal_name != nullptr && !journal_name->empty()) { + // despite the separate pool option, + // we can also specify them via the journal option + std::string journal_name_copy(*journal_name); + extract_spec(journal_name_copy, pool_name, journal_name, nullptr); + } + + if (!image_name.empty()) { + // despite the separate pool option, + // we can also specify them via the image option + std::string image_name_copy(image_name); + extract_spec(image_name_copy, pool_name, &image_name, nullptr); + } + + int r; + if (journal_name != nullptr && spec_arg_index != nullptr && + journal_name->empty()) { + std::string spec = get_positional_argument(vm, (*spec_arg_index)++); + if (!spec.empty()) { + r = extract_spec(spec, pool_name, journal_name, nullptr); + if (r < 0) { + return r; + } + } + } + + if (pool_name->empty()) { + *pool_name = at::DEFAULT_POOL_NAME; + } + + if (journal_name != nullptr && journal_name->empty() && !image_name.empty()) { + // Try to get journal name from image info. + librados::Rados rados; + librados::IoCtx io_ctx; + librbd::Image image; + int r = init_and_open_image(*pool_name, image_name, "", true, + &rados, &io_ctx, &image); + if (r < 0) { + std::cerr << "rbd: failed to open image " << image_name + << " to get journal name: " << cpp_strerror(r) << std::endl; + return r; + } + + uint64_t features; + r = image.features(&features); + if (r < 0) { + return r; + } + if ((features & RBD_FEATURE_JOURNALING) == 0) { + std::cerr << "rbd: journaling is not enabled for image " << image_name + << std::endl; + return -EINVAL; + } + *journal_name = image_id(image); + } + + if (journal_name != nullptr && journal_name->empty()) { + std::string prefix = at::get_description_prefix(mod); + std::cerr << "rbd: " + << (mod == at::ARGUMENT_MODIFIER_DEST ? prefix : std::string()) + << "journal was not specified" << std::endl; + return -EINVAL; + } + + return 0; +} + int validate_snapshot_name(at::ArgumentModifier mod, const std::string &snap_name, SnapshotPresence snapshot_presence) { @@ -445,5 +536,19 @@ int snap_set(librbd::Image &image, const std::string snap_name) { return 0; } +std::string image_id(librbd::Image& image) { + librbd::image_info_t info; + int r = image.stat(info, sizeof(info)); + if (r < 0) { + return string(); + } + + char prefix[RBD_MAX_BLOCK_NAME_SIZE + 1]; + strncpy(prefix, info.block_name_prefix, RBD_MAX_BLOCK_NAME_SIZE); + prefix[RBD_MAX_BLOCK_NAME_SIZE] = '\0'; + + return string(prefix + strlen(RBD_DATA_PREFIX)); +} + } // namespace utils } // namespace rbd diff --git a/src/tools/rbd/Utils.h b/src/tools/rbd/Utils.h index b30236db0be3c..1e4ef6c001e38 100644 --- a/src/tools/rbd/Utils.h +++ b/src/tools/rbd/Utils.h @@ -55,6 +55,11 @@ int get_pool_image_snapshot_names( std::string *pool_name, std::string *image_name, std::string *snap_name, SnapshotPresence snapshot_presence, bool image_required = true); +int get_pool_journal_names( + const boost::program_options::variables_map &vm, + argument_types::ArgumentModifier mod, size_t *spec_arg_index, + std::string *pool_name, std::string *journal_name); + int validate_snapshot_name(argument_types::ArgumentModifier mod, const std::string &snap_name, SnapshotPresence snapshot_presence); @@ -91,6 +96,8 @@ int init_and_open_image(const std::string &pool_name, int snap_set(librbd::Image &image, const std::string snap_name); +std::string image_id(librbd::Image& image); + } // namespace utils } // namespace rbd diff --git a/src/tools/rbd/action/Info.cc b/src/tools/rbd/action/Info.cc index 76e394049f415..f3d81ac0ad3e3 100644 --- a/src/tools/rbd/action/Info.cc +++ b/src/tools/rbd/action/Info.cc @@ -171,6 +171,14 @@ static int do_show_info(const char *imgname, librbd::Image& image, } } + if (features & RBD_FEATURE_JOURNALING) { + if (f) { + f->dump_string("journal", utils::image_id(image)); + } else { + std::cout << "\tjournal: " << utils::image_id(image) << std::endl; + } + } + if (f) { f->close_section(); f->flush(std::cout); diff --git a/src/tools/rbd/action/Journal.cc b/src/tools/rbd/action/Journal.cc new file mode 100644 index 0000000000000..e00665b242500 --- /dev/null +++ b/src/tools/rbd/action/Journal.cc @@ -0,0 +1,969 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "tools/rbd/ArgumentTypes.h" +#include "tools/rbd/Shell.h" +#include "tools/rbd/Utils.h" +#include "common/Cond.h" +#include "common/Formatter.h" +#include "common/ceph_json.h" +#include "common/errno.h" +#include "common/safe_io.h" +#include "include/stringify.h" +#include +#include +#include + +#include "cls/journal/cls_journal_types.h" +#include "cls/journal/cls_journal_client.h" + +#include "journal/Journaler.h" +#include "journal/ReplayEntry.h" +#include "journal/ReplayHandler.h" +//#include "librbd/Journal.h" // XXXMG: for librbd::Journal::reset() +#include "librbd/JournalTypes.h" + +namespace rbd { +namespace action { +namespace journal { + +namespace at = argument_types; +namespace po = boost::program_options; + +static int do_show_journal_info(librados::Rados& rados, librados::IoCtx& io_ctx, + const std::string& journal_id, Formatter *f) +{ + int r; + C_SaferCond cond; + + std::string header_oid = ::journal::Journaler::header_oid(journal_id); + std::string object_oid_prefix = ::journal::Journaler::object_oid_prefix( + io_ctx.get_id(), journal_id); + uint8_t order; + uint8_t splay_width; + int64_t pool_id; + + cls::journal::client::get_immutable_metadata(io_ctx, header_oid, &order, + &splay_width, &pool_id, &cond); + r = cond.wait(); + if (r < 0) { + std::cerr << "failed to get journal metadata: " << cpp_strerror(r) + << std::endl; + return r; + } + + std::string object_pool_name; + if (pool_id >= 0) { + r = rados.pool_reverse_lookup(pool_id, &object_pool_name); + if (r < 0) { + std::cerr << "error looking up pool name for pool_id=" << pool_id << ": " + << cpp_strerror(r) << std::endl; + } + } + + if (f) { + f->open_object_section("journal"); + f->dump_string("journal_id", journal_id); + f->dump_string("header_oid", header_oid); + f->dump_string("object_oid_prefix", object_oid_prefix); + f->dump_int("order", order); + f->dump_int("splay_width", splay_width); + if (!object_pool_name.empty()) { + f->dump_string("object_pool", object_pool_name); + } + f->close_section(); + f->flush(std::cout); + } else { + std::cout << "rbd journal '" << journal_id << "':" << std::endl; + std::cout << "\theader_oid: " << header_oid << std::endl; + std::cout << "\tobject_oid_prefix: " << object_oid_prefix << std::endl; + std::cout << "\torder: " << static_cast(order) << " (" + << prettybyte_t(1ull << order) << " objects)"<< std::endl; + std::cout << "\tsplay_width: " << static_cast(splay_width) << std::endl; + if (!object_pool_name.empty()) { + std::cout << "\tobject_pool: " << object_pool_name << std::endl; + } + } + return 0; +} + +static int do_show_journal_status(librados::IoCtx& io_ctx, + const std::string& journal_id, Formatter *f) +{ + int r; + + C_SaferCond cond; + uint64_t minimum_set; + uint64_t active_set; + std::set registered_clients; + std::string oid = ::journal::Journaler::header_oid(journal_id); + + cls::journal::client::get_mutable_metadata(io_ctx, oid, &minimum_set, + &active_set, ®istered_clients, + &cond); + r = cond.wait(); + if (r < 0) { + std::cerr << "warning: failed to get journal metadata" << std::endl; + return r; + } + + if (f) { + f->open_object_section("status"); + f->dump_unsigned("minimum_set", minimum_set); + f->dump_unsigned("active_set", active_set); + f->open_object_section("registered_clients"); + for (std::set::iterator c = + registered_clients.begin(); c != registered_clients.end(); c++) { + c->dump(f); + } + f->close_section(); + f->close_section(); + f->flush(std::cout); + } else { + std::cout << "minimum_set: " << minimum_set << std::endl; + std::cout << "active_set: " << active_set << std::endl; + std::cout << "registered clients: " << std::endl; + for (std::set::iterator c = + registered_clients.begin(); c != registered_clients.end(); c++) { + std::cout << "\t" << *c << std::endl; + } + } + return 0; +} + +static int do_reset_journal(librados::IoCtx& io_ctx, + const std::string& journal_id) +{ + // XXXMG: does not work due to a linking issue + //return librbd::Journal::reset(io_ctx, journal_id); + + ::journal::Journaler journaler(io_ctx, journal_id, "", 5); + + C_SaferCond cond; + journaler.init(&cond); + + int r = cond.wait(); + if (r < 0) { + std::cerr << "failed to initialize journal: " << cpp_strerror(r) + << std::endl; + return r; + } + + uint8_t order, splay_width; + int64_t pool_id; + journaler.get_metadata(&order, &splay_width, &pool_id); + + r = journaler.remove(true); + if (r < 0) { + std::cerr << "failed to reset journal: " << cpp_strerror(r) << std::endl; + return r; + } + r = journaler.create(order, splay_width, pool_id); + if (r < 0) { + std::cerr << "failed to create journal: " << cpp_strerror(r) << std::endl; + return r; + } + + // XXXMG + const std::string CLIENT_DESCRIPTION = "master image"; + + r = journaler.register_client(CLIENT_DESCRIPTION); + if (r < 0) { + std::cerr << "failed to register client: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; +} + +class Journaler : public ::journal::Journaler { +public: + Journaler(librados::IoCtx& io_ctx, const std::string& journal_id, + const std::string &client_id) : + ::journal::Journaler(io_ctx, journal_id, client_id, 5) { + } + + int init() { + int r; + + r = register_client("rbd journal"); + if (r < 0) { + std::cerr << "failed to register client: " << cpp_strerror(r) + << std::endl; + return r; + } + + C_SaferCond cond; + + ::journal::Journaler::init(&cond); + r = cond.wait(); + if (r < 0) { + std::cerr << "failed to initialize journal: " << cpp_strerror(r) + << std::endl; + (void) unregister_client(); + return r; + } + + return 0; + } + + int shutdown() { + ::journal::Journaler::shutdown(); + + int r = unregister_client(); + if (r < 0) { + std::cerr << "rbd: failed to unregister journal client: " + << cpp_strerror(r) << std::endl; + } + return r; + } +}; + +class JournalPlayer { +public: + JournalPlayer(librados::IoCtx& io_ctx, const std::string& journal_id, + const std::string &client_id) : + m_journaler(io_ctx, journal_id, client_id), + m_cond(), + m_r(0) { + } + + virtual ~JournalPlayer() {} + + virtual int exec() { + int r; + + r = m_journaler.init(); + if (r < 0) { + return r; + } + + ReplayHandler replay_handler(this); + + m_journaler.start_replay(&replay_handler); + + r = m_cond.wait(); + + if (r < 0) { + std::cerr << "rbd: failed to process journal: " << cpp_strerror(r) + << std::endl; + if (m_r == 0) { + m_r = r; + } + } + + r = m_journaler.shutdown(); + if (r < 0 && m_r == 0) { + m_r = r; + } + + return m_r; + } + +protected: + struct ReplayHandler : public ::journal::ReplayHandler { + JournalPlayer *journal; + ReplayHandler(JournalPlayer *_journal) : journal(_journal) {} + + virtual void get() {} + virtual void put() {} + + virtual void handle_entries_available() { + journal->handle_replay_ready(); + } + virtual void handle_complete(int r) { + journal->handle_replay_complete(r); + } + }; + + void handle_replay_ready() { + int r = 0; + while (true) { + ::journal::ReplayEntry replay_entry; + std::string tag; + if (!m_journaler.try_pop_front(&replay_entry, &tag)) { + break; + } + + r = process_entry(replay_entry, tag); + if (r < 0) { + break; + } + } + } + + virtual int process_entry(::journal::ReplayEntry replay_entry, + std::string& tag) = 0; + + void handle_replay_complete(int r) { + m_journaler.stop_replay(); + m_cond.complete(r); + } + + Journaler m_journaler; + C_SaferCond m_cond; + int m_r; +}; + +static int inspect_entry(bufferlist& data, + librbd::journal::EventEntry& event_entry, + bool verbose) { + try { + bufferlist::iterator it = data.begin(); + ::decode(event_entry, it); + } catch (const buffer::error &err) { + std::cerr << "failed to decode event entry: " << err.what() << std::endl; + return -EINVAL; + } + if (verbose) { + JSONFormatter f(true); + f.open_object_section("event_entry"); + event_entry.dump(&f); + f.close_section(); + f.flush(std::cout); + } + return 0; +} + +class JournalInspector : public JournalPlayer { +public: + JournalInspector(librados::IoCtx& io_ctx, const std::string& journal_id, + bool verbose) : + JournalPlayer(io_ctx, journal_id, "INSPECT"), + m_verbose(verbose), + m_s() { + } + + int exec() { + int r = JournalPlayer::exec(); + m_s.print(); + return r; + } + +private: + struct Stats { + Stats() : total(0), error(0) {} + + void print() { + std::cout << "Summary:" << std::endl + << " " << total << " entries inspected, " << error << " errors" + << std::endl; + } + + int total; + int error; + }; + + int process_entry(::journal::ReplayEntry replay_entry, + std::string& tag) { + m_s.total++; + if (m_verbose) { + std::cout << "Entry: tag=" << tag << ", commit_tid=" + << replay_entry.get_commit_tid() << std::endl; + } + bufferlist data = replay_entry.get_data(); + librbd::journal::EventEntry event_entry; + int r = inspect_entry(data, event_entry, m_verbose); + if (r < 0) { + m_r = r; + m_s.error++; + } + return 0; + } + + bool m_verbose; + Stats m_s; +}; + +static int do_inspect_journal(librados::IoCtx& io_ctx, + const std::string& journal_id, + bool verbose) { + return JournalInspector(io_ctx, journal_id, verbose).exec(); +} + +struct ExportEntry { + std::string tag; + uint64_t commit_tid; + int type; + bufferlist entry; + + ExportEntry() : tag(), commit_tid(0), type(0), entry() {} + + ExportEntry(const std::string& tag, uint64_t commit_tid, int type, + const bufferlist& entry) + : tag(tag), commit_tid(commit_tid), type(type), entry(entry) { + } + + void dump(Formatter *f) const { + ::encode_json("tag", tag, f); + ::encode_json("commit_tid", commit_tid, f); + ::encode_json("type", type, f); + ::encode_json("entry", entry, f); + } + + void decode_json(JSONObj *obj) { + JSONDecoder::decode_json("tag", tag, obj); + JSONDecoder::decode_json("commit_tid", commit_tid, obj); + JSONDecoder::decode_json("type", type, obj); + JSONDecoder::decode_json("entry", entry, obj); + } +}; + +class JournalExporter : public JournalPlayer { +public: + JournalExporter(librados::IoCtx& io_ctx, const std::string& journal_id, + int fd, bool no_error, bool verbose) : + JournalPlayer(io_ctx, journal_id, "EXPORT"), + m_journal_id(journal_id), + m_fd(fd), + m_no_error(no_error), + m_verbose(verbose), + m_s() { + } + + int exec() { + std::string header("# journal_id: " + m_journal_id + "\n"); + int r; + r = safe_write(m_fd, header.c_str(), header.size()); + if (r < 0) { + std::cerr << "rbd: failed to write to export file: " << cpp_strerror(r) + << std::endl; + return r; + } + r = JournalPlayer::exec(); + m_s.print(); + return r; + } + +private: + struct Stats { + Stats() : total(0), error(0) {} + + void print() { + std::cout << total << " entries processed, " << error << " errors" + << std::endl; + } + + int total; + int error; + }; + + int process_entry(::journal::ReplayEntry replay_entry, + std::string& tag) { + m_s.total++; + int type = -1; + bufferlist entry = replay_entry.get_data(); + librbd::journal::EventEntry event_entry; + int r = inspect_entry(entry, event_entry, m_verbose); + if (r < 0) { + m_s.error++; + m_r = r; + return m_no_error ? 0 : r; + } else { + type = event_entry.get_event_type(); + } + ExportEntry export_entry(tag, replay_entry.get_commit_tid(), type, entry); + JSONFormatter f; + ::encode_json("event_entry", export_entry, &f); + std::ostringstream oss; + f.flush(oss); + std::string objstr = oss.str(); + std::string header = stringify(objstr.size()) + " "; + r = safe_write(m_fd, header.c_str(), header.size()); + if (r == 0) { + r = safe_write(m_fd, objstr.c_str(), objstr.size()); + } + if (r == 0) { + r = safe_write(m_fd, "\n", 1); + } + if (r < 0) { + std::cerr << "rbd: failed to write to export file: " << cpp_strerror(r) + << std::endl; + m_s.error++; + return r; + } + return 0; + } + + std::string m_journal_id; + int m_fd; + bool m_no_error; + bool m_verbose; + Stats m_s; +}; + +static int do_export_journal(librados::IoCtx& io_ctx, + const std::string& journal_id, + const std::string& path, + bool no_error, bool verbose) { + int r; + int fd; + bool to_stdout = path == "-"; + if (to_stdout) { + fd = STDOUT_FILENO; + } else { + fd = open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) { + r = -errno; + std::cerr << "rbd: error creating " << path << std::endl; + return r; + } + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); + } + + r = JournalExporter(io_ctx, journal_id, fd, no_error, verbose).exec(); + + if (!to_stdout) { + close(fd); + } + + return r; +} + +class JournalImporter { +public: + JournalImporter(librados::IoCtx& io_ctx, const std::string& journal_id, + int fd, bool no_error, bool verbose) : + m_journaler(io_ctx, journal_id, "IMPORT"), + m_fd(fd), + m_no_error(no_error), + m_verbose(verbose) { + } + + bool read_entry(bufferlist& bl, int& r) { + // Entries are storead in the file using the following format: + // + // # Optional comments + // NNN {json encoded entry} + // ... + // + // Where NNN is the encoded entry size. + bl.clear(); + char buf[80]; + // Skip line feed and comments (lines started with #). + while ((r = safe_read_exact(m_fd, buf, 1)) == 0) { + if (buf[0] == '\n') { + continue; + } else if (buf[0] == '#') { + while ((r = safe_read_exact(m_fd, buf, 1)) == 0) { + if (buf[0] == '\n') { + break; + } + } + } else { + break; + } + } + if (r < 0) { + if (r == -EDOM) { + r = 0; + } + return false; + } + // Read entry size to buf. + if (!isdigit(buf[0])) { + r = -EINVAL; + std::cerr << "rbd: import data invalid format (digit expected)" + << std::endl; + return false; + } + for (size_t i = 1; i < sizeof(buf); i++) { + r = safe_read_exact(m_fd, buf + i, 1); + if (r < 0) { + std::cerr << "rbd: error reading import data" << std::endl; + return false; + } + if (!isdigit(buf[i])) { + if (buf[i] != ' ') { + r = -EINVAL; + std::cerr << "rbd: import data invalid format (space expected)" + << std::endl; + return false; + } + buf[i] = '\0'; + break; + } + } + int entry_size = atoi(buf); + if (entry_size == 0) { + r = -EINVAL; + std::cerr << "rbd: import data invalid format (zero entry size)" + << std::endl; + return false; + } + assert(entry_size > 0); + // Read entry. + r = bl.read_fd(m_fd, entry_size); + if (r < 0) { + std::cerr << "rbd: error reading from stdin: " << cpp_strerror(r) + << std::endl; + return false; + } + if (r != entry_size) { + std::cerr << "rbd: error reading from stdin: trucated" + << std::endl; + r = -EINVAL; + return false; + } + r = 0; + return true; + } + + int exec() { + int r = m_journaler.init(); + if (r < 0) { + return r; + } + m_journaler.start_append(0, 0, 0); + + int r1 = 0; + bufferlist bl; + int n = 0; + int error_count = 0; + while (read_entry(bl, r)) { + n++; + error_count++; + JSONParser p; + if (!p.parse(bl.c_str(), bl.length())) { + std::cerr << "rbd: error parsing input (entry " << n << ")" + << std::endl; + r = -EINVAL; + if (m_no_error) { + r1 = r; + continue; + } else { + break; + } + } + ExportEntry e; + try { + decode_json_obj(e, &p); + } catch (JSONDecoder::err& err) { + std::cerr << "rbd: error json decoding import data (entry " << n << "):" + << err.message << std::endl; + r = -EINVAL; + if (m_no_error) { + r1 = r; + continue; + } else { + break; + } + } + librbd::journal::EventEntry event_entry; + r = inspect_entry(e.entry, event_entry, m_verbose); + if (r < 0) { + std::cerr << "rbd: corrupted entry " << n << ": tag=" << e.tag + << ", commit_tid=" << e.commit_tid << std::endl; + if (m_no_error) { + r1 = r; + continue; + } else { + break; + } + } + m_journaler.append(e.tag, e.entry); + error_count--; + } + + std::cout << n << " entries processed, " << error_count << " errors" << std::endl; + + std::cout << "Waiting for journal append to complete..." << std::endl; + + C_SaferCond cond; + m_journaler.stop_append(&cond); + r = cond.wait(); + + if (r < 0) { + std::cerr << "failed to append journal: " << cpp_strerror(r) << std::endl; + } + + if (r1 < 0 && r == 0) { + r = r1; + } + r1 = m_journaler.shutdown(); + if (r1 < 0 && r == 0) { + r = r1; + } + return r; + } + +private: + Journaler m_journaler; + int m_fd; + bool m_no_error; + bool m_verbose; +}; + +static int do_import_journal(librados::IoCtx& io_ctx, + const std::string& journal_id, + const std::string& path, + bool no_error, bool verbose) { + int r; + + int fd; + bool from_stdin = path == "-"; + if (from_stdin) { + fd = STDIN_FILENO; + } else { + if ((fd = open(path.c_str(), O_RDONLY)) < 0) { + r = -errno; + std::cerr << "rbd: error opening " << path << std::endl; + return r; + } + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); + } + + r = JournalImporter(io_ctx, journal_id, fd, no_error, verbose).exec(); + + if (!from_stdin) { + close(fd); + } + + return r; +} + +void get_info_arguments(po::options_description *positional, + po::options_description *options) { + at::add_journal_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); + at::add_format_options(options); +} + +int execute_info(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string journal_name; + int r = utils::get_pool_journal_names(vm, at::ARGUMENT_MODIFIER_NONE, + &arg_index, &pool_name, &journal_name); + if (r < 0) { + return r; + } + + at::Format::Formatter formatter; + 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; + } + + r = do_show_journal_info(rados, io_ctx, journal_name, formatter.get()); + if (r < 0) { + std::cerr << "rbd: journal info: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; + +} + +void get_status_arguments(po::options_description *positional, + po::options_description *options) { + at::add_journal_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); + at::add_format_options(options); +} + +int execute_status(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string journal_name; + int r = utils::get_pool_journal_names(vm, at::ARGUMENT_MODIFIER_NONE, + &arg_index, &pool_name, &journal_name); + if (r < 0) { + return r; + } + + at::Format::Formatter formatter; + 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; + } + + r = do_show_journal_status(io_ctx, journal_name, formatter.get()); + if (r < 0) { + std::cerr << "rbd: journal status: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; +} + +void get_reset_arguments(po::options_description *positional, + po::options_description *options) { + at::add_journal_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); +} + +int execute_reset(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string journal_name; + int r = utils::get_pool_journal_names(vm, at::ARGUMENT_MODIFIER_NONE, + &arg_index, &pool_name, &journal_name); + 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; + } + + r = do_reset_journal(io_ctx, journal_name); + if (r < 0) { + std::cerr << "rbd: journal reset: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; +} + +void get_inspect_arguments(po::options_description *positional, + po::options_description *options) { + at::add_journal_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); + at::add_verbose_option(options); +} + +int execute_inspect(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string journal_name; + int r = utils::get_pool_journal_names(vm, at::ARGUMENT_MODIFIER_NONE, + &arg_index, &pool_name, &journal_name); + 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; + } + + r = do_inspect_journal(io_ctx, journal_name, vm[at::VERBOSE].as()); + if (r < 0) { + std::cerr << "rbd: journal inspect: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; +} + +void get_export_arguments(po::options_description *positional, + po::options_description *options) { + at::add_journal_spec_options(positional, options, + at::ARGUMENT_MODIFIER_SOURCE); + at::add_path_options(positional, options, + "export file (or '-' for stdout)"); + at::add_verbose_option(options); + at::add_no_error_option(options); +} + +int execute_export(const po::variables_map &vm) { + size_t arg_index = 0; + std::string pool_name; + std::string journal_name; + int r = utils::get_pool_journal_names(vm, at::ARGUMENT_MODIFIER_SOURCE, + &arg_index, &pool_name, &journal_name); + if (r < 0) { + return r; + } + + std::string path; + r = utils::get_path(vm, utils::get_positional_argument(vm, 1), &path); + 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; + } + + r = do_export_journal(io_ctx, journal_name, path, vm[at::NO_ERROR].as(), + vm[at::VERBOSE].as()); + if (r < 0) { + std::cerr << "rbd: journal export: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; +} + +void get_import_arguments(po::options_description *positional, + po::options_description *options) { + at::add_path_options(positional, options, + "import file (or '-' for stdin)"); + at::add_journal_spec_options(positional, options, at::ARGUMENT_MODIFIER_DEST); + at::add_verbose_option(options); + at::add_no_error_option(options); +} + +int execute_import(const po::variables_map &vm) { + std::string path; + int r = utils::get_path(vm, utils::get_positional_argument(vm, 0), &path); + if (r < 0) { + return r; + } + + size_t arg_index = 1; + std::string pool_name; + std::string journal_name; + r = utils::get_pool_journal_names(vm, at::ARGUMENT_MODIFIER_DEST, + &arg_index, &pool_name, &journal_name); + 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; + } + + r = do_import_journal(io_ctx, journal_name, path, vm[at::NO_ERROR].as(), + vm[at::VERBOSE].as()); + if (r < 0) { + std::cerr << "rbd: journal export: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; +} + +Shell::Action action_info( + {"journal", "info"}, {}, "Show information about image journal.", "", + &get_info_arguments, &execute_info); + +Shell::Action action_status( + {"journal", "status"}, {}, "Show status of image journal.", "", + &get_status_arguments, &execute_status); + +Shell::Action action_reset( + {"journal", "reset"}, {}, "Reset image journal.", "", + &get_reset_arguments, &execute_reset); + +Shell::Action action_inspect( + {"journal", "inspect"}, {}, "Inspect image journal for structural errors.", "", + &get_inspect_arguments, &execute_inspect); + +Shell::Action action_export( + {"journal", "export"}, {}, "Export image journal.", "", + &get_export_arguments, &execute_export); + +Shell::Action action_import( + {"journal", "import"}, {}, "Import image journal.", "", + &get_import_arguments, &execute_import); + +} // namespace journal +} // namespace action +} // namespace rbd -- 2.39.5