]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rbd: add new journal rbd commands
authorMykola Golub <mgolub@mirantis.com>
Fri, 18 Sep 2015 06:23:53 +0000 (09:23 +0300)
committerMykola Golub <mgolub@mirantis.com>
Fri, 4 Dec 2015 11:18:30 +0000 (13:18 +0200)
Also, extend 'rbd info' to output journal spec if journaling is enabled.

Signed-off-by: Mykola Golub <mgolub@mirantis.com>
17 files changed:
qa/workunits/rbd/journal.sh [new file with mode: 0755]
src/journal/JournalTrimmer.cc
src/journal/JournalTrimmer.h
src/journal/Journaler.cc
src/journal/Journaler.h
src/librbd/Journal.cc
src/librbd/Journal.h
src/test/cli/rbd/help.t
src/test/journal/test_JournalTrimmer.cc
src/test/run-rbd-tests
src/tools/Makefile-client.am
src/tools/rbd/ArgumentTypes.cc
src/tools/rbd/ArgumentTypes.h
src/tools/rbd/Utils.cc
src/tools/rbd/Utils.h
src/tools/rbd/action/Info.cc
src/tools/rbd/action/Journal.cc [new file with mode: 0644]

diff --git a/qa/workunits/rbd/journal.sh b/qa/workunits/rbd/journal.sh
new file mode 100755 (executable)
index 0000000..99c647b
--- /dev/null
@@ -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 <testname> [-t <testname>...] [--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
index 33e3be2c5b0d1f7967c36e30120cef5726defbd0..9e781cebd01db4b0ea6c31956c3e92cf7cdbdde0 100644 (file)
@@ -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(&registered_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(&registered_clients);
+
+      if (registered_clients.size() == 0) {
+       return -EINVAL;
+      } else if (registered_clients.size() > 1) {
+       return -EBUSY;
+      }
+    }
+
     m_remove_set = std::numeric_limits<uint64_t>::max();
     m_remove_set_pending = true;
     m_remove_set_ctx = &ctx;
index 9f557a7671bc9d4465e2499ef6ca44ec1e0e5962..46db1c51bab43529c92d26ff879b84e32808a040 100644 (file)
@@ -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:
index 231cc0400d764f126727be3485baee06ef3da0cf..25a50b89bed5ffcbaf34321212646b8775f1c975 100644 (file)
@@ -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<CephContext *>(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=";
index 1230e409b2df5f6ef296b343229044e4f7f51430..48c800d407f248f348466ac4984b56a034307226 100644 (file)
@@ -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;
index e60f2a11bf1334ea157e5fd519f7c87e07460f66..1b71d0418dc7f21815198341879ec51526e4b0d6 100644 (file)
@@ -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<CephContext *>(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);
index eb01e4c1ccc6c5b3490b3c6911dbe6758f864427..d2aae91be2e2f749cb7ededc8d26100a286735c9 100644 (file)
@@ -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;
index 6fbd7eb00e10bf5abf021aed0e6517178b1e38d9..b459056ca7aaa574333487fb156552a03b82f3e9 100644 (file)
       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.
     --format arg          output format [plain, json, or xml]
     --pretty-format       pretty formatting (json and xml)
   
+  rbd help journal export
+  usage: rbd journal export [--pool <pool>] [--image <image>] 
+                            [--journal <journal>] [--path <path>] [--verbose] 
+                            [--no-error] 
+                            <source-journal-spec> <path-name> 
+  
+  Export image journal.
+  
+  Positional arguments
+    <source-journal-spec>  source journal specification
+                           (example: [<pool-name>/]<journal-name>)
+    <path-name>            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 <path>] [--dest-pool <dest-pool>] 
+                            [--dest <dest>] [--dest-journal <dest-journal>] 
+                            [--verbose] [--no-error] 
+                            <path-name> <dest-journal-spec> 
+  
+  Import image journal.
+  
+  Positional arguments
+    <path-name>          import file (or '-' for stdin)
+    <dest-journal-spec>  destination journal specification
+                         (example: [<pool-name>/]<journal-name>)
+  
+  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 <pool>] [--image <image>] 
+                          [--journal <journal>] [--format <format>] 
+                          [--pretty-format] 
+                          <journal-spec> 
+  
+  Show information about image journal.
+  
+  Positional arguments
+    <journal-spec>       journal specification
+                         (example: [<pool-name>/]<journal-name>)
+  
+  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 <pool>] [--image <image>] 
+                             [--journal <journal>] [--verbose] 
+                             <journal-spec> 
+  
+  Inspect image journal for structural errors.
+  
+  Positional arguments
+    <journal-spec>       journal specification
+                         (example: [<pool-name>/]<journal-name>)
+  
+  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 <pool>] [--image <image>] 
+                           [--journal <journal>] 
+                           <journal-spec> 
+  
+  Reset image journal.
+  
+  Positional arguments
+    <journal-spec>       journal specification
+                         (example: [<pool-name>/]<journal-name>)
+  
+  Optional arguments
+    -p [ --pool ] arg    pool name
+    --image arg          image name
+    --journal arg        journal name
+  
+  rbd help journal status
+  usage: rbd journal status [--pool <pool>] [--image <image>] 
+                            [--journal <journal>] [--format <format>] 
+                            [--pretty-format] 
+                            <journal-spec> 
+  
+  Show status of image journal.
+  
+  Positional arguments
+    <journal-spec>       journal specification
+                         (example: [<pool-name>/]<journal-name>)
+  
+  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 <pool>] [--format <format>] [--pretty-format] 
                   <pool-name> 
index ff1f6b82f718b7c20d244720a88ce436a9eac4fe..18572aa609ce1d0da116d79fce141f09f07f8f69 100644 (file)
@@ -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));
 }
 
index 82d106e90120e0c91989c65c8ee5913115841073..dd76ce72185640c6e669e3b76ed8c0aef9af39de 100755 (executable)
@@ -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() {
index 904261bf5b450c2cbfe549555cd6bf0d5d23d7d3..72d105835f35d67eb471cacb02da17c8f46b6bdd 100644 (file)
@@ -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
index 1618a6e51ba3820e6d7ee72f44195f2328c7c48a..0c3dc5597f87e8cfbfb7ad0c00be3daaa2b70061 100644 (file)
@@ -124,6 +124,29 @@ void add_snap_option(po::options_description *opt,
     (name.c_str(), po::value<std::string>(), 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<std::string>(), 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: [<pool-name>/]<journal-name>)").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;
index d8ecadd521c2293c2bea2a2a3d0a2b0d2cc4b1a9..e9240f27cb9453b253bce029a72ce09e45c6d901 100644 (file)
@@ -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<std::string> 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();
 
index 4a56785e865793341667837f10cdc7fb4fb5eed4..cb71ebdf1e24c9a1445edeac593761992bc05df5 100644 (file)
@@ -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<std::string>();
+  }
+  if (vm.count(journal_key) && journal_name != nullptr) {
+    *journal_name = vm[journal_key].as<std::string>();
+  }
+
+  std::string image_name;
+  if (vm.count(image_key)) {
+    image_name = vm[image_key].as<std::string>();
+  }
+
+  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
index b30236db0be3c51c925c384e59638611287da759..1e4ef6c001e38486de657ce5106ddd120a93b5b8 100644 (file)
@@ -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
 
index 76e394049f415e2ff5c09b398a0bd9742b03963d..f3d81ac0ad3e300b33072e98d57db0d1ba935bff 100644 (file)
@@ -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 (file)
index 0000000..e00665b
--- /dev/null
@@ -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 <fstream>
+#include <sstream>
+#include <boost/program_options.hpp>
+
+#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<int>(order) << " ("
+             << prettybyte_t(1ull << order) << " objects)"<< std::endl;
+    std::cout << "\tsplay_width: " << static_cast<int>(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<cls::journal::Client> registered_clients;
+  std::string oid = ::journal::Journaler::header_oid(journal_id);
+
+  cls::journal::client::get_mutable_metadata(io_ctx, oid, &minimum_set,
+                                            &active_set, &registered_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<cls::journal::Client>::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<cls::journal::Client>::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<bool>());
+  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<bool>(),
+                       vm[at::VERBOSE].as<bool>());
+  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<bool>(),
+                       vm[at::VERBOSE].as<bool>());
+  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