c401cc
From fc0de008b808886ff8800527298d100adfcc14f4 Mon Sep 17 00:00:00 2001
c401cc
Message-Id: <fc0de008b808886ff8800527298d100adfcc14f4@dist-git>
c401cc
From: Eric Blake <eblake@redhat.com>
c401cc
Date: Wed, 26 Feb 2014 14:54:23 +0100
c401cc
Subject: [PATCH] storage: implement rudimentary glusterfs pool refresh
c401cc
c401cc
https://bugzilla.redhat.com/show_bug.cgi?id=1032370
c401cc
c401cc
Actually put gfapi to use, by allowing the creation of a gluster
c401cc
pool.  Right now, all volumes are treated as raw and directories
c401cc
are skipped; further patches will allow peering into files to
c401cc
allow for qcow2 files and backing chains, and reporting proper
c401cc
volume allocation.  This implementation was tested against Fedora
c401cc
19's glusterfs 3.4.1; it might be made simpler by requiring a
c401cc
higher minimum, and/or require more hacks to work with a lower
c401cc
minimum.
c401cc
c401cc
* src/storage/storage_backend_gluster.c
c401cc
(virStorageBackendGlusterRefreshPool): Initial implementation.
c401cc
(virStorageBackendGlusterOpen, virStorageBackendGlusterClose)
c401cc
(virStorageBackendGlusterRefreshVol): New helper functions.
c401cc
c401cc
Signed-off-by: Eric Blake <eblake@redhat.com>
c401cc
(cherry picked from commit efee1af54ad58e7147442714b2baa560814f94be)
c401cc
Signed-off-by: Jiri Denemark <jdenemar@redhat.com>
c401cc
---
c401cc
 src/storage/storage_backend_gluster.c | 249 +++++++++++++++++++++++++++++++++-
c401cc
 1 file changed, 244 insertions(+), 5 deletions(-)
c401cc
c401cc
diff --git a/src/storage/storage_backend_gluster.c b/src/storage/storage_backend_gluster.c
c401cc
index 2863c73..d57427e 100644
c401cc
--- a/src/storage/storage_backend_gluster.c
c401cc
+++ b/src/storage/storage_backend_gluster.c
c401cc
@@ -23,20 +23,259 @@
c401cc
 
c401cc
 #include <glusterfs/api/glfs.h>
c401cc
 
c401cc
-#include "virerror.h"
c401cc
 #include "storage_backend_gluster.h"
c401cc
 #include "storage_conf.h"
c401cc
+#include "viralloc.h"
c401cc
+#include "virerror.h"
c401cc
+#include "virlog.h"
c401cc
+#include "virstoragefile.h"
c401cc
+#include "virstring.h"
c401cc
+#include "viruri.h"
c401cc
 
c401cc
 #define VIR_FROM_THIS VIR_FROM_STORAGE
c401cc
 
c401cc
+struct _virStorageBackendGlusterState {
c401cc
+    glfs_t *vol;
c401cc
+
c401cc
+    /* Accept the same URIs as qemu's block/gluster.c:
c401cc
+     * gluster[+transport]://[server[:port]]/vol/[dir/]image[?socket=...] */
c401cc
+    virURI *uri;
c401cc
+
c401cc
+    char *volname; /* vol from URI, no '/' */
c401cc
+    char *dir; /* dir from URI, or "/"; always starts and ends in '/' */
c401cc
+};
c401cc
+
c401cc
+typedef struct _virStorageBackendGlusterState virStorageBackendGlusterState;
c401cc
+typedef virStorageBackendGlusterState *virStorageBackendGlusterStatePtr;
c401cc
+
c401cc
+static void
c401cc
+virStorageBackendGlusterClose(virStorageBackendGlusterStatePtr state)
c401cc
+{
c401cc
+    if (!state)
c401cc
+        return;
c401cc
+
c401cc
+    /* Yuck - glusterfs-api-3.4.1 appears to always return -1 for
c401cc
+     * glfs_fini, with errno containing random data, so there's no way
c401cc
+     * to tell if it succeeded. 3.4.2 is supposed to fix this.*/
c401cc
+    if (state->vol && glfs_fini(state->vol) < 0)
c401cc
+        VIR_DEBUG("shutdown of gluster volume %s failed with errno %d",
c401cc
+                  state->volname, errno);
c401cc
+
c401cc
+    virURIFree(state->uri);
c401cc
+    VIR_FREE(state->volname);
c401cc
+    VIR_FREE(state->dir);
c401cc
+    VIR_FREE(state);
c401cc
+}
c401cc
+
c401cc
+static virStorageBackendGlusterStatePtr
c401cc
+virStorageBackendGlusterOpen(virStoragePoolObjPtr pool)
c401cc
+{
c401cc
+    virStorageBackendGlusterStatePtr ret = NULL;
c401cc
+    const char *name = pool->def->source.name;
c401cc
+    const char *dir = pool->def->source.dir;
c401cc
+    bool trailing_slash = true;
c401cc
+
c401cc
+    /* Volume name must not contain '/'; optional path allows use of a
c401cc
+     * subdirectory within the volume name.  */
c401cc
+    if (strchr(name, '/')) {
c401cc
+        virReportError(VIR_ERR_XML_ERROR,
c401cc
+                       _("gluster pool name '%s' must not contain /"),
c401cc
+                       name);
c401cc
+        return NULL;
c401cc
+    }
c401cc
+    if (dir) {
c401cc
+        if (*dir != '/') {
c401cc
+            virReportError(VIR_ERR_XML_ERROR,
c401cc
+                           _("gluster pool path '%s' must start with /"),
c401cc
+                           dir);
c401cc
+            return NULL;
c401cc
+        }
c401cc
+        if (strchr(dir, '\0')[-1] != '/')
c401cc
+            trailing_slash = false;
c401cc
+    }
c401cc
+
c401cc
+    if (VIR_ALLOC(ret) < 0)
c401cc
+        return NULL;
c401cc
+
c401cc
+    if (VIR_STRDUP(ret->volname, name) < 0)
c401cc
+        goto error;
c401cc
+    if (virAsprintf(&ret->dir, "%s%s", dir ? dir : "/",
c401cc
+                    trailing_slash ? "" : "/") < 0)
c401cc
+        goto error;
c401cc
+
c401cc
+    /* FIXME: Currently hard-coded to tcp transport; XML needs to be
c401cc
+     * extended to allow alternate transport */
c401cc
+    if (VIR_ALLOC(ret->uri) < 0)
c401cc
+        goto error;
c401cc
+    if (VIR_STRDUP(ret->uri->scheme, "gluster") < 0)
c401cc
+        goto error;
c401cc
+    if (VIR_STRDUP(ret->uri->server, pool->def->source.hosts[0].name) < 0)
c401cc
+        goto error;
c401cc
+    if (virAsprintf(&ret->uri->path, "/%s%s", ret->volname, ret->dir) < 0)
c401cc
+        goto error;
c401cc
+    ret->uri->port = pool->def->source.hosts[0].port;
c401cc
+
c401cc
+    /* Actually connect to glfs */
c401cc
+    if (!(ret->vol = glfs_new(ret->volname))) {
c401cc
+        virReportOOMError();
c401cc
+        goto error;
c401cc
+    }
c401cc
+
c401cc
+    if (glfs_set_volfile_server(ret->vol, "tcp",
c401cc
+                                ret->uri->server, ret->uri->port) < 0 ||
c401cc
+        glfs_init(ret->vol) < 0) {
c401cc
+        char *uri = virURIFormat(ret->uri);
c401cc
+
c401cc
+        virReportSystemError(errno, _("failed to connect to %s"),
c401cc
+                             NULLSTR(uri));
c401cc
+        VIR_FREE(uri);
c401cc
+        goto error;
c401cc
+    }
c401cc
+
c401cc
+    if (glfs_chdir(ret->vol, ret->dir) < 0) {
c401cc
+        virReportSystemError(errno,
c401cc
+                             _("failed to change to directory '%s' in '%s'"),
c401cc
+                             ret->dir, ret->volname);
c401cc
+        goto error;
c401cc
+    }
c401cc
+
c401cc
+    return ret;
c401cc
+
c401cc
+error:
c401cc
+    virStorageBackendGlusterClose(ret);
c401cc
+    return NULL;
c401cc
+}
c401cc
+
c401cc
+
c401cc
+/* Populate *volptr for the given name and stat information, or leave
c401cc
+ * it NULL if the entry should be skipped (such as ".").  Return 0 on
c401cc
+ * success, -1 on failure. */
c401cc
+static int
c401cc
+virStorageBackendGlusterRefreshVol(virStorageBackendGlusterStatePtr state,
c401cc
+                                   const char *name,
c401cc
+                                   struct stat *st,
c401cc
+                                   virStorageVolDefPtr *volptr)
c401cc
+{
c401cc
+    char *tmp;
c401cc
+    int ret = -1;
c401cc
+    virStorageVolDefPtr vol = NULL;
c401cc
+
c401cc
+    *volptr = NULL;
c401cc
+
c401cc
+    /* Silently skip '.' and '..'.  */
c401cc
+    if (STREQ(name, ".") || STREQ(name, ".."))
c401cc
+        return 0;
c401cc
+    /* FIXME: support directories.  For now, silently skip them.  */
c401cc
+    if (S_ISDIR(st->st_mode))
c401cc
+        return 0;
c401cc
+
c401cc
+    if (VIR_ALLOC(vol) < 0)
c401cc
+        goto cleanup;
c401cc
+    if (VIR_STRDUP(vol->name, name) < 0)
c401cc
+        goto cleanup;
c401cc
+    if (virAsprintf(&vol->key, "%s%s%s", state->volname, state->dir,
c401cc
+                    vol->name) < 0)
c401cc
+        goto cleanup;
c401cc
+
c401cc
+    vol->type = VIR_STORAGE_VOL_NETWORK;
c401cc
+    tmp = state->uri->path;
c401cc
+    state->uri->path = vol->key;
c401cc
+    if (!(vol->target.path = virURIFormat(state->uri))) {
c401cc
+        state->uri->path = tmp;
c401cc
+        goto cleanup;
c401cc
+    }
c401cc
+    state->uri->path = tmp;
c401cc
+
c401cc
+    /* FIXME - must open files to determine if they are non-raw */
c401cc
+    vol->target.format = VIR_STORAGE_FILE_RAW;
c401cc
+    vol->capacity = vol->allocation = st->st_size;
c401cc
+
c401cc
+    *volptr = vol;
c401cc
+    vol = NULL;
c401cc
+    ret = 0;
c401cc
+cleanup:
c401cc
+    virStorageVolDefFree(vol);
c401cc
+    return ret;
c401cc
+}
c401cc
 
c401cc
 static int
c401cc
 virStorageBackendGlusterRefreshPool(virConnectPtr conn ATTRIBUTE_UNUSED,
c401cc
-                                    virStoragePoolObjPtr pool ATTRIBUTE_UNUSED)
c401cc
+                                    virStoragePoolObjPtr pool)
c401cc
 {
c401cc
-    virReportError(VIR_ERR_NO_SUPPORT, "%s",
c401cc
-                   _("gluster pool type not fully supported yet"));
c401cc
-    return -1;
c401cc
+    int ret = -1;
c401cc
+    virStorageBackendGlusterStatePtr state = NULL;
c401cc
+    struct {
c401cc
+        struct dirent ent;
c401cc
+        /* See comment below about readdir_r needing padding */
c401cc
+        char padding[MAX(1, 256 - (int) (sizeof(struct dirent)
c401cc
+                                         - offsetof(struct dirent, d_name)))];
c401cc
+    } de;
c401cc
+    struct dirent *ent;
c401cc
+    glfs_fd_t *dir = NULL;
c401cc
+    struct stat st;
c401cc
+    struct statvfs sb;
c401cc
+
c401cc
+    if (!(state = virStorageBackendGlusterOpen(pool)))
c401cc
+        goto cleanup;
c401cc
+
c401cc
+    /* Why oh why did glfs 3.4 decide to expose only readdir_r rather
c401cc
+     * than readdir?  POSIX admits that readdir_r is inherently a
c401cc
+     * flawed design, because systems are not required to define
c401cc
+     * NAME_MAX: http://austingroupbugs.net/view.php?id=696
c401cc
+     * http://womble.decadent.org.uk/readdir_r-advisory.html
c401cc
+     *
c401cc
+     * Fortunately, gluster appears to limit its underlying bricks to
c401cc
+     * only use file systems such as XFS that have a NAME_MAX of 255;
c401cc
+     * so we are currently guaranteed that if we provide 256 bytes of
c401cc
+     * tail padding, then we should have enough space to avoid buffer
c401cc
+     * overflow no matter whether the OS used d_name[], d_name[1], or
c401cc
+     * d_name[256] in its 'struct dirent'.
c401cc
+     * http://lists.gnu.org/archive/html/gluster-devel/2013-10/msg00083.html
c401cc
+     */
c401cc
+
c401cc
+    if (!(dir = glfs_opendir(state->vol, state->dir))) {
c401cc
+        virReportSystemError(errno, _("cannot open path '%s' in '%s'"),
c401cc
+                             state->dir, state->volname);
c401cc
+        goto cleanup;
c401cc
+    }
c401cc
+    while (!(errno = glfs_readdirplus_r(dir, &st, &de.ent, &ent)) && ent) {
c401cc
+        virStorageVolDefPtr vol;
c401cc
+        int okay = virStorageBackendGlusterRefreshVol(state,
c401cc
+                                                      ent->d_name, &st,
c401cc
+                                                      &vol;;
c401cc
+
c401cc
+        if (okay < 0)
c401cc
+            goto cleanup;
c401cc
+        if (vol && VIR_APPEND_ELEMENT(pool->volumes.objs, pool->volumes.count,
c401cc
+                                      vol) < 0)
c401cc
+            goto cleanup;
c401cc
+    }
c401cc
+    if (errno) {
c401cc
+        virReportSystemError(errno, _("failed to read directory '%s' in '%s'"),
c401cc
+                             state->dir, state->volname);
c401cc
+        goto cleanup;
c401cc
+    }
c401cc
+
c401cc
+    if (glfs_statvfs(state->vol, state->dir, &sb) < 0) {
c401cc
+        virReportSystemError(errno, _("cannot statvfs path '%s' in '%s'"),
c401cc
+                             state->dir, state->volname);
c401cc
+        goto cleanup;
c401cc
+    }
c401cc
+
c401cc
+    pool->def->capacity = ((unsigned long long)sb.f_frsize *
c401cc
+                           (unsigned long long)sb.f_blocks);
c401cc
+    pool->def->available = ((unsigned long long)sb.f_bfree *
c401cc
+                            (unsigned long long)sb.f_frsize);
c401cc
+    pool->def->allocation = pool->def->capacity - pool->def->available;
c401cc
+
c401cc
+    ret = 0;
c401cc
+cleanup:
c401cc
+    if (dir)
c401cc
+        glfs_closedir(dir);
c401cc
+    virStorageBackendGlusterClose(state);
c401cc
+    if (ret < 0)
c401cc
+        virStoragePoolObjClearVols(pool);
c401cc
+    return ret;
c401cc
 }
c401cc
 
c401cc
 virStorageBackend virStorageBackendGluster = {
c401cc
-- 
c401cc
1.9.0
c401cc