]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd/migration: add external clusters support
authorIlya Dryomov <idryomov@gmail.com>
Fri, 16 Aug 2024 17:09:39 +0000 (19:09 +0200)
committerIlya Dryomov <idryomov@gmail.com>
Thu, 22 Aug 2024 10:30:32 +0000 (12:30 +0200)
This commit extends NativeFormat (aka migration where the migration
source is an RBD image) to support external Ceph clusters, limited to
import-only mode.

Co-authored-by: Or Ozeri <oro@il.ibm.com>
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
31 files changed:
doc/rbd/rbd-live-migration.rst
qa/suites/rbd/migration-external/% [new file with mode: 0644]
qa/suites/rbd/migration-external/.qa [new symlink]
qa/suites/rbd/migration-external/1-base/.qa [new symlink]
qa/suites/rbd/migration-external/1-base/install.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/2-clusters/.qa [new symlink]
qa/suites/rbd/migration-external/2-clusters/2-node.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/3-objectstore [new symlink]
qa/suites/rbd/migration-external/4-supported-random-distro$ [new symlink]
qa/suites/rbd/migration-external/5-data-pool/.qa [new symlink]
qa/suites/rbd/migration-external/5-data-pool/ec.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/5-data-pool/none.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/5-data-pool/replicated.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/6-prepare/.qa [new symlink]
qa/suites/rbd/migration-external/6-prepare/native-clone.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/6-prepare/native-standalone.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/7-io-workloads/.qa [new symlink]
qa/suites/rbd/migration-external/7-io-workloads/qemu_xfstests.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/8-migrate-workloads/.qa [new symlink]
qa/suites/rbd/migration-external/8-migrate-workloads/execute.yaml [new file with mode: 0644]
qa/suites/rbd/migration-external/conf [new symlink]
src/librbd/ImageCtx.cc
src/librbd/ImageCtx.h
src/librbd/api/Migration.cc
src/librbd/image/CloseRequest.cc
src/librbd/image/RefreshParentRequest.cc
src/librbd/image/RefreshParentRequest.h
src/librbd/migration/NativeFormat.cc
src/librbd/migration/NativeFormat.h
src/librbd/migration/OpenSourceImageRequest.cc
src/librbd/migration/OpenSourceImageRequest.h

index c3e09193d3114feede989591ef59c00fee0fa9ec..1d3e16b996ed627965bf68183e99c2519664cf0a 100644 (file)
@@ -4,11 +4,11 @@
 
 .. index:: Ceph Block Device; live-migration
 
-RBD images can be live-migrated between different pools within the same cluster;
-between different image formats and layouts; or from external data sources.
-When started, the source will be deep-copied to the destination image, pulling
-all snapshot history while preserving the sparse allocation of data where
-possible.
+RBD images can be live-migrated between different pools, image formats and/or
+layouts within the same Ceph cluster; from an image in another Ceph cluster; or
+from external data sources. When started, the source will be deep-copied to
+the destination image, pulling all snapshot history while preserving the sparse
+allocation of data where possible.
 
 By default, when live-migrating RBD images within the same Ceph cluster, the
 source image will be marked read-only and all clients will instead redirect
@@ -18,8 +18,9 @@ image during the migration to remove the dependency on the source image's
 parent.
 
 The live-migration process can also be used in an import-only mode where the
-source image remains unmodified and the target image can be linked to an
-external data source such as a backing file, HTTP(s) file, or S3 object.
+source image remains unmodified and the target image can be linked to an image
+in another Ceph cluster or to an external data source such as a backing file,
+HTTP(s) file, or S3 object.
 
 The live-migration copy process can safely run in the background while the new
 target image is in use. There is currently a requirement to temporarily stop
@@ -156,6 +157,10 @@ as follows::
 
         {
             "type": "native",
+            ["cluster_name": "<cluster-name>",] (specify if image in another cluster,
+                                                 requires ``<cluster-name>.conf`` file)
+            ["client_name": "<client-name>",] (for connecting to another cluster,
+                                               default is ``client.admin``)
             "pool_name": "<pool-name>",
             ["pool_id": <pool-id>,] (optional alternative to "pool_name")
             ["pool_namespace": "<pool-namespace",] (optional)
diff --git a/qa/suites/rbd/migration-external/% b/qa/suites/rbd/migration-external/%
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/qa/suites/rbd/migration-external/.qa b/qa/suites/rbd/migration-external/.qa
new file mode 120000 (symlink)
index 0000000..a602a03
--- /dev/null
@@ -0,0 +1 @@
+../.qa/
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/1-base/.qa b/qa/suites/rbd/migration-external/1-base/.qa
new file mode 120000 (symlink)
index 0000000..a602a03
--- /dev/null
@@ -0,0 +1 @@
+../.qa/
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/1-base/install.yaml b/qa/suites/rbd/migration-external/1-base/install.yaml
new file mode 100644 (file)
index 0000000..0728d3f
--- /dev/null
@@ -0,0 +1,8 @@
+meta:
+- desc: run two ceph clusters
+tasks:
+- install:
+- ceph:
+    cluster: cluster1
+- ceph:
+    cluster: cluster2
diff --git a/qa/suites/rbd/migration-external/2-clusters/.qa b/qa/suites/rbd/migration-external/2-clusters/.qa
new file mode 120000 (symlink)
index 0000000..a602a03
--- /dev/null
@@ -0,0 +1 @@
+../.qa/
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/2-clusters/2-node.yaml b/qa/suites/rbd/migration-external/2-clusters/2-node.yaml
new file mode 100644 (file)
index 0000000..848e630
--- /dev/null
@@ -0,0 +1,15 @@
+meta:
+- desc: 2 ceph clusters with 1 mon and 3 osds each
+roles:
+- - cluster1.mon.a
+  - cluster1.mgr.x
+  - cluster1.osd.0
+  - cluster1.osd.1
+  - cluster1.osd.2
+  - cluster1.client.0
+- - cluster2.mon.a
+  - cluster2.mgr.x
+  - cluster2.osd.0
+  - cluster2.osd.1
+  - cluster2.osd.2
+  - cluster2.client.0
diff --git a/qa/suites/rbd/migration-external/3-objectstore b/qa/suites/rbd/migration-external/3-objectstore
new file mode 120000 (symlink)
index 0000000..c40bd32
--- /dev/null
@@ -0,0 +1 @@
+.qa/objectstore
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/4-supported-random-distro$ b/qa/suites/rbd/migration-external/4-supported-random-distro$
new file mode 120000 (symlink)
index 0000000..0862b44
--- /dev/null
@@ -0,0 +1 @@
+.qa/distros/supported-random-distro$
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/5-data-pool/.qa b/qa/suites/rbd/migration-external/5-data-pool/.qa
new file mode 120000 (symlink)
index 0000000..a602a03
--- /dev/null
@@ -0,0 +1 @@
+../.qa/
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/5-data-pool/ec.yaml b/qa/suites/rbd/migration-external/5-data-pool/ec.yaml
new file mode 100644 (file)
index 0000000..f8a3997
--- /dev/null
@@ -0,0 +1,29 @@
+tasks:
+- exec:
+    cluster1.client.0:
+      - sudo ceph --cluster cluster1 osd erasure-code-profile set teuthologyprofile crush-failure-domain=osd m=1 k=2
+      - sudo ceph --cluster cluster1 osd pool create datapool 4 4 erasure teuthologyprofile
+      - sudo ceph --cluster cluster1 osd pool set datapool allow_ec_overwrites true
+      - rbd --cluster cluster1 pool init datapool
+    cluster2.client.0:
+      - sudo ceph --cluster cluster2 osd erasure-code-profile set teuthologyprofile crush-failure-domain=osd m=1 k=2
+      - sudo ceph --cluster cluster2 osd pool create datapool 4 4 erasure teuthologyprofile
+      - sudo ceph --cluster cluster2 osd pool set datapool allow_ec_overwrites true
+      - rbd --cluster cluster2 pool init datapool
+
+overrides:
+  thrashosds:
+    bdev_inject_crash: 2
+    bdev_inject_crash_probability: .5
+  ceph:
+    fs: xfs
+    conf:
+      client:
+        rbd default data pool: datapool
+      osd: # force bluestore since it's required for ec overwrites
+        osd objectstore: bluestore
+        bluestore block size: 96636764160
+        enable experimental unrecoverable data corrupting features: "*"
+        osd debug randomize hobject sort order: false
+# this doesn't work with failures bc the log writes are not atomic across the two backends
+#        bluestore bluefs env mirror: true
diff --git a/qa/suites/rbd/migration-external/5-data-pool/none.yaml b/qa/suites/rbd/migration-external/5-data-pool/none.yaml
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/qa/suites/rbd/migration-external/5-data-pool/replicated.yaml b/qa/suites/rbd/migration-external/5-data-pool/replicated.yaml
new file mode 100644 (file)
index 0000000..3ecbaf8
--- /dev/null
@@ -0,0 +1,14 @@
+tasks:
+- exec:
+    cluster1.client.0:
+      - sudo ceph --cluster cluster1 osd pool create datapool 4
+      - rbd --cluster cluster1 pool init datapool
+    cluster2.client.0:
+      - sudo ceph --cluster cluster2 osd pool create datapool 4
+      - rbd --cluster cluster2 pool init datapool
+
+overrides:
+  ceph:
+    conf:
+      client:
+        rbd default data pool: datapool
diff --git a/qa/suites/rbd/migration-external/6-prepare/.qa b/qa/suites/rbd/migration-external/6-prepare/.qa
new file mode 120000 (symlink)
index 0000000..a602a03
--- /dev/null
@@ -0,0 +1 @@
+../.qa/
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/6-prepare/native-clone.yaml b/qa/suites/rbd/migration-external/6-prepare/native-clone.yaml
new file mode 100644 (file)
index 0000000..2ca92dc
--- /dev/null
@@ -0,0 +1,29 @@
+tasks:
+  - exec:
+      cluster2.client.0:
+        - echo '{"type":"qcow","stream":{"type":"http","url":"http://download.ceph.com/qa/ubuntu-12.04.qcow2"}}' | rbd --cluster cluster2 migration prepare --import-only --source-spec-path - client.0.0-src
+        - rbd --cluster cluster2 migration execute client.0.0-src
+        - rbd --cluster cluster2 migration commit client.0.0-src
+        - rbd --cluster cluster2 snap create client.0.0-src@snap
+        - rbd --cluster cluster2 snap protect client.0.0-src@snap
+        - rbd --cluster cluster2 clone client.0.0-src@snap client.0.0
+        - rbd --cluster cluster2 snap create client.0.0@snap
+        - rbd --cluster cluster2 create --size 1G client.0.1-src
+        - rbd --cluster cluster2 bench --io-type write --io-pattern rand --io-size 16K --io-threads 1 --io-total 1M client.0.1-src
+        - rbd --cluster cluster2 snap create client.0.1-src@snap
+        - rbd --cluster cluster2 snap protect client.0.1-src@snap
+        - rbd --cluster cluster2 clone client.0.1-src@snap client.0.1
+        - rbd --cluster cluster2 bench --io-type write --io-pattern rand --io-size 16K --io-threads 1 --io-total 1M client.0.1
+        - rbd --cluster cluster2 snap create client.0.1@snap
+        - rbd --cluster cluster2 create --size 1G client.0.2-src
+        - rbd --cluster cluster2 bench --io-type write --io-pattern rand --io-size 16K --io-threads 1 --io-total 1M client.0.2-src
+        - rbd --cluster cluster2 snap create client.0.2-src@snap
+        - rbd --cluster cluster2 snap protect client.0.2-src@snap
+        - rbd --cluster cluster2 clone client.0.2-src@snap client.0.2
+        - rbd --cluster cluster2 bench --io-type write --io-pattern rand --io-size 16K --io-threads 1 --io-total 2M client.0.2
+        - rbd --cluster cluster2 snap create client.0.2@snap
+  - exec:
+      cluster1.client.0:
+        - echo '{"type":"native","cluster_name":"cluster2","client_name":"client.admin","pool_name":"rbd","image_name":"client.0.0","snap_name":"snap"}' | rbd --cluster cluster1 migration prepare --import-only --source-spec-path - client.0.0
+        - echo '{"type":"native","cluster_name":"cluster2","client_name":"client.admin","pool_name":"rbd","image_name":"client.0.1","snap_name":"snap"}' | rbd --cluster cluster1 migration prepare --import-only --source-spec-path - client.0.1
+        - echo '{"type":"native","cluster_name":"cluster2","client_name":"client.admin","pool_name":"rbd","image_name":"client.0.2","snap_name":"snap"}' | rbd --cluster cluster1 migration prepare --import-only --source-spec-path - client.0.2
diff --git a/qa/suites/rbd/migration-external/6-prepare/native-standalone.yaml b/qa/suites/rbd/migration-external/6-prepare/native-standalone.yaml
new file mode 100644 (file)
index 0000000..5fdf4d3
--- /dev/null
@@ -0,0 +1,18 @@
+tasks:
+  - exec:
+      cluster2.client.0:
+        - echo '{"type":"qcow","stream":{"type":"http","url":"http://download.ceph.com/qa/ubuntu-12.04.qcow2"}}' | rbd --cluster cluster2 migration prepare --import-only --source-spec-path - client.0.0
+        - rbd --cluster cluster2 migration execute client.0.0
+        - rbd --cluster cluster2 migration commit client.0.0
+        - rbd --cluster cluster2 snap create client.0.0@snap
+        - rbd --cluster cluster2 create --size 1G client.0.1
+        - rbd --cluster cluster2 bench --io-type write --io-pattern rand --io-size 16K --io-threads 1 --io-total 2M client.0.1
+        - rbd --cluster cluster2 snap create client.0.1@snap
+        - rbd --cluster cluster2 create --size 1G client.0.2
+        - rbd --cluster cluster2 bench --io-type write --io-pattern rand --io-size 16K --io-threads 1 --io-total 2M client.0.2
+        - rbd --cluster cluster2 snap create client.0.2@snap
+  - exec:
+      cluster1.client.0:
+        - echo '{"type":"native","cluster_name":"cluster2","client_name":"client.admin","pool_name":"rbd","image_name":"client.0.0","snap_name":"snap"}' | rbd --cluster cluster1 migration prepare --import-only --source-spec-path - client.0.0
+        - echo '{"type":"native","cluster_name":"cluster2","client_name":"client.admin","pool_name":"rbd","image_name":"client.0.1","snap_name":"snap"}' | rbd --cluster cluster1 migration prepare --import-only --source-spec-path - client.0.1
+        - echo '{"type":"native","cluster_name":"cluster2","client_name":"client.admin","pool_name":"rbd","image_name":"client.0.2","snap_name":"snap"}' | rbd --cluster cluster1 migration prepare --import-only --source-spec-path - client.0.2
diff --git a/qa/suites/rbd/migration-external/7-io-workloads/.qa b/qa/suites/rbd/migration-external/7-io-workloads/.qa
new file mode 120000 (symlink)
index 0000000..a602a03
--- /dev/null
@@ -0,0 +1 @@
+../.qa/
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/7-io-workloads/qemu_xfstests.yaml b/qa/suites/rbd/migration-external/7-io-workloads/qemu_xfstests.yaml
new file mode 100644 (file)
index 0000000..c44011f
--- /dev/null
@@ -0,0 +1,14 @@
+io_workload:
+  sequential:
+    - qemu:
+        cluster1.client.0:
+          type: block
+          disks:
+            - action: none
+              image_name: client.0.0
+            - action: none
+              image_name: client.0.1
+            - action: none
+              image_name: client.0.2
+          test: qa/run_xfstests_qemu.sh
+exclude_arch: armv7l
diff --git a/qa/suites/rbd/migration-external/8-migrate-workloads/.qa b/qa/suites/rbd/migration-external/8-migrate-workloads/.qa
new file mode 120000 (symlink)
index 0000000..a602a03
--- /dev/null
@@ -0,0 +1 @@
+../.qa/
\ No newline at end of file
diff --git a/qa/suites/rbd/migration-external/8-migrate-workloads/execute.yaml b/qa/suites/rbd/migration-external/8-migrate-workloads/execute.yaml
new file mode 100644 (file)
index 0000000..d0afe71
--- /dev/null
@@ -0,0 +1,14 @@
+tasks:
+  - parallel:
+      - io_workload
+      - migrate_workload
+migrate_workload:
+  sequential:
+    - exec:
+        cluster1.client.0:
+          - sleep $((RANDOM % 600))
+          - rbd --cluster cluster1 migration execute client.0.0
+          - sleep $((RANDOM % 600))
+          - rbd --cluster cluster1 migration commit client.0.0
+          - sleep $((RANDOM % 600))
+          - rbd --cluster cluster1 migration execute client.0.1
diff --git a/qa/suites/rbd/migration-external/conf b/qa/suites/rbd/migration-external/conf
new file mode 120000 (symlink)
index 0000000..4bc0fe8
--- /dev/null
@@ -0,0 +1 @@
+.qa/rbd/conf
\ No newline at end of file
index 2e92f0cc4af6bb044c951caac7d4a385e883a2b9..347e8867fa2316f7cf817d090a29ad4115050e4e 100644 (file)
@@ -158,6 +158,7 @@ librados::IoCtx duplicate_io_ctx(librados::IoCtx& io_ctx) {
     ldout(cct, 10) << this << " " << __func__ << dendl;
 
     ceph_assert(parent == nullptr);
+    ceph_assert(parent_rados == nullptr);
     ceph_assert(config_watcher == nullptr);
     ceph_assert(image_watcher == NULL);
     ceph_assert(exclusive_lock == NULL);
index 4edabf7fd0b1157a5fc764a003245aef364f67b7..84c9284e31c5552f9f496f44b8926c21c6a87a48 100644 (file)
@@ -144,7 +144,7 @@ namespace librbd {
                        // lock_tag
                        // lockers
                        // object_map
-                       // parent_md and parent
+                       // parent_md, parent and parent_rados
                        // encryption_format
 
     ceph::shared_mutex timestamp_lock; // protects (create/access/modify)_timestamp
@@ -164,6 +164,8 @@ namespace librbd {
     std::string id; // only used for new-format images
     ParentImageInfo parent_md;
     ImageCtx *parent = nullptr;
+    librados::Rados *parent_rados = nullptr; // set iff image is being imported
+                                             // from another cluster
     ImageCtx *child = nullptr;
     MigrationInfo migration_info;
     cls::rbd::GroupSpec group_spec;
index 67b9028caeb22ccb752b842c2bbdb13240beb722..dea3f8384c6b11fdc1af524ed260e384fea5f7ac 100644 (file)
@@ -527,11 +527,13 @@ int Migration<I>::prepare_import(
                  << dest_io_ctx.get_pool_name() << "/"
                  << dest_image_name << ", opts=" << opts << dendl;
 
-  I* src_image_ctx = nullptr;
+  I* src_image_ctx;
+  librados::Rados* src_rados;
   C_SaferCond open_ctx;
   auto req = migration::OpenSourceImageRequest<I>::create(
     dest_io_ctx, nullptr, CEPH_NOSNAP,
-    {-1, "", "", "", source_spec, {}, 0, false}, &src_image_ctx, &open_ctx);
+    {-1, "", "", "", source_spec, {}, 0, false}, &src_image_ctx, &src_rados,
+    &open_ctx);
   req->send();
 
   int r = open_ctx.wait();
@@ -540,8 +542,9 @@ int Migration<I>::prepare_import(
     return r;
   }
 
-  BOOST_SCOPE_EXIT_TPL(src_image_ctx) {
+  BOOST_SCOPE_EXIT_TPL(src_image_ctx, src_rados) {
     src_image_ctx->state->close();
+    delete src_rados;
   } BOOST_SCOPE_EXIT_END;
 
   uint64_t image_format = 2;
index 7293687f5b81cfe00deff0d4608443fba4bfdb41..eac755e45794942f35a708bd12b08aab115c963e 100644 (file)
@@ -307,6 +307,11 @@ void CloseRequest<I>::handle_close_parent(int r) {
   ldout(cct, 10) << this << " " << __func__ << ": r=" << r << dendl;
 
   m_image_ctx->parent = nullptr;
+  if (m_image_ctx->parent_rados != nullptr) {
+    delete m_image_ctx->parent_rados;
+    m_image_ctx->parent_rados = nullptr;
+  }
+
   save_result(r);
   if (r < 0) {
     lderr(cct) << "error closing parent image: " << cpp_strerror(r) << dendl;
index bfaef2dcc2f426cf29be3e3817be75b4e9a432fc..75bf64ee9c052dc5af09314777ce64535ba5970a 100644 (file)
@@ -90,6 +90,7 @@ template <typename I>
 void RefreshParentRequest<I>::apply() {
   ceph_assert(ceph_mutex_is_wlocked(m_child_image_ctx.image_lock));
   std::swap(m_child_image_ctx.parent, m_parent_image_ctx);
+  std::swap(m_child_image_ctx.parent_rados, m_parent_rados);
 }
 
 template <typename I>
@@ -101,6 +102,7 @@ void RefreshParentRequest<I>::finalize(Context *on_finish) {
   if (m_parent_image_ctx != nullptr) {
     send_close_parent();
   } else {
+    ceph_assert(m_parent_rados == nullptr);
     send_complete(0);
   }
 }
@@ -119,7 +121,7 @@ void RefreshParentRequest<I>::send_open_parent() {
         &RefreshParentRequest<I>::handle_open_parent, false>(this));
     auto req = migration::OpenSourceImageRequest<I>::create(
       m_child_image_ctx.md_ctx, &m_child_image_ctx, m_parent_md.spec.snap_id,
-      m_migration_info, &m_parent_image_ctx, ctx);
+      m_migration_info, &m_parent_image_ctx, &m_parent_rados, ctx);
     req->send();
     return;
   }
@@ -188,6 +190,10 @@ Context *RefreshParentRequest<I>::handle_close_parent(int *result) {
   ldout(cct, 10) << this << " " << __func__ << " r=" << *result << dendl;
 
   m_parent_image_ctx = nullptr;
+  if (m_parent_rados != nullptr) {
+    delete m_parent_rados;
+    m_parent_rados = nullptr;
+  }
 
   if (*result < 0) {
     lderr(cct) << "failed to close parent image: " << cpp_strerror(*result)
index 100fa4fd1121ffd65b152055acc36e0a6f2fc191..271e856418dc38588414dc2cd3c334c72be4b408 100644 (file)
@@ -5,6 +5,7 @@
 #define CEPH_LIBRBD_IMAGE_REFRESH_PARENT_REQUEST_H
 
 #include "include/int_types.h"
+#include "include/rados/librados_fwd.hpp"
 #include "librbd/Types.h"
 
 class Context;
@@ -68,6 +69,7 @@ private:
   Context *m_on_finish;
 
   ImageCtxT *m_parent_image_ctx = nullptr;
+  librados::Rados *m_parent_rados = nullptr;
 
   int m_error_result;
 
index d682daab9d14a120bc83174a9722c86e44fabc70..f49a2594a82f04ef5ecb9d1a8658b052cd8ceeaa 100644 (file)
@@ -2,8 +2,11 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "librbd/migration/NativeFormat.h"
+#include "common/ceph_argparse.h"
+#include "common/common_init.h"
 #include "common/dout.h"
 #include "common/errno.h"
+#include "include/scope_guard.h"
 #include "librbd/ImageCtx.h"
 #include "librbd/ImageState.h"
 #include "json_spirit/json_spirit.h"
@@ -20,6 +23,8 @@ namespace migration {
 namespace {
 
 const std::string TYPE_KEY{"type"};
+const std::string CLUSTER_NAME_KEY{"cluster_name"};
+const std::string CLIENT_NAME_KEY{"client_name"};
 const std::string POOL_ID_KEY{"pool_id"};
 const std::string POOL_NAME_KEY{"pool_name"};
 const std::string POOL_NAMESPACE_KEY{"pool_namespace"};
@@ -58,8 +63,11 @@ template <typename I>
 int NativeFormat<I>::create_image_ctx(
     librados::IoCtx& dst_io_ctx,
     const json_spirit::mObject& source_spec_object,
-    bool import_only, uint64_t src_snap_id, I** src_image_ctx) {
+    bool import_only, uint64_t src_snap_id, I** src_image_ctx,
+    librados::Rados** src_rados) {
   auto cct = reinterpret_cast<CephContext*>(dst_io_ctx.cct());
+  std::string cluster_name;
+  std::string client_name;
   std::string pool_name;
   int64_t pool_id = -1;
   std::string pool_namespace;
@@ -69,6 +77,30 @@ int NativeFormat<I>::create_image_ctx(
   uint64_t snap_id = CEPH_NOSNAP;
   int r;
 
+  if (auto it = source_spec_object.find(CLUSTER_NAME_KEY);
+      it != source_spec_object.end()) {
+    if (it->second.type() == json_spirit::str_type) {
+      cluster_name = it->second.get_str();
+    } else {
+      lderr(cct) << "invalid cluster name" << dendl;
+      return -EINVAL;
+    }
+  }
+
+  if (auto it = source_spec_object.find(CLIENT_NAME_KEY);
+      it != source_spec_object.end()) {
+    if (cluster_name.empty()) {
+      lderr(cct) << "cannot specify client name without cluster name" << dendl;
+      return -EINVAL;
+    }
+    if (it->second.type() == json_spirit::str_type) {
+      client_name = it->second.get_str();
+    } else {
+      lderr(cct) << "invalid client name" << dendl;
+      return -EINVAL;
+    }
+  }
+
   if (auto it = source_spec_object.find(POOL_NAME_KEY);
       it != source_spec_object.end()) {
     if (it->second.type() == json_spirit::str_type) {
@@ -180,7 +212,53 @@ int NativeFormat<I>::create_image_ctx(
     snap_id = src_snap_id;
   }
 
-  // TODO add support for external clusters
+  std::unique_ptr<librados::Rados> rados_ptr;
+  if (!cluster_name.empty()) {
+    // manually bootstrap a CephContext, skipping reading environment
+    // variables for now -- since we don't have access to command line
+    // arguments here, the least confusing option is to limit initial
+    // remote cluster config to a file in the default location
+    // TODO: support specifying mon_host and key via source spec
+    // TODO: support merging in effective local cluster config to get
+    // overrides for log levels, etc
+    CephInitParameters iparams(CEPH_ENTITY_TYPE_CLIENT);
+    if (!client_name.empty() && !iparams.name.from_str(client_name)) {
+      lderr(cct) << "failed to set remote client name" << dendl;
+      return -EINVAL;
+    }
+
+    auto remote_cct = common_preinit(iparams, CODE_ENVIRONMENT_LIBRARY, 0);
+    auto put_remote_cct = make_scope_guard([remote_cct] { remote_cct->put(); });
+
+    remote_cct->_conf->cluster = cluster_name;
+
+    // pass CEPH_CONF_FILE_DEFAULT instead of nullptr to prevent
+    // CEPH_CONF environment variable from being picked up
+    r = remote_cct->_conf.parse_config_files(CEPH_CONF_FILE_DEFAULT, nullptr,
+                                             0);
+    if (r < 0) {
+      remote_cct->_conf.complain_about_parse_error(cct);
+      lderr(cct) << "failed to read ceph conf for remote cluster: "
+                 << cpp_strerror(r) << dendl;
+      return r;
+    }
+
+    remote_cct->_conf.apply_changes(nullptr);
+
+    rados_ptr.reset(new librados::Rados());
+    r = rados_ptr->init_with_context(remote_cct);
+    ceph_assert(r == 0);
+
+    r = rados_ptr->connect();
+    if (r < 0) {
+      lderr(cct) << "failed to connect to remote cluster: " << cpp_strerror(r)
+                 << dendl;
+      return r;
+    }
+  } else {
+    rados_ptr.reset(new librados::Rados(dst_io_ctx));
+  }
+
   librados::IoCtx src_io_ctx;
   if (!pool_name.empty()) {
     r = rados_ptr->ioctx_create(pool_name.c_str(), src_io_ctx);
@@ -202,6 +280,13 @@ int NativeFormat<I>::create_image_ctx(
     *src_image_ctx = I::create(image_name, image_id, snap_id, src_io_ctx,
                                true);
   }
+
+  if (!cluster_name.empty()) {
+    *src_rados = rados_ptr.release();
+  } else {
+    *src_rados = nullptr;
+  }
+
   return 0;
 }
 
index b4a84ae3e18381bb53545496fa6a1a69662034e4..581c6c0bb2d6a397bca3e75c5c1fac48c3454a1d 100644 (file)
@@ -28,7 +28,8 @@ public:
   static int create_image_ctx(librados::IoCtx& dst_io_ctx,
                               const json_spirit::mObject& source_spec_object,
                               bool import_only, uint64_t src_snap_id,
-                              ImageCtxT** src_image_ctx);
+                              ImageCtxT** src_image_ctx,
+                              librados::Rados** src_rados);
 };
 
 } // namespace migration
index 953de3b3927c80deb4e863b2b14cf032fd6e954e..2bd2d1935f49a0c3ac4d50cacc8f1426eafaa50f 100644 (file)
@@ -6,6 +6,7 @@
 #include "common/errno.h"
 #include "librbd/ImageCtx.h"
 #include "librbd/ImageState.h"
+#include "librbd/TaskFinisher.h"
 #include "librbd/Utils.h"
 #include "librbd/io/ImageDispatcher.h"
 #include "librbd/migration/FormatInterface.h"
@@ -24,11 +25,13 @@ namespace migration {
 template <typename I>
 OpenSourceImageRequest<I>::OpenSourceImageRequest(
     librados::IoCtx& dst_io_ctx, I* dst_image_ctx, uint64_t src_snap_id,
-    const MigrationInfo &migration_info, I** src_image_ctx, Context* on_finish)
+    const MigrationInfo &migration_info, I** src_image_ctx,
+    librados::Rados** src_rados, Context* on_finish)
   : m_cct(reinterpret_cast<CephContext*>(dst_io_ctx.cct())),
     m_dst_io_ctx(dst_io_ctx), m_dst_image_ctx(dst_image_ctx),
     m_src_snap_id(src_snap_id), m_migration_info(migration_info),
-    m_src_image_ctx(src_image_ctx), m_on_finish(on_finish) {
+    m_src_image_ctx(src_image_ctx), m_src_rados(src_rados),
+    m_on_finish(on_finish) {
   ldout(m_cct, 10) << dendl;
 }
 
@@ -74,7 +77,7 @@ void OpenSourceImageRequest<I>::open_native(
 
   int r = NativeFormat<I>::create_image_ctx(m_dst_io_ctx, source_spec_object,
                                             import_only, m_src_snap_id,
-                                            m_src_image_ctx);
+                                            m_src_image_ctx, m_src_rados);
   if (r < 0) {
     lderr(m_cct) << "failed to create native image context: "
                  << cpp_strerror(r) << dendl;
@@ -113,7 +116,17 @@ void OpenSourceImageRequest<I>::handle_open_native(int r) {
   if (r < 0) {
     lderr(m_cct) << "failed to open native image: " << cpp_strerror(r)
                  << dendl;
-    finish(r);
+
+    // m_src_rados must be deleted outside the scope of its task
+    // finisher thread to avoid the finisher attempting to destroy
+    // itself and locking up
+    // since the local image (m_dst_image_ctx) may not be available,
+    // redirect to the local rados' task finisher
+    auto ctx = new LambdaContext([this](int r) {
+      delete *m_src_rados;
+      finish(r);
+    });
+    TaskFinisherSingleton::get_singleton(m_cct).queue(ctx, r);
     return;
   }
 
@@ -127,6 +140,8 @@ void OpenSourceImageRequest<I>::open_format(
 
   // note that all source image ctx properties are placeholders
   *m_src_image_ctx = I::create("", "", CEPH_NOSNAP, m_dst_io_ctx, true);
+  *m_src_rados = nullptr;
+
   auto src_image_ctx = *m_src_image_ctx;
   src_image_ctx->child = m_dst_image_ctx;
 
@@ -293,6 +308,7 @@ void OpenSourceImageRequest<I>::finish(int r) {
 
   if (r < 0) {
     *m_src_image_ctx = nullptr;
+    *m_src_rados = nullptr;
   } else {
     register_image_dispatch();
   }
index 465e0aa14f35af18282b114b25c7311f99d8f658..a62c6a8d0069519b4bde53b467f6f93a31184d4a 100644 (file)
@@ -27,18 +27,20 @@ public:
                                         ImageCtxT* destination_image_ctx,
                                         uint64_t src_snap_id,
                                         const MigrationInfo &migration_info,
-                                        ImageCtxT** source_image_ctx,
+                                        ImageCtxT** src_image_ctx,
+                                        librados::Rados** src_rados,
                                         Context* on_finish) {
     return new OpenSourceImageRequest(dst_io_ctx, destination_image_ctx,
                                       src_snap_id, migration_info,
-                                      source_image_ctx, on_finish);
+                                      src_image_ctx, src_rados, on_finish);
   }
 
   OpenSourceImageRequest(librados::IoCtx& dst_io_ctx,
                          ImageCtxT* destination_image_ctx,
                          uint64_t src_snap_id,
                          const MigrationInfo &migration_info,
-                         ImageCtxT** source_image_ctx,
+                         ImageCtxT** src_image_ctx,
+                         librados::Rados** src_rados,
                          Context* on_finish);
 
   void send();
@@ -79,6 +81,7 @@ private:
   uint64_t m_src_snap_id;
   MigrationInfo m_migration_info;
   ImageCtxT** m_src_image_ctx;
+  librados::Rados** m_src_rados;
   Context* m_on_finish;
 
   std::unique_ptr<FormatInterface> m_format;