]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
librbd: new RBD mirroring peer bootstrap API methods
authorJason Dillaman <dillaman@redhat.com>
Fri, 13 Sep 2019 17:12:02 +0000 (13:12 -0400)
committerJason Dillaman <dillaman@redhat.com>
Wed, 18 Sep 2019 18:06:45 +0000 (14:06 -0400)
These new methods will help facilitate the creation of keys and
the transfer of cluster connection metadata between Ceph clusters.

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/include/rbd/librbd.h
src/include/rbd/librbd.hpp
src/include/rbd_types.h
src/librbd/api/Mirror.cc
src/librbd/api/Mirror.h
src/librbd/librbd.cc
src/pybind/rbd/rbd.pyx
src/test/librbd/test_mirroring.cc
src/test/pybind/test_rbd.py

index 78d358ddc86bfdf4d7f1173d5c79478f0d23fc2c..55668b0632784ee5a2c28f196142af986c4f5f96 100644 (file)
@@ -139,6 +139,12 @@ typedef enum {
   RBD_MIRROR_MODE_POOL      /* mirroring enabled on all journaled images */
 } rbd_mirror_mode_t;
 
+typedef enum {
+  RBD_MIRROR_PEER_DIRECTION_RX    = 0,
+  RBD_MIRROR_PEER_DIRECTION_TX    = 1,
+  RBD_MIRROR_PEER_DIRECTION_RX_TX = 2
+} rbd_mirror_peer_direction_t;
+
 typedef struct {
   char *uuid;
   char *cluster_name;
@@ -441,6 +447,12 @@ CEPH_RBD_API int rbd_mirror_mode_get(rados_ioctx_t io_ctx,
 CEPH_RBD_API int rbd_mirror_mode_set(rados_ioctx_t io_ctx,
                                      rbd_mirror_mode_t mirror_mode);
 
+CEPH_RBD_API int rbd_mirror_peer_bootstrap_create(rados_ioctx_t io_ctx,
+                                                  char *token, size_t *max_len);
+CEPH_RBD_API int rbd_mirror_peer_bootstrap_import(
+    rados_ioctx_t io_ctx, rbd_mirror_peer_direction_t direction,
+    const char *token);
+
 CEPH_RBD_API int rbd_mirror_peer_add(rados_ioctx_t io_ctx,
                                      char *uuid, size_t uuid_max_length,
                                      const char *cluster_name,
index d1d2667bd1043678df84d2eb9caa8de2ac1bfd0f..15eb9f56a31db9525c68cf4fe435a76cb1b64688 100644 (file)
@@ -74,6 +74,8 @@ namespace librbd {
     std::string address;
   } locker_t;
 
+  typedef rbd_mirror_peer_direction_t mirror_peer_direction_t;
+
   typedef struct {
     std::string uuid;
     std::string cluster_name;
@@ -270,6 +272,11 @@ public:
   int mirror_mode_get(IoCtx& io_ctx, rbd_mirror_mode_t *mirror_mode);
   int mirror_mode_set(IoCtx& io_ctx, rbd_mirror_mode_t mirror_mode);
 
+  int mirror_peer_bootstrap_create(IoCtx& io_ctx, std::string* token);
+  int mirror_peer_bootstrap_import(IoCtx& io_ctx,
+                                   mirror_peer_direction_t direction,
+                                   const std::string &token);
+
   int mirror_peer_add(IoCtx& io_ctx, std::string *uuid,
                       const std::string &cluster_name,
                       const std::string &client_name);
index 2b6243af9e191159669eb040e0d2f74eea2400f8..35a1a8bc3c3bd5c1109eae3b8c20ff2c1994ee78 100644 (file)
  * MON config-key prefix for storing optional remote cluster connectivity
  * parameters
  */
-#define RBD_MIRROR_CONFIG_KEY_PREFIX "rbd/mirror/"
-#define RBD_MIRROR_SITE_NAME_CONFIG_KEY RBD_MIRROR_CONFIG_KEY_PREFIX "site_name"
-#define RBD_MIRROR_PEER_CONFIG_KEY_PREFIX RBD_MIRROR_CONFIG_KEY_PREFIX "peer/"
+#define RBD_MIRROR_CONFIG_KEY_PREFIX          "rbd/mirror/"
+#define RBD_MIRROR_SITE_NAME_CONFIG_KEY       RBD_MIRROR_CONFIG_KEY_PREFIX "site_name"
+#define RBD_MIRROR_PEER_CLIENT_ID_CONFIG_KEY  RBD_MIRROR_CONFIG_KEY_PREFIX "peer_client_id"
+#define RBD_MIRROR_PEER_CONFIG_KEY_PREFIX     RBD_MIRROR_CONFIG_KEY_PREFIX "peer/"
 
 struct rbd_info {
        ceph_le64 max_id;
index 985c591b39acc700229ec7e8123effa5bb3e343e..4e2b370457041fbdbab090841a3347a53082ab74 100644 (file)
@@ -24,6 +24,7 @@
 #include "librbd/mirror/Types.h"
 #include "librbd/MirroringWatcher.h"
 #include <boost/algorithm/string/trim.hpp>
+#include <boost/algorithm/string/replace.hpp>
 #include <boost/scope_exit.hpp>
 #include "json_spirit/json_spirit.h"
 
@@ -105,6 +106,141 @@ int remove_peer_config_key(librados::IoCtx& io_ctx,
   return 0;
 }
 
+int create_bootstrap_user(CephContext* cct, librados::Rados& rados,
+                          std::string* peer_client_id, std::string* cephx_key) {
+  ldout(cct, 20) << dendl;
+
+  // retrieve peer CephX user from config-key
+  int r = get_config_key(rados, RBD_MIRROR_PEER_CLIENT_ID_CONFIG_KEY,
+                         peer_client_id);
+  if (r == -EACCES) {
+      ldout(cct, 5) << "insufficient permissions to get peer-client-id "
+                    << "config-key" << dendl;
+      return r;
+  } else if (r < 0 && r != -ENOENT) {
+    lderr(cct) << "failed to retrieve peer client id key: "
+               << cpp_strerror(r) << dendl;
+    return r;
+  } else if (r == -ENOENT || peer_client_id->empty()) {
+    ldout(cct, 20) << "creating new peer-client-id config-key" << dendl;
+
+    *peer_client_id = "rbd-mirror-peer";
+    r = set_config_key(rados, RBD_MIRROR_PEER_CLIENT_ID_CONFIG_KEY,
+                       *peer_client_id);
+    if (r == -EACCES) {
+      ldout(cct, 5) << "insufficient permissions to update peer-client-id "
+                    << "config-key" << dendl;
+      return r;
+    } else if (r < 0) {
+      lderr(cct) << "failed to update peer client id key: "
+                 << cpp_strerror(r) << dendl;
+      return r;
+    }
+  }
+  ldout(cct, 20) << "peer_client_id=" << *peer_client_id << dendl;
+
+  // create peer client user
+  std::string cmd =
+    R"({)" \
+    R"(  "prefix": "auth get-or-create",)" \
+    R"(  "entity": "client.)" + *peer_client_id + R"(",)" \
+    R"(  "caps": [)" \
+    R"(    "mon", "profile rbd-mirror-peer",)" \
+    R"(    "osd", "profile rbd"],)" \
+    R"(  "format": "json")" \
+    R"(})";
+
+  bufferlist in_bl;
+  bufferlist out_bl;
+
+  r = rados.mon_command(cmd, in_bl, &out_bl, nullptr);
+  if (r == -EINVAL) {
+    ldout(cct, 5) << "caps mismatch for existing user" << dendl;
+    return -EEXIST;
+  } else if (r == -EACCES) {
+    ldout(cct, 5) << "insufficient permissions to create user" << dendl;
+    return r;
+  } else if (r < 0) {
+    lderr(cct) << "failed to create or update RBD mirroring bootstrap user: "
+               << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  // extract key from response
+  bool json_valid = false;
+  json_spirit::mValue json_root;
+  if(json_spirit::read(out_bl.to_str(), json_root)) {
+    try {
+      auto& json_obj = json_root.get_array()[0].get_obj();
+      *cephx_key = json_obj["key"].get_str();
+      json_valid = true;
+    } catch (std::runtime_error&) {
+    }
+  }
+
+  if (!json_valid) {
+    lderr(cct) << "invalid auth keyring JSON received" << dendl;
+    return -EBADMSG;
+  }
+
+  return 0;
+}
+
+int create_bootstrap_peer(CephContext* cct, librados::IoCtx& io_ctx,
+                          const std::string& site_name, const std::string& fsid,
+                          const std::string& client_id, const std::string& key,
+                          const std::string& mon_host,
+                          const std::string& cluster1,
+                          const std::string& cluster2) {
+  ldout(cct, 20) << dendl;
+
+  std::string peer_uuid;
+  std::vector<mirror_peer_t> peers;
+  int r = Mirror<>::peer_list(io_ctx, &peers);
+  if (r < 0 && r != -ENOENT) {
+    lderr(cct) << "failed to list mirror peers: " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  if (peers.empty()) {
+    r = Mirror<>::peer_add(io_ctx, &peer_uuid, site_name,
+                           "client." + client_id);
+    if (r < 0) {
+      lderr(cct) << "failed to add " << cluster1 << " peer to "
+                 << cluster2 << " " << "cluster: " << cpp_strerror(r) << dendl;
+      return r;
+    }
+  } else if (peers[0].cluster_name != site_name &&
+             peers[0].cluster_name != fsid) {
+    // only support a single peer
+    lderr(cct) << "multiple peers are not currently supported" << dendl;
+    return -EINVAL;
+  } else {
+    peer_uuid = peers[0].uuid;
+
+    if (peers[0].cluster_name != site_name) {
+      r = Mirror<>::peer_set_cluster(io_ctx, peer_uuid, site_name);
+      if (r < 0) {
+        // non-fatal attempt to update site name
+        lderr(cct) << "failed to update peer site name" << dendl;
+      }
+    }
+  }
+
+  Mirror<>::Attributes attributes {
+    {"mon_host", mon_host},
+    {"key", key}};
+  r = Mirror<>::peer_set_attributes(io_ctx, peer_uuid, attributes);
+  if (r < 0) {
+    lderr(cct) << "failed to update " << cluster1 << " cluster connection "
+               << "attributes in " << cluster2 << " cluster: "
+               << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  return 0;
+}
+
 template <typename I>
 int validate_mirroring_enabled(I *ictx) {
   CephContext *cct = ictx->cct;
@@ -475,11 +611,6 @@ int Mirror<I>::image_resync(I *ictx) {
     return r;
   }
 
-  r = validate_mirroring_enabled(ictx);
-  if (r < 0) {
-    return r;
-  }
-
   C_SaferCond tag_owner_ctx;
   bool is_tag_owner;
   Journal<I>::is_tag_owner(ictx, &is_tag_owner, &tag_owner_ctx);
@@ -853,6 +984,234 @@ int Mirror<I>::mode_set(librados::IoCtx& io_ctx,
   return 0;
 }
 
+template <typename I>
+int Mirror<I>::peer_bootstrap_create(librados::IoCtx& io_ctx,
+                                     std::string* token) {
+  CephContext *cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+  ldout(cct, 20) << dendl;
+
+  auto mirror_mode = cls::rbd::MIRROR_MODE_DISABLED;
+  int r = cls_client::mirror_mode_get(&io_ctx, &mirror_mode);
+  if (r < 0 && r != -ENOENT) {
+    lderr(cct) << "failed to retrieve mirroring mode: " << cpp_strerror(r)
+               << dendl;
+    return r;
+  } else if (mirror_mode == cls::rbd::MIRROR_MODE_DISABLED) {
+    return -EINVAL;
+  }
+
+  // retrieve the cluster fsid
+  std::string fsid;
+  librados::Rados rados(io_ctx);
+  r = rados.cluster_fsid(&fsid);
+  if (r < 0) {
+    lderr(cct) << "failed to retrieve cluster fsid: " << cpp_strerror(r)
+               << dendl;
+    return r;
+  }
+
+  std::string peer_client_id;
+  std::string cephx_key;
+  r = create_bootstrap_user(cct, rados, &peer_client_id, &cephx_key);
+  if (r < 0) {
+    return r;
+  }
+
+  std::string mon_host = cct->_conf.get_val<std::string>("mon_host");
+  ldout(cct, 20) << "mon_host=" << mon_host << dendl;
+
+  // format the token response
+  bufferlist token_bl;
+  token_bl.append(
+    R"({)" \
+      R"("fsid":")" + fsid + R"(",)" + \
+      R"("client_id":")" + peer_client_id + R"(",)" + \
+      R"("key":")" + cephx_key + R"(",)" + \
+      R"("mon_host":")" + \
+        boost::replace_all_copy(mon_host, "\"", "\\\"") + R"(")" + \
+    R"(})");
+  ldout(cct, 20) << "token=" << token_bl.to_str() << dendl;
+
+  bufferlist base64_bl;
+  token_bl.encode_base64(base64_bl);
+  *token = base64_bl.to_str();
+
+  return 0;
+}
+
+template <typename I>
+int Mirror<I>::peer_bootstrap_import(librados::IoCtx& io_ctx,
+                                     rbd_mirror_peer_direction_t direction,
+                                     const std::string& token) {
+  CephContext *cct = reinterpret_cast<CephContext *>(io_ctx.cct());
+  ldout(cct, 20) << dendl;
+
+  if (direction != RBD_MIRROR_PEER_DIRECTION_RX &&
+      direction != RBD_MIRROR_PEER_DIRECTION_RX_TX) {
+    lderr(cct) << "invalid mirror peer direction" << dendl;
+    return -EINVAL;
+  }
+
+  bufferlist token_bl;
+  try {
+    bufferlist base64_bl;
+    base64_bl.append(token);
+    token_bl.decode_base64(base64_bl);
+  } catch (buffer::error& err) {
+    lderr(cct) << "failed to decode base64" << dendl;
+    return -EINVAL;
+  }
+
+  ldout(cct, 20) << "token=" << token_bl.to_str() << dendl;
+
+  bool json_valid = false;
+  std::string expected_remote_fsid;
+  std::string remote_client_id;
+  std::string remote_key;
+  std::string remote_mon_host;
+
+  json_spirit::mValue json_root;
+  if(json_spirit::read(token_bl.to_str(), json_root)) {
+    try {
+      auto& json_obj = json_root.get_obj();
+      expected_remote_fsid = json_obj["fsid"].get_str();
+      remote_client_id = json_obj["client_id"].get_str();
+      remote_key = json_obj["key"].get_str();
+      remote_mon_host = json_obj["mon_host"].get_str();
+      json_valid = true;
+    } catch (std::runtime_error&) {
+    }
+  }
+
+  if (!json_valid) {
+    lderr(cct) << "invalid bootstrap token JSON received" << dendl;
+    return -EINVAL;
+  }
+
+  // sanity check import process
+  std::string local_fsid;
+  librados::Rados rados(io_ctx);
+  int r = rados.cluster_fsid(&local_fsid);
+  if (r < 0) {
+    lderr(cct) << "failed to retrieve cluster fsid: " << cpp_strerror(r)
+               << dendl;
+    return r;
+  }
+
+  std::string local_site_name;
+  r = site_name_get(rados, &local_site_name);
+  if (r < 0) {
+    lderr(cct) << "failed to retrieve cluster site name: " << cpp_strerror(r)
+               << dendl;
+    return r;
+  }
+
+  // attempt to connect to remote cluster
+  librados::Rados remote_rados;
+  remote_rados.init(remote_client_id.c_str());
+
+  auto remote_cct = reinterpret_cast<CephContext*>(remote_rados.cct());
+  remote_cct->_conf.set_val("mon_host", remote_mon_host);
+  remote_cct->_conf.set_val("key", remote_key);
+
+  r = remote_rados.connect();
+  if (r < 0) {
+    lderr(cct) << "failed to connect to peer cluster: " << cpp_strerror(r)
+               << dendl;
+    return r;
+  }
+
+  std::string remote_fsid;
+  r = remote_rados.cluster_fsid(&remote_fsid);
+  if (r < 0) {
+    lderr(cct) << "failed to retrieve remote cluster fsid: "
+               << cpp_strerror(r) << dendl;
+    return r;
+  } else if (local_fsid == remote_fsid) {
+    lderr(cct) << "cannot import token for local cluster" << dendl;
+    return -EINVAL;
+  } else if (expected_remote_fsid != remote_fsid) {
+    lderr(cct) << "unexpected remote cluster fsid" << dendl;
+    return -EINVAL;
+  }
+
+  std::string remote_site_name;
+  r = site_name_get(remote_rados, &remote_site_name);
+  if (r < 0) {
+    lderr(cct) << "failed to retrieve remote cluster site name: "
+               << cpp_strerror(r) << dendl;
+    return r;
+  } else if (local_site_name == remote_site_name) {
+    lderr(cct) << "cannot import token for duplicate site name" << dendl;
+    return -EINVAL;
+  }
+
+  librados::IoCtx remote_io_ctx;
+  r = remote_rados.ioctx_create(io_ctx.get_pool_name().c_str(), remote_io_ctx);
+  if (r == -ENOENT) {
+    ldout(cct, 10) << "remote pool does not exist" << dendl;
+    return r;
+  } else if (r < 0) {
+    lderr(cct) << "failed to open remote pool '" << io_ctx.get_pool_name()
+               << "': " << cpp_strerror(r) << dendl;
+    return r;
+  }
+
+  auto remote_mirror_mode = cls::rbd::MIRROR_MODE_DISABLED;
+  r = cls_client::mirror_mode_get(&remote_io_ctx, &remote_mirror_mode);
+  if (r < 0 && r != -ENOENT) {
+    lderr(cct) << "failed to retrieve remote mirroring mode: "
+               << cpp_strerror(r) << dendl;
+    return r;
+  } else if (remote_mirror_mode == cls::rbd::MIRROR_MODE_DISABLED) {
+    return -ENOSYS;
+  }
+
+  auto local_mirror_mode = cls::rbd::MIRROR_MODE_DISABLED;
+  r = cls_client::mirror_mode_get(&io_ctx, &local_mirror_mode);
+  if (r < 0 && r != -ENOENT) {
+    lderr(cct) << "failed to retrieve local mirroring mode: " << cpp_strerror(r)
+               << dendl;
+    return r;
+  } else if (local_mirror_mode == cls::rbd::MIRROR_MODE_DISABLED) {
+    // copy mirror mode from remote peer
+    r = mode_set(io_ctx, static_cast<rbd_mirror_mode_t>(remote_mirror_mode));
+    if (r < 0) {
+      return r;
+    }
+  }
+
+  if (direction == RBD_MIRROR_PEER_DIRECTION_RX_TX) {
+    // create a local mirror peer user and export it to the remote cluster
+    std::string local_client_id;
+    std::string local_key;
+    r = create_bootstrap_user(cct, rados, &local_client_id, &local_key);
+    if (r < 0) {
+      return r;
+    }
+
+    std::string local_mon_host = cct->_conf.get_val<std::string>("mon_host");
+
+    // create local cluster peer in remote cluster
+    r = create_bootstrap_peer(cct, remote_io_ctx, local_site_name, local_fsid,
+                              local_client_id, local_key, local_mon_host,
+                              "local", "remote");
+    if (r < 0) {
+      return r;
+    }
+  }
+
+  // create remote cluster peer in local cluster
+  r = create_bootstrap_peer(cct, io_ctx, remote_site_name, remote_fsid,
+                            remote_client_id, remote_key, remote_mon_host,
+                            "remote", "local");
+  if (r < 0) {
+    return r;
+  }
+
+  return 0;
+}
+
 template <typename I>
 int Mirror<I>::peer_add(librados::IoCtx& io_ctx, std::string *uuid,
                         const std::string &cluster_name,
index 0544a0547c5822f2181e71c7ddbb658ab27716ae..94768acdbbd66999bb776e872cf63151a58b7c0a 100644 (file)
@@ -29,6 +29,11 @@ struct Mirror {
   static int mode_get(librados::IoCtx& io_ctx, rbd_mirror_mode_t *mirror_mode);
   static int mode_set(librados::IoCtx& io_ctx, rbd_mirror_mode_t mirror_mode);
 
+  static int peer_bootstrap_create(librados::IoCtx& io_ctx, std::string* token);
+  static int peer_bootstrap_import(librados::IoCtx& io_ctx,
+                                   rbd_mirror_peer_direction_t direction,
+                                   const std::string& token);
+
   static int peer_add(librados::IoCtx& io_ctx, std::string *uuid,
                       const std::string &cluster_name,
                       const std::string &client_name);
index 84d0984ef1b6d9202a54082a988cc62e7f364157..bd8a58e86c19152999037c376d21a7492a506e89 100644 (file)
@@ -882,6 +882,17 @@ namespace librbd {
     return librbd::api::Mirror<>::mode_set(io_ctx, mirror_mode);
   }
 
+  int RBD::mirror_peer_bootstrap_create(IoCtx& io_ctx, std::string* token) {
+    return librbd::api::Mirror<>::peer_bootstrap_create(io_ctx, token);
+  }
+
+  int RBD::mirror_peer_bootstrap_import(IoCtx& io_ctx,
+                                        rbd_mirror_peer_direction_t direction,
+                                        const std::string& token) {
+    return librbd::api::Mirror<>::peer_bootstrap_import(io_ctx, direction,
+                                                        token);
+  }
+
   int RBD::mirror_peer_add(IoCtx& io_ctx, std::string *uuid,
                            const std::string &cluster_name,
                            const std::string &client_name) {
@@ -2770,6 +2781,37 @@ extern "C" int rbd_mirror_mode_set(rados_ioctx_t p,
   return librbd::api::Mirror<>::mode_set(io_ctx, mirror_mode);
 }
 
+extern "C" int rbd_mirror_peer_bootstrap_create(rados_ioctx_t p, char *token,
+                                                size_t *max_len) {
+  librados::IoCtx io_ctx;
+  librados::IoCtx::from_rados_ioctx_t(p, io_ctx);
+
+  std::string token_str;
+  int r = librbd::api::Mirror<>::peer_bootstrap_create(io_ctx, &token_str);
+  if (r < 0) {
+    return r;
+  }
+
+  auto total_len = token_str.size() + 1;
+  if (*max_len < total_len) {
+    *max_len = total_len;
+    return -ERANGE;
+  }
+  *max_len = total_len;
+
+  strcpy(token, token_str.c_str());
+  return 0;
+}
+
+extern "C" int rbd_mirror_peer_bootstrap_import(
+    rados_ioctx_t p, rbd_mirror_peer_direction_t direction,
+    const char *token) {
+  librados::IoCtx io_ctx;
+  librados::IoCtx::from_rados_ioctx_t(p, io_ctx);
+
+  return librbd::api::Mirror<>::peer_bootstrap_import(io_ctx, direction, token);
+}
+
 extern "C" int rbd_mirror_peer_add(rados_ioctx_t p, char *uuid,
                                    size_t uuid_max_length,
                                    const char *cluster_name,
index 02fa9d6a2c97147ad3bbe3c524188c1eaad623ae..9aae566c623676c9a828f7fcdf0d9832933d064c 100644 (file)
@@ -154,6 +154,11 @@ cdef extern from "rbd/librbd.h" nogil:
         _RBD_MIRROR_MODE_IMAGE "RBD_MIRROR_MODE_IMAGE"
         _RBD_MIRROR_MODE_POOL "RBD_MIRROR_MODE_POOL"
 
+    ctypedef enum rbd_mirror_peer_direction_t:
+        _RBD_MIRROR_PEER_DIRECTION_RX "RBD_MIRROR_PEER_DIRECTION_RX"
+        _RBD_MIRROR_PEER_DIRECTION_TX "RBD_MIRROR_PEER_DIRECTION_TX"
+        _RBD_MIRROR_PEER_DIRECTION_RX_TX "RBD_MIRROR_PEER_DIRECTION_RX_TX"
+
     ctypedef struct rbd_mirror_peer_t:
         char *uuid
         char *cluster_name
@@ -347,6 +352,12 @@ cdef extern from "rbd/librbd.h" nogil:
     int rbd_mirror_mode_get(rados_ioctx_t io, rbd_mirror_mode_t *mirror_mode)
     int rbd_mirror_mode_set(rados_ioctx_t io, rbd_mirror_mode_t mirror_mode)
 
+    int rbd_mirror_peer_bootstrap_create(rados_ioctx_t io_ctx, char *token,
+                                         size_t *max_len)
+    int rbd_mirror_peer_bootstrap_import(
+        rados_ioctx_t io_ctx, rbd_mirror_peer_direction_t direction,
+        const char *token)
+
     int rbd_mirror_peer_add(rados_ioctx_t io, char *uuid,
                             size_t uuid_max_length, const char *cluster_name,
                             const char *client_name)
@@ -653,6 +664,10 @@ RBD_MIRROR_MODE_DISABLED = _RBD_MIRROR_MODE_DISABLED
 RBD_MIRROR_MODE_IMAGE = _RBD_MIRROR_MODE_IMAGE
 RBD_MIRROR_MODE_POOL = _RBD_MIRROR_MODE_POOL
 
+RBD_MIRROR_PEER_DIRECTION_RX = _RBD_MIRROR_PEER_DIRECTION_RX
+RBD_MIRROR_PEER_DIRECTION_TX = _RBD_MIRROR_PEER_DIRECTION_TX
+RBD_MIRROR_PEER_DIRECTION_RX_TX = _RBD_MIRROR_PEER_DIRECTION_RX_TX
+
 RBD_MIRROR_IMAGE_DISABLING = _RBD_MIRROR_IMAGE_DISABLING
 RBD_MIRROR_IMAGE_ENABLED = _RBD_MIRROR_IMAGE_ENABLED
 RBD_MIRROR_IMAGE_DISABLED = _RBD_MIRROR_IMAGE_DISABLED
@@ -1770,6 +1785,55 @@ class RBD(object):
         if ret != 0:
             raise make_ex(ret, 'error setting mirror mode')
 
+    def mirror_peer_bootstrap_create(self, ioctx):
+        """
+        Creates a new RBD mirroring bootstrap token for an
+        external cluster.
+
+        :param ioctx: determines which RADOS pool is written
+        :type ioctx: :class:`rados.Ioctx`
+        :returns: str - bootstrap token
+        """
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            char *_token = NULL
+            size_t _max_size = 512
+        try:
+            while True:
+                _token = <char *>realloc_chk(_token, _max_size)
+                with nogil:
+                    ret = rbd_mirror_peer_bootstrap_create(_ioctx, _token,
+                                                           &_max_size)
+                if ret >= 0:
+                    break
+                elif ret != -errno.ERANGE:
+                    raise make_ex(ret, 'error creating bootstrap token')
+            return decode_cstr(_token)
+        finally:
+            free(_token)
+
+    def mirror_peer_bootstrap_import(self, ioctx, direction, token):
+        """
+        Import a bootstrap token from an external cluster to
+        auto-configure the mirror peer.
+
+        :param ioctx: determines which RADOS pool is written
+        :type ioctx: :class:`rados.Ioctx`
+        :param direction: mirror peer direction
+        :type direction: int
+        :param token: bootstrap token
+        :type token: str
+        """
+        token = cstr(token, 'token')
+        cdef:
+            rados_ioctx_t _ioctx = convert_ioctx(ioctx)
+            rbd_mirror_peer_direction_t _direction = direction
+            char *_token = token
+        with nogil:
+            ret = rbd_mirror_peer_bootstrap_import(_ioctx, _direction, _token)
+        if ret != 0:
+            raise make_ex(ret, 'error importing bootstrap token')
+
     def mirror_peer_add(self, ioctx, cluster_name, client_name):
         """
         Add mirror peer.
index a31b11e00b0aebfd7384c16d1a81a7129ad5e8ba..204a38ff724d3b7a563da2d26441a58844400cbc 100644 (file)
@@ -968,3 +968,25 @@ TEST_F(TestMirroring, SiteName) {
   ASSERT_EQ(0, m_rbd.mirror_site_name_get(_rados, &site_name));
   ASSERT_EQ(fsid, site_name);
 }
+
+TEST_F(TestMirroring, Bootstrap) {
+  REQUIRE(!is_librados_test_stub(_rados));
+
+  std::string token_b64;
+  ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_DISABLED));
+  ASSERT_EQ(-EINVAL, m_rbd.mirror_peer_bootstrap_create(m_ioctx, &token_b64));
+
+  ASSERT_EQ(0, m_rbd.mirror_mode_set(m_ioctx, RBD_MIRROR_MODE_POOL));
+  ASSERT_EQ(0, m_rbd.mirror_peer_bootstrap_create(m_ioctx, &token_b64));
+
+  bufferlist token_b64_bl;
+  token_b64_bl.append(token_b64);
+
+  bufferlist token_bl;
+  token_bl.decode_base64(token_b64_bl);
+
+  // cannot import token into same cluster
+  ASSERT_EQ(-EINVAL,
+            m_rbd.mirror_peer_bootstrap_import(
+              m_ioctx, RBD_MIRROR_PEER_DIRECTION_RX, token_b64));
+}
index c5f3ed109c97184d80804f77037a9d145d7e6750..1f2a5b8ddd862d0a54aabc1e6a85937b6c90d65e 100644 (file)
@@ -1,6 +1,8 @@
 # vim: expandtab smarttab shiftwidth=4 softtabstop=4
+import base64
 import errno
 import functools
+import json
 import socket
 import os
 import time
@@ -28,7 +30,8 @@ from rbd import (RBD, Group, Image, ImageNotFound, InvalidArgument, ImageExists,
                  RBD_IMAGE_MIGRATION_STATE_PREPARED, RBD_CONFIG_SOURCE_CONFIG,
                  RBD_CONFIG_SOURCE_POOL, RBD_CONFIG_SOURCE_IMAGE,
                  RBD_MIRROR_PEER_ATTRIBUTE_NAME_MON_HOST,
-                 RBD_MIRROR_PEER_ATTRIBUTE_NAME_KEY)
+                 RBD_MIRROR_PEER_ATTRIBUTE_NAME_KEY,
+                 RBD_MIRROR_PEER_DIRECTION_RX)
 
 rados = None
 ioctx = None
@@ -1796,6 +1799,24 @@ class TestMirroring(object):
         self.rbd.mirror_site_name_set(rados, "")
         eq(rados.get_fsid(), self.rbd.mirror_site_name_get(rados))
 
+    def test_mirror_peer_bootstrap(self):
+        eq([], list(self.rbd.mirror_peer_list(ioctx)))
+
+        self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_DISABLED)
+        assert_raises(InvalidArgument, self.rbd.mirror_peer_bootstrap_create,
+                      ioctx);
+
+        self.rbd.mirror_mode_set(ioctx, RBD_MIRROR_MODE_POOL)
+        token_b64 = self.rbd.mirror_peer_bootstrap_create(ioctx)
+        token = base64.b64decode(token_b64)
+        token_dict = json.loads(token)
+        eq(sorted(['fsid', 'client_id', 'key', 'mon_host']),
+            sorted(list(token_dict.keys())))
+
+        # requires different cluster
+        assert_raises(InvalidArgument, self.rbd.mirror_peer_bootstrap_import,
+            ioctx, RBD_MIRROR_PEER_DIRECTION_RX, token_b64)
+
     def test_mirror_peer(self):
         eq([], list(self.rbd.mirror_peer_list(ioctx)))
         cluster_name = "test_cluster"