[libvirt] [PATCH] storage backend: Add RBD (RADOS Block Device) support

Wido den Hollander wido at widodh.nl
Fri Mar 30 09:04:43 UTC 2012


This patch adds support for a new storage backend with RBD support.

RBD is the RADOS Block Device and is part of the Ceph distributed storage system.

It comes in two flavours: Qemu-RBD and Kernel RBD, this storage backend only supports
Qemu-RBD, thus limiting the use of this storage driver to Qemu only.

To function this backend relies on librbd and librados being present on the local system.

The backend also supports Cephx authentication for safe authentication with the Ceph cluster.

For storing credentials it uses the build-in secret mechanism of libvirt.

Signed-off-by: Wido den Hollander <wido at widodh.nl>
---
 configure.ac                             |   20 ++
 include/libvirt/libvirt.h.in             |    1 +
 src/Makefile.am                          |    9 +
 src/conf/storage_conf.c                  |  197 ++++++++++---
 src/conf/storage_conf.h                  |   16 +
 src/storage/storage_backend.c            |    6 +
 src/storage/storage_backend_rbd.c        |  465 ++++++++++++++++++++++++++++++
 src/storage/storage_backend_rbd.h        |   30 ++
 tests/storagepoolxml2xmlin/pool-rbd.xml  |   11 +
 tests/storagepoolxml2xmlout/pool-rbd.xml |   15 +
 tools/virsh.c                            |    7 +
 11 files changed, 734 insertions(+), 43 deletions(-)
 create mode 100644 src/storage/storage_backend_rbd.c
 create mode 100644 src/storage/storage_backend_rbd.h
 create mode 100644 tests/storagepoolxml2xmlin/pool-rbd.xml
 create mode 100644 tests/storagepoolxml2xmlout/pool-rbd.xml

diff --git a/configure.ac b/configure.ac
index 32cc8d0..b693e5b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1743,6 +1743,8 @@ AC_ARG_WITH([storage-mpath],
   AC_HELP_STRING([--with-storage-mpath], [with mpath backend for the storage driver @<:@default=check@:>@]),[],[with_storage_mpath=check])
 AC_ARG_WITH([storage-disk],
   AC_HELP_STRING([--with-storage-disk], [with GPartd Disk backend for the storage driver @<:@default=check@:>@]),[],[with_storage_disk=check])
+AC_ARG_WITH([storage-rbd],
+  AC_HELP_STRING([--with-storage-rbd], [with RADOS Block Device backend for the storage driver @<:@default=check@:>@]),[],[with_storage_rbd=check])
 
 if test "$with_libvirtd" = "no"; then
   with_storage_dir=no
@@ -1752,6 +1754,7 @@ if test "$with_libvirtd" = "no"; then
   with_storage_scsi=no
   with_storage_mpath=no
   with_storage_disk=no
+  with_storage_rbd=no
 fi
 if test "$with_storage_dir" = "yes" ; then
   AC_DEFINE_UNQUOTED([WITH_STORAGE_DIR], 1, [whether directory backend for storage driver is enabled])
@@ -1910,6 +1913,22 @@ if test "$with_storage_mpath" = "check"; then
 fi
 AM_CONDITIONAL([WITH_STORAGE_MPATH], [test "$with_storage_mpath" = "yes"])
 
+if test "$with_storage_rbd" = "yes" || test "$with_storage_rbd" = "check"; then
+    AC_CHECK_HEADER([rbd/librbd.h], [LIBRBD_FOUND=yes; break;])
+
+    LIBRBD_LIBS="-lrbd -lrados -lcrypto"
+
+    if test "$LIBRBD_FOUND" = "yes"; then
+        with_storage_rbd=yes
+        LIBS="$LIBS $LIBRBD_LIBS"
+    else
+        with_storage_rbd=no
+    fi
+
+    AC_DEFINE_UNQUOTED([WITH_STORAGE_RBD], 1, [wether RBD backend for storage driver is enabled])
+fi
+AM_CONDITIONAL([WITH_STORAGE_RBD], [test "$with_storage_rbd" = "yes"])
+
 LIBPARTED_CFLAGS=
 LIBPARTED_LIBS=
 if test "$with_storage_disk" = "yes" ||
@@ -2673,6 +2692,7 @@ AC_MSG_NOTICE([   iSCSI: $with_storage_iscsi])
 AC_MSG_NOTICE([    SCSI: $with_storage_scsi])
 AC_MSG_NOTICE([   mpath: $with_storage_mpath])
 AC_MSG_NOTICE([    Disk: $with_storage_disk])
+AC_MSG_NOTICE([     RBD: $with_storage_rbd])
 AC_MSG_NOTICE([])
 AC_MSG_NOTICE([Security Drivers])
 AC_MSG_NOTICE([])
diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in
index 499dcd4..ee1d5ec 100644
--- a/include/libvirt/libvirt.h.in
+++ b/include/libvirt/libvirt.h.in
@@ -2324,6 +2324,7 @@ typedef enum {
   VIR_STORAGE_VOL_FILE = 0,     /* Regular file based volumes */
   VIR_STORAGE_VOL_BLOCK = 1,    /* Block based volumes */
   VIR_STORAGE_VOL_DIR = 2,      /* Directory-passthrough based volume */
+  VIR_STORAGE_VOL_NETWORK = 3,  /* Network volumes like RBD (RADOS Block Device) */
 
 #ifdef VIR_ENUM_SENTINELS
     VIR_STORAGE_VOL_LAST
diff --git a/src/Makefile.am b/src/Makefile.am
index a2aae9d..e4457c3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -498,6 +498,9 @@ STORAGE_DRIVER_MPATH_SOURCES =					\
 STORAGE_DRIVER_DISK_SOURCES =					\
 		storage/storage_backend_disk.h storage/storage_backend_disk.c
 
+STORAGE_DRIVER_RBD_SOURCES =					\
+		storage/storage_backend_rbd.h storage/storage_backend_rbd.c
+
 STORAGE_HELPER_DISK_SOURCES =					\
 		storage/parthelper.c
 
@@ -1040,6 +1043,11 @@ if WITH_STORAGE_DISK
 libvirt_driver_storage_la_SOURCES += $(STORAGE_DRIVER_DISK_SOURCES)
 endif
 
+if WITH_STORAGE_RBD
+libvirt_driver_storage_la_SOURCES += $(STORAGE_DRIVER_RBD_SOURCES)
+libvirt_la_LIBADD += $(LIBRBD_LIBS)
+endif
+
 if WITH_NODE_DEVICES
 # Needed to keep automake quiet about conditionals
 if WITH_DRIVER_MODULES
@@ -1139,6 +1147,7 @@ EXTRA_DIST +=							\
 		$(STORAGE_DRIVER_SCSI_SOURCES)			\
 		$(STORAGE_DRIVER_MPATH_SOURCES)			\
 		$(STORAGE_DRIVER_DISK_SOURCES)			\
+		$(STORAGE_DRIVER_RBD_SOURCES)			\
 		$(NODE_DEVICE_DRIVER_SOURCES)			\
 		$(NODE_DEVICE_DRIVER_HAL_SOURCES)		\
 		$(NODE_DEVICE_DRIVER_UDEV_SOURCES)		\
diff --git a/src/conf/storage_conf.c b/src/conf/storage_conf.c
index bdf6218..2a0b5eb 100644
--- a/src/conf/storage_conf.c
+++ b/src/conf/storage_conf.c
@@ -52,7 +52,7 @@ VIR_ENUM_IMPL(virStoragePool,
               VIR_STORAGE_POOL_LAST,
               "dir", "fs", "netfs",
               "logical", "disk", "iscsi",
-              "scsi", "mpath")
+              "scsi", "mpath", "rbd")
 
 VIR_ENUM_IMPL(virStoragePoolFormatFileSystem,
               VIR_STORAGE_POOL_FS_LAST,
@@ -110,6 +110,7 @@ enum {
     VIR_STORAGE_POOL_SOURCE_ADAPTER         = (1<<3),
     VIR_STORAGE_POOL_SOURCE_NAME            = (1<<4),
     VIR_STORAGE_POOL_SOURCE_INITIATOR_IQN   = (1<<5),
+    VIR_STORAGE_POOL_SOURCE_NETWORK         = (1<<6),
 };
 
 
@@ -194,6 +195,15 @@ static virStoragePoolTypeInfo poolTypeInfo[] = {
             .formatToString = virStoragePoolFormatDiskTypeToString,
         }
     },
+    { .poolType = VIR_STORAGE_POOL_RBD,
+      .poolOptions = {
+             .flags = (VIR_STORAGE_POOL_SOURCE_NETWORK |
+                       VIR_STORAGE_POOL_SOURCE_NAME),
+        },
+       .volOptions = {
+            .formatToString = virStoragePoolFormatDiskTypeToString,
+        }
+    },
     { .poolType = VIR_STORAGE_POOL_MPATH,
       .volOptions = {
             .formatToString = virStoragePoolFormatDiskTypeToString,
@@ -277,6 +287,11 @@ virStoragePoolSourceClear(virStoragePoolSourcePtr source)
         return;
 
     VIR_FREE(source->host.name);
+    for (i = 0 ; i < source->nhost ; i++) {
+        VIR_FREE(source->hosts[i].name);
+    }
+    VIR_FREE(source->hosts);
+
     for (i = 0 ; i < source->ndevice ; i++) {
         VIR_FREE(source->devices[i].freeExtents);
         VIR_FREE(source->devices[i].path);
@@ -293,6 +308,12 @@ virStoragePoolSourceClear(virStoragePoolSourcePtr source)
         VIR_FREE(source->auth.chap.login);
         VIR_FREE(source->auth.chap.passwd);
     }
+
+    if (source->authType == VIR_STORAGE_POOL_AUTH_CEPHX) {
+        VIR_FREE(source->auth.cephx.username);
+        VIR_FREE(source->auth.cephx.secret.uuid);
+        VIR_FREE(source->auth.cephx.secret.usage);
+    }
 }
 
 void
@@ -395,6 +416,27 @@ virStoragePoolDefParseAuthChap(xmlXPathContextPtr ctxt,
 }
 
 static int
+virStoragePoolDefParseAuthCephx(xmlXPathContextPtr ctxt,
+                               virStoragePoolAuthCephxPtr auth) {
+    auth->username = virXPathString("string(./auth/@username)", ctxt);
+    if (auth->username == NULL) {
+        virStorageReportError(VIR_ERR_XML_ERROR,
+                              "%s", _("missing auth username attribute"));
+        return -1;
+    }
+
+    auth->secret.uuid = virXPathString("string(./auth/secret/@uuid)", ctxt);
+    auth->secret.usage = virXPathString("string(./auth/secret/@usage)", ctxt);
+    if (auth->secret.uuid == NULL && auth->secret.usage == NULL) {
+        virStorageReportError(VIR_ERR_XML_ERROR,
+                              "%s", _("missing auth secret uuid or usage attribute"));
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
 virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
                              virStoragePoolSourcePtr source,
                              int pool_type,
@@ -414,6 +456,12 @@ virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
     }
 
     source->name = virXPathString("string(./name)", ctxt);
+    if (pool_type == VIR_STORAGE_POOL_RBD && source->name == NULL) {
+        virStorageReportError(VIR_ERR_XML_ERROR,
+                                  _("%s"), "missing mandatory 'name' field for RBD pool name");
+            VIR_FREE(source->name);
+            goto cleanup;
+    }
 
     if (options->formatFromString) {
         char *format = virXPathString("string(./format/@type)", ctxt);
@@ -431,17 +479,39 @@ virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
         VIR_FREE(format);
     }
 
-    source->host.name = virXPathString("string(./host/@name)", ctxt);
-    port = virXPathString("string(./host/@port)", ctxt);
-    if (port) {
-        if (virStrToLong_i(port, NULL, 10, &source->host.port) < 0) {
-            virStorageReportError(VIR_ERR_XML_ERROR,
-                                  _("Invalid port number: %s"),
-                                  port);
+    source->nhost = virXPathNodeSet("./host", ctxt, &nodeset);
+
+    if (source->nhost) {
+        if (VIR_ALLOC_N(source->hosts, source->nhost) < 0) {
+            virReportOOMError();
             goto cleanup;
         }
-    }
 
+        for (i = 0 ; i < source->nhost ; i++) {
+            char *name = virXMLPropString(nodeset[i], "name");
+            if (name == NULL) {
+                virStorageReportError(VIR_ERR_XML_ERROR,
+                        "%s", _("missing storage pool host name"));
+                goto cleanup;
+            }
+            source->hosts[i].name = name;
+            if(i == 0 && source->nhost == 1)
+                source->host.name = name;
+
+            port = virXMLPropString(nodeset[i], "port");
+            if (port) {
+                if (virStrToLong_i(port, NULL, 10, &source->hosts[i].port) < 0) {
+                    virStorageReportError(VIR_ERR_XML_ERROR,
+                                          _("Invalid port number: %s"),
+                                          port);
+                    goto cleanup;
+                } else {
+                    if (i == 0 && source->nhost == 1)
+                        virStrToLong_i(port, NULL, 10, &source->host.port);
+                }
+            }
+        }
+    }
 
     source->initiator.iqn = virXPathString("string(./initiator/iqn/@name)", ctxt);
 
@@ -478,6 +548,8 @@ virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
     } else {
         if (STREQ(authType, "chap")) {
             source->authType = VIR_STORAGE_POOL_AUTH_CHAP;
+        } else if (STREQ(authType, "ceph")) {
+            source->authType = VIR_STORAGE_POOL_AUTH_CEPHX;
         } else {
             virStorageReportError(VIR_ERR_XML_ERROR,
                                   _("unknown auth type '%s'"),
@@ -491,6 +563,11 @@ virStoragePoolDefParseSource(xmlXPathContextPtr ctxt,
             goto cleanup;
     }
 
+    if (source->authType == VIR_STORAGE_POOL_AUTH_CEPHX) {
+        if (virStoragePoolDefParseAuthCephx(ctxt, &source->auth.cephx) < 0)
+            goto cleanup;
+    }
+
     source->vendor = virXPathString("string(./vendor/@name)", ctxt);
     source->product = virXPathString("string(./product/@name)", ctxt);
 
@@ -682,6 +759,15 @@ virStoragePoolDefParseXML(xmlXPathContextPtr ctxt) {
         }
     }
 
+    if (options->flags & VIR_STORAGE_POOL_SOURCE_NETWORK) {
+        if (!ret->source.host.name && ret->source.nhost < 1) {
+            virStorageReportError(VIR_ERR_XML_ERROR,
+                                  "%s",
+                                  _("missing storage pool source network host name"));
+            goto cleanup;
+        }
+    }
+
     if (options->flags & VIR_STORAGE_POOL_SOURCE_DIR) {
         if (!ret->source.dir) {
             virStorageReportError(VIR_ERR_XML_ERROR,
@@ -717,20 +803,22 @@ virStoragePoolDefParseXML(xmlXPathContextPtr ctxt) {
         }
     }
 
-    if ((tmppath = virXPathString("string(./target/path)", ctxt)) == NULL) {
-        virStorageReportError(VIR_ERR_XML_ERROR,
-                              "%s", _("missing storage pool target path"));
-        goto cleanup;
-    }
-    ret->target.path = virFileSanitizePath(tmppath);
-    VIR_FREE(tmppath);
-    if (!ret->target.path)
-        goto cleanup;
-
+    /* When we are working with a virtual disk we can skip the target path and permissions */
+    if (!(options->flags & VIR_STORAGE_POOL_SOURCE_NETWORK)) {
+        if ((tmppath = virXPathString("string(./target/path)", ctxt)) == NULL) {
+            virStorageReportError(VIR_ERR_XML_ERROR,
+                                  "%s", _("missing storage pool target path"));
+            goto cleanup;
+        }
+        ret->target.path = virFileSanitizePath(tmppath);
+        VIR_FREE(tmppath);
+        if (!ret->target.path)
+            goto cleanup;
 
-    if (virStorageDefParsePerms(ctxt, &ret->target.perms,
-                                "./target/permissions", 0700) < 0)
-        goto cleanup;
+        if (virStorageDefParsePerms(ctxt, &ret->target.perms,
+                                    "./target/permissions", 0700) < 0)
+            goto cleanup;
+    }
 
     return ret;
 
@@ -800,12 +888,15 @@ virStoragePoolSourceFormat(virBufferPtr buf,
     int i, j;
 
     virBufferAddLit(buf,"  <source>\n");
-    if ((options->flags & VIR_STORAGE_POOL_SOURCE_HOST) &&
-        src->host.name) {
-        virBufferAsprintf(buf, "    <host name='%s'", src->host.name);
-        if (src->host.port)
-            virBufferAsprintf(buf, " port='%d'", src->host.port);
-        virBufferAddLit(buf, "/>\n");
+    if ((options->flags & VIR_STORAGE_POOL_SOURCE_HOST ||
+        options->flags & VIR_STORAGE_POOL_SOURCE_NETWORK) &&
+        src->nhost) {
+        for (i = 0; i < src->nhost; i++) {
+            virBufferAsprintf(buf, "    <host name='%s'", src->hosts[i].name);
+            if (src->hosts[i].port)
+                virBufferAsprintf(buf, " port='%d'", src->hosts[i].port);
+            virBufferAddLit(buf, "/>\n");
+        }
     }
 
     if ((options->flags & VIR_STORAGE_POOL_SOURCE_DEVICE) &&
@@ -860,6 +951,23 @@ virStoragePoolSourceFormat(virBufferPtr buf,
                           src->auth.chap.login,
                           src->auth.chap.passwd);
 
+    if (src->authType == VIR_STORAGE_POOL_AUTH_CEPHX) {
+        virBufferAsprintf(buf,"    <auth username='%s' type='ceph'>\n",
+                          src->auth.cephx.username);
+
+        virBufferAsprintf(buf,"      %s", "<secret");
+        if (src->auth.cephx.secret.uuid != NULL) {
+            virBufferAsprintf(buf," uuid='%s'", src->auth.cephx.secret.uuid);
+        }
+
+        if (src->auth.cephx.secret.usage != NULL) {
+            virBufferAsprintf(buf," usage='%s'", src->auth.cephx.secret.usage);
+        }
+        virBufferAsprintf(buf,"%s", "/>\n");
+
+        virBufferAsprintf(buf,"    %s", "</auth>\n");
+    }
+
     if (src->vendor != NULL) {
         virBufferEscapeString(buf,"    <vendor name='%s'/>\n", src->vendor);
     }
@@ -907,25 +1015,28 @@ virStoragePoolDefFormat(virStoragePoolDefPtr def) {
     if (virStoragePoolSourceFormat(&buf, options, &def->source) < 0)
         goto cleanup;
 
-    virBufferAddLit(&buf,"  <target>\n");
+    /* RBD devices are no local block devs nor files, so it doesn't have a target */
+    if (def->type != VIR_STORAGE_POOL_RBD) {
+        virBufferAddLit(&buf,"  <target>\n");
 
-    if (def->target.path)
-        virBufferAsprintf(&buf,"    <path>%s</path>\n", def->target.path);
+        if (def->target.path)
+            virBufferAsprintf(&buf,"    <path>%s</path>\n", def->target.path);
 
-    virBufferAddLit(&buf,"    <permissions>\n");
-    virBufferAsprintf(&buf,"      <mode>0%o</mode>\n",
-                      def->target.perms.mode);
-    virBufferAsprintf(&buf,"      <owner>%d</owner>\n",
-                      def->target.perms.uid);
-    virBufferAsprintf(&buf,"      <group>%d</group>\n",
-                      def->target.perms.gid);
+        virBufferAddLit(&buf,"    <permissions>\n");
+        virBufferAsprintf(&buf,"      <mode>0%o</mode>\n",
+                        def->target.perms.mode);
+        virBufferAsprintf(&buf,"      <owner>%d</owner>\n",
+                        def->target.perms.uid);
+        virBufferAsprintf(&buf,"      <group>%d</group>\n",
+                        def->target.perms.gid);
 
-    if (def->target.perms.label)
-        virBufferAsprintf(&buf,"      <label>%s</label>\n",
-                          def->target.perms.label);
+        if (def->target.perms.label)
+            virBufferAsprintf(&buf,"      <label>%s</label>\n",
+                            def->target.perms.label);
 
-    virBufferAddLit(&buf,"    </permissions>\n");
-    virBufferAddLit(&buf,"  </target>\n");
+        virBufferAddLit(&buf,"    </permissions>\n");
+        virBufferAddLit(&buf,"  </target>\n");
+    }
     virBufferAddLit(&buf,"</pool>\n");
 
     if (virBufferError(&buf))
diff --git a/src/conf/storage_conf.h b/src/conf/storage_conf.h
index 1ef9295..6b91ca5 100644
--- a/src/conf/storage_conf.h
+++ b/src/conf/storage_conf.h
@@ -120,6 +120,7 @@ enum virStoragePoolType {
     VIR_STORAGE_POOL_ISCSI,    /* iSCSI targets */
     VIR_STORAGE_POOL_SCSI,     /* SCSI HBA */
     VIR_STORAGE_POOL_MPATH,    /* Multipath devices */
+	VIR_STORAGE_POOL_RBD,      /* RADOS Block Device */
 
     VIR_STORAGE_POOL_LAST,
 };
@@ -137,6 +138,7 @@ enum virStoragePoolDeviceType {
 enum virStoragePoolAuthType {
     VIR_STORAGE_POOL_AUTH_NONE,
     VIR_STORAGE_POOL_AUTH_CHAP,
+    VIR_STORAGE_POOL_AUTH_CEPHX,
 };
 
 typedef struct _virStoragePoolAuthChap virStoragePoolAuthChap;
@@ -146,6 +148,15 @@ struct _virStoragePoolAuthChap {
     char *passwd;
 };
 
+typedef struct _virStoragePoolAuthCephx virStoragePoolAuthCephx;
+typedef virStoragePoolAuthCephx *virStoragePoolAuthCephxPtr;
+struct _virStoragePoolAuthCephx {
+    char *username;
+    struct {
+            char *uuid;
+            char *usage;
+    } secret;
+};
 
 /*
  * For remote pools, info on how to reach the host
@@ -215,6 +226,10 @@ struct _virStoragePoolSource {
     /* An optional host */
     virStoragePoolSourceHost host;
 
+    /* Or multiple hosts */
+    int nhost;
+    virStoragePoolSourceHostPtr hosts;
+
     /* And either one or more devices ... */
     int ndevice;
     virStoragePoolSourceDevicePtr devices;
@@ -234,6 +249,7 @@ struct _virStoragePoolSource {
     int authType;       /* virStoragePoolAuthType */
     union {
         virStoragePoolAuthChap chap;
+        virStoragePoolAuthCephx cephx;
     } auth;
 
     /* Vendor of the source */
diff --git a/src/storage/storage_backend.c b/src/storage/storage_backend.c
index caac2f8..e2e9b51 100644
--- a/src/storage/storage_backend.c
+++ b/src/storage/storage_backend.c
@@ -77,6 +77,9 @@
 #if WITH_STORAGE_DIR
 # include "storage_backend_fs.h"
 #endif
+#if WITH_STORAGE_RBD
+# include "storage_backend_rbd.h"
+#endif
 
 #define VIR_FROM_THIS VIR_FROM_STORAGE
 
@@ -103,6 +106,9 @@ static virStorageBackendPtr backends[] = {
 #if WITH_STORAGE_DISK
     &virStorageBackendDisk,
 #endif
+#if WITH_STORAGE_RBD
+    &virStorageBackendRBD,
+#endif
     NULL
 };
 
diff --git a/src/storage/storage_backend_rbd.c b/src/storage/storage_backend_rbd.c
new file mode 100644
index 0000000..056059a
--- /dev/null
+++ b/src/storage/storage_backend_rbd.c
@@ -0,0 +1,465 @@
+/*
+ * storage_backend_rbd.c: storage backend for RBD (RADOS Block Device) handling
+ *
+ * Copyright (C) 2012 Wido den Hollander
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Wido den Hollander <wido at widodh.nl>
+ */
+
+#include <config.h>
+
+#include "virterror_internal.h"
+#include "storage_backend_rbd.h"
+#include "storage_conf.h"
+#include "util.h"
+#include "memory.h"
+#include "logging.h"
+#include "base64.h"
+#include "rados/librados.h"
+#include "rbd/librbd.h"
+
+#define VIR_FROM_THIS VIR_FROM_STORAGE
+
+struct _virStorageBackendRBDState {
+    rados_t cluster;
+    rados_ioctx_t ioctx;
+    time_t starttime;
+};
+
+typedef struct _virStorageBackendRBDState virStorageBackendRBDState;
+typedef virStorageBackendRBDState virStorageBackendRBDStatePtr;
+
+static int virStorageBackendRBDOpenRADOSConn(virStorageBackendRBDStatePtr *ptr,
+                                             virConnectPtr conn,
+                                             virStoragePoolObjPtr pool)
+{
+    int ret = -1;
+    unsigned char *secret_value;
+    size_t secret_value_size;
+    char *rados_key;
+    virBuffer mon_host = VIR_BUFFER_INITIALIZER;
+    virSecretPtr secret = NULL;
+
+    VIR_DEBUG("Found Cephx username: %s",
+              pool->def->source.auth.cephx.username);
+
+    if (pool->def->source.auth.cephx.username != NULL) {
+        VIR_DEBUG("Using cephx authorization");
+        if (rados_create(&ptr->cluster,
+            pool->def->source.auth.cephx.username) < 0) {
+            virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("failed to initialize RADOS"));
+            goto cleanup;
+        }
+
+        if (pool->def->source.auth.cephx.secret.uuid != NULL) {
+            VIR_DEBUG("Looking up secret by UUID: %s",
+                      pool->def->source.auth.cephx.secret.uuid);
+            secret = virSecretLookupByUUIDString(conn,
+                                                 pool->def->source.auth.cephx.secret.uuid);
+        }
+
+        if (pool->def->source.auth.cephx.secret.usage != NULL) {
+            VIR_DEBUG("Looking up secret by usage: %s",
+                      pool->def->source.auth.cephx.secret.usage);
+            secret = virSecretLookupByUsage(conn, VIR_SECRET_USAGE_TYPE_CEPH,
+                                            pool->def->source.auth.cephx.secret.usage);
+        }
+
+        if (secret == NULL) {
+            virStorageReportError(VIR_ERR_NO_SECRET,
+                                  _("failed to find the secret"));
+            goto cleanup;
+        }
+
+        secret_value = virSecretGetValue(secret, &secret_value_size, 0);
+        base64_encode_alloc((char *)secret_value,
+                            secret_value_size, &rados_key);
+        memset(secret_value, 0, secret_value_size);
+
+        if (rados_key == NULL) {
+            virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("failed to decode the RADOS key"));
+            goto cleanup;
+        }
+
+        VIR_DEBUG("Found cephx key: %s", rados_key);
+        if (rados_conf_set(ptr->cluster, "key", rados_key) < 0) {
+            virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("failed to set RADOS option: %s"),
+                                  "rados_key");
+            goto cleanup;
+        }
+
+        memset(rados_key, 0, strlen(rados_key));
+
+        if (rados_conf_set(ptr->cluster, "auth_supported", "cephx") < 0) {
+            virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("failed to set RADOS option: %s"),
+                                  "auth_supported");
+            goto cleanup;
+        }
+    } else {
+        VIR_DEBUG("Not using cephx authorization");
+        if (rados_conf_set(ptr->cluster, "auth_supported", "none") < 0) {
+            virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("failed to set RADOS option: %s"),
+                                  "auth_supported");
+            goto cleanup;
+        }
+        if (rados_create(&ptr->cluster, NULL) < 0) {
+            virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("failed to create the RADOS cluster"));
+            goto cleanup;
+        }
+    }
+
+    VIR_DEBUG("Found %d RADOS cluster monitors in the pool configuration",
+              pool->def->source.nhost); 
+
+    int i;
+    for (i = 0; i < pool->def->source.nhost; i++) {
+        if (pool->def->source.hosts[i].name != NULL &&
+            !pool->def->source.hosts[i].port) {
+            virBufferAsprintf(&mon_host, "%s:6789,",
+                              pool->def->source.hosts[i].name);
+        } else if (pool->def->source.hosts[i].name != NULL &&
+            pool->def->source.hosts[i].port) {
+            virBufferAsprintf(&mon_host, "%s:%d,",
+                              pool->def->source.hosts[i].name,
+                              pool->def->source.hosts[i].port);
+        } else {
+            virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                                  _("received malformed monitor, check the XML definition"));
+        }
+    }
+
+    char *mon_buff = virBufferContentAndReset(&mon_host);
+    VIR_DEBUG("RADOS mon_host has been set to: %s", mon_buff);
+    if (rados_conf_set(ptr->cluster, "mon_host", mon_buff) < 0) {
+       virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                             _("failed to set RADOS option: %s"),
+                             "mon_host");
+        goto cleanup;
+    }
+
+    ptr->starttime = time(0);
+    if (rados_connect(ptr->cluster) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                              _("failed to connect to the RADOS monitor on: %s"),
+                              virBufferContentAndReset(&mon_host));
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(secret_value);
+    VIR_FREE(rados_key);
+    virSecretFree(secret);
+    virBufferFreeAndReset(&mon_host);
+    return ret;
+}
+
+static int virStorageBackendRBDCloseRADOSConn(virStorageBackendRBDStatePtr ptr)
+{
+    int ret = 0;
+
+    if (ptr.ioctx != NULL) {
+        VIR_DEBUG("Closing RADOS IoCTX");
+        rados_ioctx_destroy(ptr.ioctx);
+        ret = -1;
+    }
+
+    if (ptr.cluster != NULL) {
+        VIR_DEBUG("Closing RADOS connection");
+        rados_shutdown(ptr.cluster);
+        ret = -2;
+    }
+
+    time_t runtime = time(0) - ptr.starttime;
+    VIR_DEBUG("RADOS connection existed for %ld seconds", runtime);
+
+    return ret;
+}
+
+static int volStorageBackendRBDRefreshVolInfo(virStorageVolDefPtr vol,
+                                              virStoragePoolObjPtr pool,
+                                              virStorageBackendRBDStatePtr ptr)
+{
+    int ret = -1;
+    rbd_image_t image;
+    if (rbd_open(ptr.ioctx, vol->name, &image, NULL) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                              _("failed to open the RBD image '%s'"),
+                              vol->name);
+        goto cleanup;
+    }
+
+    rbd_image_info_t info;
+    if (rbd_stat(image, &info, sizeof(info)) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                              _("failed to stat the RBD image"));
+        goto cleanup;
+    }
+
+    VIR_DEBUG("Refreshed RBD image %s/%s (size: %llu obj_size: %llu num_objs: %llu)",
+              pool->def->source.name, vol->name, (unsigned long long)info.size,
+              (unsigned long long)info.obj_size,
+              (unsigned long long)info.num_objs);
+
+    vol->capacity = info.size;
+    vol->allocation = info.obj_size * info.num_objs;
+    vol->type = VIR_STORAGE_VOL_NETWORK;
+
+    VIR_FREE(vol->target.path);
+    if (virAsprintf(&vol->target.path, "rbd:%s/%s",
+                    pool->def->source.name,
+                    vol->name) == -1) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    VIR_FREE(vol->key);
+    if (virAsprintf(&vol->key, "%s/%s",
+                    pool->def->source.name,
+                    vol->name) == -1) {
+        virReportOOMError();
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    rbd_close(image);
+    return ret;
+}
+
+static int virStorageBackendRBDRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED,
+                                           virStoragePoolObjPtr pool)
+{
+    size_t max_size = 1024;
+    int ret = -1;
+    virStorageBackendRBDStatePtr ptr;
+    ptr.cluster = NULL;
+    ptr.ioctx = NULL;
+
+    if (virStorageBackendRBDOpenRADOSConn(&ptr, conn, pool) < 0) {
+        goto cleanup;
+    }
+
+    if (rados_ioctx_create(ptr.cluster,
+        pool->def->source.name, &ptr.ioctx) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                              _("failed to create the RBD IoCTX. Does the pool '%s' exist?"),
+                              pool->def->source.name);
+        goto cleanup;
+    }
+
+    struct rados_cluster_stat_t stat;
+    if (rados_cluster_stat(ptr.cluster, &stat) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR, "%s",
+                              _("failed to stat the RADOS cluster"));
+        goto cleanup;
+    }
+
+    struct rados_pool_stat_t poolstat;
+    if (rados_ioctx_pool_stat(ptr.ioctx, &poolstat) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                              _("failed to stat the RADOS pool '%s'"),
+                              pool->def->source.name);
+        goto cleanup;
+    }
+
+    pool->def->capacity = stat.kb * 1024;
+    pool->def->available = stat.kb_avail * 1024;
+    pool->def->allocation = poolstat.num_bytes;
+
+    int num_images, i;
+    char *names, *name = NULL;
+
+    if (VIR_ALLOC_N(names, 1024) < 0)
+        goto cleanup;
+
+    int len = rbd_list(ptr.ioctx, names, &max_size);
+
+    for (i = 0, num_images = 0, name = names; name < names + len; i++) {
+
+        if (VIR_REALLOC_N(pool->volumes.objs, pool->volumes.count + 1) < 0) {
+            virStoragePoolObjClearVols(pool);
+            virReportOOMError();
+            goto cleanup;
+        }
+
+        virStorageVolDefPtr vol;
+        if (VIR_ALLOC(vol) < 0)
+            goto cleanup;
+
+        vol->name = strdup(name);
+        if (vol->name == NULL)
+            goto cleanup;
+        name += strlen(name) + 1;
+
+        if (volStorageBackendRBDRefreshVolInfo(vol, pool, ptr) < 0)
+            goto cleanup;
+
+        pool->volumes.objs[pool->volumes.count++] = vol;
+    }
+
+    VIR_DEBUG("Refreshed RBD pool %s (kb: %llu kb_avail: %llu num_bytes: %llu num_images: %d)",
+              pool->def->source.name, (unsigned long long)stat.kb,
+              (unsigned long long)stat.kb_avail,
+              (unsigned long long)poolstat.num_bytes, pool->volumes.count);
+
+    ret = 0;
+
+cleanup:
+    VIR_FREE(names);
+    virStorageBackendRBDCloseRADOSConn(ptr);
+    return ret;
+}
+
+static int virStorageBackendRBDDeleteVol(virConnectPtr conn,
+                                         virStoragePoolObjPtr pool,
+                                         virStorageVolDefPtr vol,
+                                         unsigned int flags)
+{
+    int ret = -1;
+    virStorageBackendRBDStatePtr ptr;
+    ptr.cluster = NULL;
+    ptr.ioctx = NULL;
+
+    VIR_DEBUG("Removing RBD image %s/%s", pool->def->source.name, vol->name);
+
+    if (virStorageBackendRBDOpenRADOSConn(&ptr, conn, pool) < 0) {
+        goto cleanup;
+    }
+
+    if (rados_ioctx_create(ptr.cluster,
+        pool->def->source.name, &ptr.ioctx) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                              _("failed to create the RBD IoCTX. Does the pool '%s' exist?"),
+                              pool->def->source.name);
+        goto cleanup;
+    }
+
+    if (rbd_remove(ptr.ioctx, vol->name) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                              _("failed to remove volume '%s/%s'"),
+                              pool->def->source.name,
+                              vol->name);
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    virStorageBackendRBDCloseRADOSConn(ptr);
+    return ret;
+}
+
+static int virStorageBackendRBDCreateVol(virConnectPtr conn,
+                                         virStoragePoolObjPtr pool,
+                                         virStorageVolDefPtr vol)
+{
+    virStorageBackendRBDStatePtr ptr;
+    ptr.cluster = NULL;
+    ptr.ioctx = NULL;
+    int order = 0;
+    int ret = -1;
+
+    VIR_DEBUG("Creating RBD image %s/%s with size %llu",
+              pool->def->source.name,
+              vol->name, vol->capacity);
+
+    if (virStorageBackendRBDOpenRADOSConn(&ptr, conn, pool) < 0) {
+        goto cleanup;
+    }
+
+    if (rados_ioctx_create(ptr.cluster,
+        pool->def->source.name,&ptr.ioctx) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("failed to create the RBD IoCTX. Does the pool '%s' exist?"),
+                               pool->def->source.name);
+        goto cleanup;
+    }
+
+    if (vol->target.encryption != NULL) {
+        virStorageReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+                              _("storage pool does not support encrypted volumes"));
+        goto cleanup;
+    }
+
+    if (rbd_create(ptr.ioctx, vol->name, vol->capacity, &order) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                              _("failed to create volume '%s/%s'"),
+                              pool->def->source.name,
+                              vol->name);
+        goto cleanup;
+    }
+
+    if (volStorageBackendRBDRefreshVolInfo(vol, pool, ptr) < 0) {
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    virStorageBackendRBDCloseRADOSConn(ptr);
+    return ret;
+}
+
+static int virStorageBackendRBDRefreshVol(virConnectPtr conn,
+                                          virStoragePoolObjPtr pool ATTRIBUTE_UNUSED,
+                                          virStorageVolDefPtr vol)
+{
+    virStorageBackendRBDStatePtr ptr;
+    ptr.cluster = NULL;
+    ptr.ioctx = NULL;
+    int ret = -1;
+
+    if (virStorageBackendRBDOpenRADOSConn(&ptr, conn, pool) < 0) {
+        goto cleanup;
+    }
+
+    if (rados_ioctx_create(ptr.cluster,
+        pool->def->source.name, &ptr.ioctx) < 0) {
+        virStorageReportError(VIR_ERR_INTERNAL_ERROR,
+                               _("failed to create the RBD IoCTX. Does the pool '%s' exist?"),
+                               pool->def->source.name);
+        goto cleanup;
+    }
+
+    if (volStorageBackendRBDRefreshVolInfo(vol, pool, ptr) < 0) {
+        goto cleanup;
+    }
+
+    ret = 0;
+
+cleanup:
+    virStorageBackendRBDCloseRADOSConn(ptr);
+    return ret;
+}
+
+virStorageBackend virStorageBackendRBD = {
+    .type = VIR_STORAGE_POOL_RBD,
+
+    .refreshPool = virStorageBackendRBDRefreshPool,
+    .createVol = virStorageBackendRBDCreateVol,
+    .refreshVol = virStorageBackendRBDRefreshVol,
+    .deleteVol = virStorageBackendRBDDeleteVol,
+};
diff --git a/src/storage/storage_backend_rbd.h b/src/storage/storage_backend_rbd.h
new file mode 100644
index 0000000..2ae2513
--- /dev/null
+++ b/src/storage/storage_backend_rbd.h
@@ -0,0 +1,30 @@
+/*
+ * storage_backend_rbd.h: storage backend for RBD (RADOS Block Device) handling
+ *
+ * Copyright (C) 2012 Wido den Hollander
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
+ *
+ * Author: Wido den Hollander <wido at widodh.nl>
+ */
+
+#ifndef __VIR_STORAGE_BACKEND_RBD_H__
+# define __VIR_STORAGE_BACKEND_RBD_H__
+
+# include "storage_backend.h"
+
+extern virStorageBackend virStorageBackendRBD;
+
+#endif /* __VIR_STORAGE_BACKEND_RBD_H__ */
diff --git a/tests/storagepoolxml2xmlin/pool-rbd.xml b/tests/storagepoolxml2xmlin/pool-rbd.xml
new file mode 100644
index 0000000..c9d4790
--- /dev/null
+++ b/tests/storagepoolxml2xmlin/pool-rbd.xml
@@ -0,0 +1,11 @@
+<pool type='rbd'>
+  <name>ceph</name>
+  <source>
+    <name>rbd</name>
+    <host name='localhost' port='6789'/>
+    <host name='localhost' port='6790'/>
+    <auth username='admin' type='ceph'>
+	<secret uuid='2ec115d7-3a88-3ceb-bc12-0ac909a6fd87' usage='admin'/>
+    </auth>
+  </source>
+</pool>
diff --git a/tests/storagepoolxml2xmlout/pool-rbd.xml b/tests/storagepoolxml2xmlout/pool-rbd.xml
new file mode 100644
index 0000000..fa7fb34
--- /dev/null
+++ b/tests/storagepoolxml2xmlout/pool-rbd.xml
@@ -0,0 +1,15 @@
+<pool type='rbd'>
+  <name>ceph</name>
+  <uuid>47c1faee-0207-e741-f5ae-d9b019b98fe2</uuid>
+  <capacity unit='bytes'>0</capacity>
+  <allocation unit='bytes'>0</allocation>
+  <available unit='bytes'>0</available>
+  <source>
+    <host name='localhost' port='6789'/>
+    <host name='localhost' port='6790'/>
+    <name>rbd</name>
+    <auth username='admin' type='ceph'>
+      <secret uuid='2ec115d7-3a88-3ceb-bc12-0ac909a6fd87' usage='admin'/>
+    </auth>
+  </source>
+</pool>
diff --git a/tools/virsh.c b/tools/virsh.c
index 8ee25c3..632c75e 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -11894,6 +11894,10 @@ cmdVolInfo(vshControl *ctl, const vshCmd *cmd)
             vshPrint(ctl, "%-15s %s\n", _("Type:"), _("dir"));
             break;
 
+        case VIR_STORAGE_VOL_NETWORK:
+            vshPrint(ctl, "%-15s %s\n", _("Type:"), _("network"));
+            break;
+
         default:
             vshPrint(ctl, "%-15s %s\n", _("Type:"), _("unknown"));
         }
@@ -19852,6 +19856,9 @@ vshShowVersion(vshControl *ctl ATTRIBUTE_UNUSED)
 #ifdef WITH_STORAGE_LVM
     vshPrint(ctl, " LVM");
 #endif
+#ifdef WITH_STORAGE_RBD
+    vshPrint(ctl, " RBD");
+#endif
     vshPrint(ctl, "\n");
 
     vshPrint(ctl, "%s", _(" Miscellaneous:"));
-- 
1.7.0.4




More information about the libvir-list mailing list