]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd: implement 'export-diff' and 'import-diff' commands
authorSage Weil <sage@inktank.com>
Tue, 26 Mar 2013 20:32:28 +0000 (13:32 -0700)
committerJosh Durgin <josh.durgin@inktank.com>
Mon, 1 Apr 2013 06:32:40 +0000 (23:32 -0700)
Export a diff of an image from a previous snapshot to a file (or stdout).

Import a diff and apply it to an image, and then create the ending
snapshot.

Signed-off-by: Sage Weil <sage@inktank.com>
doc/dev/rbd-diff.rst [new file with mode: 0644]
qa/workunits/rbd/diff.sh [new file with mode: 0755]
src/rbd.cc
src/test/cli/rbd/help.t

diff --git a/doc/dev/rbd-diff.rst b/doc/dev/rbd-diff.rst
new file mode 100644 (file)
index 0000000..c9729cb
--- /dev/null
@@ -0,0 +1,57 @@
+RBD Incremental Backup
+======================
+
+This is a simple streaming file format for representing a diff between
+two snapshots (or a snapshot and the head) of an RBD image.
+
+Header
+------
+
+le32: string length, always 13
+"rbd diff v1"
+
+Record
+------
+
+Every record has a one byte "tag" that identifies the record type, followed by some other
+data.
+
+From snap
+---------
+
+u8: 'f'
+le32: snap name length
+snap name
+
+To snap
+-------
+
+u8: 't'
+le32: snap name length
+snap name
+
+Size
+----
+
+u8: 's'
+u64: (ending) image size
+
+Updated data
+------------
+
+u8: 'w'
+le64: offset
+le64: length
+length bytes of actual data
+
+Zero data
+---------
+
+u8: 'z'
+le64: offset
+le64: length
+
+
+
+
+
diff --git a/qa/workunits/rbd/diff.sh b/qa/workunits/rbd/diff.sh
new file mode 100755 (executable)
index 0000000..6f9cd27
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/bash -ex
+
+function cleanup() {
+    rbd snap purge foo || :
+    rbd rm foo || :
+    rbd snap purge foo.copy || :
+    rbd rm foo.copy || :
+    rm -f foo.diff foo.out
+}
+
+cleanup
+
+rbd create foo --size 1000
+rbd bench-write foo --io-size 4096 --io-threads 5 --io-total 4096000 --io-pattern rand
+
+#rbd cp foo foo.copy
+rbd create foo.copy --size 1000
+rbd export-diff foo - | rbd import-diff - foo.copy
+
+rbd snap create foo --snap=two
+rbd bench-write foo --io-size 4096 --io-threads 5 --io-total 4096000 --io-pattern rand
+rbd snap create foo --snap=three
+rbd snap create foo.copy --snap=two
+
+rbd export-diff foo@three --from-snap two foo.diff
+rbd import-diff foo.diff foo.copy
+rbd snap ls foo.copy | grep three
+
+rbd export foo foo.out
+orig=`md5sum foo.out | awk '{print $1}'`
+rm foo.out
+rbd export foo.copy foo.out
+copy=`md5sum foo.out | awk '{print $1}'`
+
+if [ "$orig" != "$copy" ]; then
+    echo does not match
+    exit 1
+fi
+
+cleanup
+
+echo OK
+
index 29f438bd44338471cc2efe24e8af7f6c8fcb640d..2ddbacb60398bab3f24b7a1fdc420a079680819d 100644 (file)
@@ -65,6 +65,8 @@
 #define MAX_SECRET_LEN 1000
 #define MAX_POOL_NAME_SIZE 128
 
+#define RBD_DIFF_BANNER "rbd diff v1\n"
+
 static string dir_oid = RBD_DIRECTORY;
 static string dir_info_oid = RBD_INFO;
 
@@ -94,6 +96,11 @@ void usage()
 "                                              (dest defaults\n"
 "                                               as the filename part of file)\n"
 "                                              \"-\" for stdin\n"
+"  export-diff <image-name> [--from-snap <snap-name>] <path>\n"
+"                                              export an incremental diff to\n"
+"                                              path, or \"-\" for stdout\n"
+"  import-diff <path> <image-name>             import an incremental diff from\n"
+"                                              path or \"-\" for stdin\n"
 "  (cp | copy) <src> <dest>                    copy src image to dest\n"
 "  (mv | rename) <src> <dest>                  rename src image to dest\n"
 "  snap ls <image-name>                        dump list of image snapshots\n"
@@ -922,11 +929,16 @@ static int do_bench_write(librbd::Image& image, uint64_t io_size,
 }
 
 struct ExportContext {
+  librbd::Image *image;
   int fd;
   uint64_t totalsize;
   MyProgressContext pc;
 
-  ExportContext(int f, uint64_t t) : fd(f), totalsize(t), pc("Exporting image")
+  ExportContext(librbd::Image *i, int f, uint64_t t) :
+    image(i),
+    fd(f),
+    totalsize(t),
+    pc("Exporting image")
   {}
 };
 
@@ -1000,7 +1012,7 @@ static int do_export(librbd::Image& image, const char *path)
   if (fd < 0)
     return -errno;
 
-  ExportContext ec(fd, info.size);
+  ExportContext ec(&image, fd, info.size);
   r = image.read_iterate(0, info.size, export_read_cb, (void *)&ec);
   if (r < 0)
     goto out;
@@ -1019,6 +1031,108 @@ static int do_export(librbd::Image& image, const char *path)
   return r;
 }
 
+static int export_diff_cb(uint64_t ofs, size_t _len, bool zero, void *arg)
+{
+  ExportContext *ec = static_cast<ExportContext *>(arg);
+  int r;
+
+  // extent
+  bufferlist bl;
+  __u8 tag = zero ? 'z' : 'w';
+  ::encode(tag, bl);
+  ::encode(ofs, bl);
+  uint64_t len = _len;
+  ::encode(len, bl);
+  r = bl.write_fd(ec->fd);
+  if (r < 0)
+    return r;
+
+  if (!zero) {
+    // read block
+    bl.clear();
+    r = ec->image->read(ofs, len, bl);
+    if (r < 0)
+      return r;
+    r = bl.write_fd(ec->fd);
+    if (r < 0)
+      return r;
+  }
+
+  return 0;
+}
+
+static int do_export_diff(librbd::Image& image, const char *fromsnapname,
+                         const char *endsnapname,
+                         const char *path)
+{
+  int64_t r;
+  librbd::image_info_t info;
+  int fd;
+
+  r = image.stat(info, sizeof(info));
+  if (r < 0)
+    return r;
+
+  if (strcmp(path, "-") == 0)
+    fd = 1;
+  else
+    fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644);
+  if (fd < 0)
+    return -errno;
+
+  {
+    // header
+    bufferlist bl;
+    bl.append(RBD_DIFF_BANNER, strlen(RBD_DIFF_BANNER));
+
+    __u8 tag;
+    if (fromsnapname) {
+      tag = 'f';
+      ::encode(tag, bl);
+      string from(fromsnapname);
+      ::encode(from, bl);
+    }
+
+    if (endsnapname) {
+      tag = 't';
+      ::encode(tag, bl);
+      string to(endsnapname);
+      ::encode(to, bl);
+    }
+
+    tag = 's';
+    ::encode(tag, bl);
+    uint64_t endsize = info.size;
+    ::encode(endsize, bl);
+
+    r = bl.write_fd(fd);
+    if (r < 0)
+      return r;
+  }
+
+  ExportContext ec(&image, fd, info.size);
+  r = image.diff_iterate(fromsnapname, 0, info.size, export_diff_cb, (void *)&ec);
+  if (r < 0)
+    goto out;
+
+  {
+    __u8 tag = 'e';
+    bufferlist bl;
+    ::encode(tag, bl);
+    bl.write_fd(fd);
+    if (r < 0)
+      return r;
+  }
+
+ out:
+  close(fd);
+  if (r < 0)
+    ec.pc.fail();
+  else
+    ec.pc.finish();
+  return r;
+}
+
 static const char *imgname_from_path(const char *path)
 {
   const char *imgname;
@@ -1205,6 +1319,168 @@ static int do_import(librbd::RBD &rbd, librados::IoCtx& io_ctx,
   return r;
 }
 
+static int read_string(int fd, unsigned max, string *out)
+{
+  char buf[4];
+
+  int r = safe_read(fd, buf, 4);
+  if (r < 0)
+    return r;
+
+  bufferlist bl;
+  bl.append(buf, 4);
+  bufferlist::iterator p = bl.begin();
+  uint32_t len;
+  ::decode(len, p);
+  if (len > max)
+    return -EINVAL;
+
+  char sbuf[len];
+  r = safe_read(fd, sbuf, len);
+  if (r < 0)
+    return r;
+  out->assign(sbuf, len);
+  return len;
+}
+
+static int do_import_diff(librbd::Image &image, const char *path)
+{
+  int fd, r;
+  struct stat stat_buf;
+  MyProgressContext pc("Importing image diff");
+  uint64_t size = 0;
+  uint64_t off = 0;
+  string from, to;
+
+  bool from_stdin = !strcmp(path, "-");
+  if (from_stdin) {
+    fd = 0;
+  } else {
+    fd = open(path, O_RDONLY);
+    if (fd < 0) {
+      r = -errno;
+      cerr << "rbd: error opening " << path << std::endl;
+      return r;
+    }
+    r = ::fstat(fd, &stat_buf);
+    if (r < 0)
+      goto done;
+    size = (uint64_t)stat_buf.st_size;
+  }
+
+  char buf[15];
+  r = safe_read(fd, buf, strlen(RBD_DIFF_BANNER));
+  if (r < 0)
+    goto done;
+  buf[strlen(RBD_DIFF_BANNER)] = '\0';
+  if (strcmp(buf, RBD_DIFF_BANNER)) {
+    cerr << "invalid banner '" << buf << "', expected '" << RBD_DIFF_BANNER << "'" << std::endl;
+    r = -EINVAL;
+    goto done;
+  }
+
+  while (true) {
+    __u8 tag;
+    r = safe_read(fd, &tag, 1);
+    if (r <= 0)
+      goto done;
+
+    if (tag == 'e') {
+      cout << " end diff" << std::endl;
+      break;
+    }
+    else if (tag == 'f') {
+      r = read_string(fd, 4096, &from);   // 4k limit to make sure we don't get a garbage string
+      if (r < 0)
+       goto done;
+      cout << " from snap " << from << std::endl;
+    }
+    else if (tag == 't') {
+      r = read_string(fd, 4096, &to);   // 4k limit to make sure we don't get a garbage string
+      if (r < 0)
+       goto done;
+      cout << "   to snap " << to << std::endl;
+    }
+    else if (tag == 's') {
+      uint64_t end_size;
+      char buf[8];
+      r = safe_read(fd, buf, 8);
+      if (r < 0)
+       goto done;
+      bufferlist bl;
+      bl.append(buf, 8);
+      bufferlist::iterator p = bl.begin();
+      ::decode(end_size, p);
+      uint64_t cur_size;
+      image.size(&cur_size);
+      if (cur_size != end_size) {
+       cout << "resize " << end_size << " -> " << size << std::endl;
+       image.resize(end_size);
+      } else {
+       cout << "size " << end_size << " (no change)" << std::endl;
+      }
+      if (from_stdin)
+       size = end_size;
+    }
+    else if (tag == 'w' || tag == 'z') {
+      uint64_t len;
+      char buf[16];
+      r = safe_read(fd, buf, 16);
+      if (r < 0)
+       goto done;
+      bufferlist bl;
+      bl.append(buf, 16);
+      bufferlist::iterator p = bl.begin();
+      ::decode(off, p);
+      ::decode(len, p);
+
+      if (tag == 'w') {
+       bufferptr bp = buffer::create(len);
+       r = safe_read(fd, bp.c_str(), len);
+       if (r < 0)
+         goto done;
+       bufferlist data;
+       data.append(bp);
+       cout << " write " << off << "~" << len << std::endl;
+       image.write(off, len, data);
+      } else if (tag == 'z') {
+       cout << " zero " << off << "~" << len << std::endl;
+       image.discard(off, len);
+      }
+    }
+    else {
+      cerr << "unrecognized tag byte " << (int)tag << " in stream; aborting" << std::endl;
+      r = -EINVAL;
+      goto done;
+    }
+    if (!from_stdin) {
+      // progress through input
+      uint64_t off = lseek64(fd, 0, SEEK_CUR);
+      pc.update_progress(off, size);
+    } else if (size) {
+      // progress through image offsets.  this may jitter if blocks
+      // aren't in order, but it is better than nothing.
+      pc.update_progress(off, size);
+    }
+  }
+
+  // take final snap
+  if (to.length()) {
+    cout << " create end snap " << to << std::endl;
+    r = image.snap_create(to.c_str());
+  }
+
+ done:
+  if (!from_stdin) {
+    if (r < 0)
+      pc.fail();
+    else
+      pc.finish();
+    close(fd);
+  }
+  return r;
+}
+
 static int do_copy(librbd::Image &src, librados::IoCtx& dest_pp,
                   const char *destname)
 {
@@ -1614,7 +1890,9 @@ enum {
   OPT_RESIZE,
   OPT_RM,
   OPT_EXPORT,
+  OPT_EXPORT_DIFF,
   OPT_IMPORT,
+  OPT_IMPORT_DIFF,
   OPT_COPY,
   OPT_RENAME,
   OPT_SNAP_CREATE,
@@ -1656,8 +1934,12 @@ static int get_cmd(const char *cmd, bool snapcmd, bool lockcmd)
       return OPT_RM;
     if (strcmp(cmd, "export") == 0)
       return OPT_EXPORT;
+    if (strcmp(cmd, "export-diff") == 0)
+      return OPT_EXPORT_DIFF;
     if (strcmp(cmd, "import") == 0)
       return OPT_IMPORT;
+    if (strcmp(cmd, "import-diff") == 0)
+      return OPT_IMPORT_DIFF;
     if (strcmp(cmd, "copy") == 0 ||
         strcmp(cmd, "cp") == 0)
       return OPT_COPY;
@@ -1753,7 +2035,8 @@ int main(int argc, const char **argv)
   const char *imgname = NULL, *snapname = NULL, *destname = NULL,
     *dest_poolname = NULL, *dest_snapname = NULL, *path = NULL,
     *devpath = NULL, *lock_cookie = NULL, *lock_client = NULL,
-    *lock_tag = NULL, *output_format = "plain";
+    *lock_tag = NULL, *output_format = "plain",
+    *fromsnapname = NULL;
   bool lflag = false;
   int pretty_format = 0;
   long long stripe_unit = 0, stripe_count = 0;
@@ -1789,6 +2072,8 @@ int main(int argc, const char **argv)
       dest_poolname = strdup(val.c_str());
     } else if (ceph_argparse_witharg(args, i, &val, "--snap", (char*)NULL)) {
       snapname = strdup(val.c_str());
+    } else if (ceph_argparse_witharg(args, i, &val, "--from-snap", (char*)NULL)) {
+      fromsnapname = strdup(val.c_str());
     } else if (ceph_argparse_witharg(args, i, &val, "-i", "--image", (char*)NULL)) {
       imgname = strdup(val.c_str());
     } else if (ceph_argparse_withlonglong(args, i, &sizell, &err, "-s", "--size", (char*)NULL)) {
@@ -1912,10 +2197,12 @@ if (!set_conf_param(v, p1, p2, p3)) { \
        SET_CONF_PARAM(v, &devpath, NULL, NULL);
        break;
       case OPT_EXPORT:
+      case OPT_EXPORT_DIFF:
        SET_CONF_PARAM(v, &imgname, &path, NULL);
        break;
       case OPT_IMPORT:
-       SET_CONF_PARAM(v, &path, &destname, NULL);
+      case OPT_IMPORT_DIFF:
+       SET_CONF_PARAM(v, &path, &imgname, NULL);
        break;
       case OPT_COPY:
       case OPT_RENAME:
@@ -1975,7 +2262,7 @@ if (!set_conf_param(v, p1, p2, p3)) { \
     return EXIT_FAILURE;
   }
 
-  if (opt_cmd == OPT_IMPORT && !path) {
+  if ((opt_cmd == OPT_IMPORT || opt_cmd == OPT_IMPORT_DIFF) && !path) {
     cerr << "rbd: path was not specified" << std::endl;
     return EXIT_FAILURE;
   }
@@ -1999,7 +2286,10 @@ if (!set_conf_param(v, p1, p2, p3)) { \
     return EXIT_FAILURE;
   }
 
-  if (opt_cmd != OPT_LIST && opt_cmd != OPT_IMPORT && opt_cmd != OPT_UNMAP &&
+  if (opt_cmd != OPT_LIST &&
+      opt_cmd != OPT_IMPORT &&
+      opt_cmd != OPT_IMPORT_DIFF &&
+      opt_cmd != OPT_UNMAP &&
       opt_cmd != OPT_SHOWMAPPED && !imgname) {
     cerr << "rbd: image name was not specified" << std::endl;
     return EXIT_FAILURE;
@@ -2016,7 +2306,7 @@ if (!set_conf_param(v, p1, p2, p3)) { \
                      (char **)&imgname, (char **)&snapname);
   if (snapname && opt_cmd != OPT_SNAP_CREATE && opt_cmd != OPT_SNAP_ROLLBACK &&
       opt_cmd != OPT_SNAP_REMOVE && opt_cmd != OPT_INFO &&
-      opt_cmd != OPT_EXPORT && opt_cmd != OPT_COPY &&
+      opt_cmd != OPT_EXPORT && opt_cmd != OPT_EXPORT_DIFF && opt_cmd != OPT_COPY &&
       opt_cmd != OPT_MAP && opt_cmd != OPT_CLONE &&
       opt_cmd != OPT_SNAP_PROTECT && opt_cmd != OPT_SNAP_UNPROTECT &&
       opt_cmd != OPT_CHILDREN) {
@@ -2111,11 +2401,12 @@ if (!set_conf_param(v, p1, p2, p3)) { \
        opt_cmd == OPT_FLATTEN || opt_cmd == OPT_LOCK_ADD ||
        opt_cmd == OPT_LOCK_REMOVE || opt_cmd == OPT_BENCH_WRITE ||
        opt_cmd == OPT_INFO || opt_cmd == OPT_SNAP_LIST ||
-       opt_cmd == OPT_EXPORT || opt_cmd == OPT_COPY ||
+       opt_cmd == OPT_IMPORT_DIFF ||
+       opt_cmd == OPT_EXPORT || opt_cmd == OPT_EXPORT_DIFF || opt_cmd == OPT_COPY ||
        opt_cmd == OPT_CHILDREN || opt_cmd == OPT_LOCK_LIST)) {
 
     if (opt_cmd == OPT_INFO || opt_cmd == OPT_SNAP_LIST ||
-       opt_cmd == OPT_EXPORT || opt_cmd == OPT_COPY ||
+       opt_cmd == OPT_EXPORT || opt_cmd == OPT_EXPORT || opt_cmd == OPT_COPY ||
        opt_cmd == OPT_CHILDREN || opt_cmd == OPT_LOCK_LIST) {
       r = rbd.open_read_only(io_ctx, image, imgname, NULL);
     } else {
@@ -2129,7 +2420,10 @@ if (!set_conf_param(v, p1, p2, p3)) { \
   }
 
   if (snapname && talk_to_cluster &&
-      (opt_cmd == OPT_INFO || opt_cmd == OPT_EXPORT || opt_cmd == OPT_COPY ||
+      (opt_cmd == OPT_INFO ||
+       opt_cmd == OPT_EXPORT ||
+       opt_cmd == OPT_EXPORT_DIFF ||
+       opt_cmd == OPT_COPY ||
        opt_cmd == OPT_CHILDREN)) {
     r = image.snap_set(snapname);
     if (r < 0) {
@@ -2372,6 +2666,18 @@ if (!set_conf_param(v, p1, p2, p3)) { \
     }
     break;
 
+  case OPT_EXPORT_DIFF:
+    if (!path) {
+      cerr << "rbd: export-diff requires pathname" << std::endl;
+      return EXIT_FAILURE;
+    }
+    r = do_export_diff(image, fromsnapname, snapname, path);
+    if (r < 0) {
+      cerr << "rbd: export-diff error: " << cpp_strerror(-r) << std::endl;
+      return EXIT_FAILURE;
+    }
+    break;
+
   case OPT_IMPORT:
     if (!path) {
       cerr << "rbd: import requires pathname" << std::endl;
@@ -2385,6 +2691,15 @@ if (!set_conf_param(v, p1, p2, p3)) { \
     }
     break;
 
+  case OPT_IMPORT_DIFF:
+    assert(path);
+    r = do_import_diff(image, path);
+    if (r < 0) {
+      cerr << "rbd: import-diff failed: " << cpp_strerror(-r) << std::endl;
+      return EXIT_FAILURE;
+    }
+    break;
+
   case OPT_COPY:
     r = do_copy(image, dest_io_ctx, destname);
     if (r < 0) {
index e32325d39d3bbe6a9e11094fb18feccb3b1fb6b0..25f79023b3b0ac9979ad216afaabf23111060cb9 100644 (file)
                                                 (dest defaults
                                                  as the filename part of file)
                                                 "-" for stdin
+    export-diff <image-name> [--from-snap <snap-name>] <path>
+                                                export an incremental diff to
+                                                path, or "-" for stdout
+    import-diff <path> <image-name>             import an incremental diff from
+                                                path or "-" for stdin
     (cp | copy) <src> <dest>                    copy src image to dest
     (mv | rename) <src> <dest>                  rename src image to dest
     snap ls <image-name>                        dump list of image snapshots