|
|
6ae9ed |
From 296915a9c0a8de38ed83203f21811ef19465d69e Mon Sep 17 00:00:00 2001
|
|
|
6ae9ed |
Message-Id: <296915a9c0a8de38ed83203f21811ef19465d69e@dist-git>
|
|
|
6ae9ed |
From: Peter Krempa <pkrempa@redhat.com>
|
|
|
6ae9ed |
Date: Tue, 2 Aug 2016 13:41:48 +0200
|
|
|
6ae9ed |
Subject: [PATCH] util: storage: Add parser for qemu's json backing
|
|
|
6ae9ed |
pseudo-protocol
|
|
|
6ae9ed |
|
|
|
6ae9ed |
Add a modular parser that will allow to parse 'json' backing definitions
|
|
|
6ae9ed |
that are supported by qemu. The initial implementation adds support for
|
|
|
6ae9ed |
the 'file' driver.
|
|
|
6ae9ed |
|
|
|
6ae9ed |
Due to the approach qemu took to implement the JSON backing strings it's
|
|
|
6ae9ed |
possible to specify them in two approaches.
|
|
|
6ae9ed |
|
|
|
6ae9ed |
The object approach:
|
|
|
6ae9ed |
json:{ "file" : { "driver":"file",
|
|
|
6ae9ed |
"filename":"/path/to/file"
|
|
|
6ae9ed |
}
|
|
|
6ae9ed |
}
|
|
|
6ae9ed |
|
|
|
6ae9ed |
And a partially flattened approach:
|
|
|
6ae9ed |
json:{"file.driver":"file"
|
|
|
6ae9ed |
"file.filename":"/path/to/file"
|
|
|
6ae9ed |
}
|
|
|
6ae9ed |
|
|
|
6ae9ed |
Both of the above are supported by qemu and by the code added in this
|
|
|
6ae9ed |
commit. The current implementation de-flattens the first level ('file.')
|
|
|
6ae9ed |
if possible and required. Other handling may be added later but
|
|
|
6ae9ed |
currently only one level was possible anyways.
|
|
|
6ae9ed |
|
|
|
6ae9ed |
(cherry picked from commit e91f767c7433d9216c741f441196cd67c1934375)
|
|
|
6ae9ed |
https://bugzilla.redhat.com/show_bug.cgi?id=1134878 [JSON backing]
|
|
|
6ae9ed |
https://bugzilla.redhat.com/show_bug.cgi?id=1247521 [gluster multi-host]
|
|
|
6ae9ed |
---
|
|
|
6ae9ed |
src/util/virstoragefile.c | 161 ++++++++++++++++++++++++++++++++++++++++++++--
|
|
|
6ae9ed |
tests/virstoragetest.c | 15 +++++
|
|
|
6ae9ed |
2 files changed, 169 insertions(+), 7 deletions(-)
|
|
|
6ae9ed |
|
|
|
6ae9ed |
diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c
|
|
|
6ae9ed |
index aa5f302..25f363d 100644
|
|
|
6ae9ed |
--- a/src/util/virstoragefile.c
|
|
|
6ae9ed |
+++ b/src/util/virstoragefile.c
|
|
|
6ae9ed |
@@ -43,6 +43,7 @@
|
|
|
6ae9ed |
#include "viruri.h"
|
|
|
6ae9ed |
#include "dirname.h"
|
|
|
6ae9ed |
#include "virbuffer.h"
|
|
|
6ae9ed |
+#include "virjson.h"
|
|
|
6ae9ed |
|
|
|
6ae9ed |
#define VIR_FROM_THIS VIR_FROM_STORAGE
|
|
|
6ae9ed |
|
|
|
6ae9ed |
@@ -2523,10 +2524,154 @@ virStorageSourceParseBackingColon(virStorageSourcePtr src,
|
|
|
6ae9ed |
}
|
|
|
6ae9ed |
|
|
|
6ae9ed |
|
|
|
6ae9ed |
+static int
|
|
|
6ae9ed |
+virStorageSourceParseBackingJSONPath(virStorageSourcePtr src,
|
|
|
6ae9ed |
+ virJSONValuePtr json,
|
|
|
6ae9ed |
+ int type)
|
|
|
6ae9ed |
+{
|
|
|
6ae9ed |
+ const char *path;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (!(path = virJSONValueObjectGetString(json, "filename"))) {
|
|
|
6ae9ed |
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
|
6ae9ed |
+ _("missing 'filename' field in JSON backing volume "
|
|
|
6ae9ed |
+ "definition"));
|
|
|
6ae9ed |
+ return -1;
|
|
|
6ae9ed |
+ }
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (VIR_STRDUP(src->path, path) < 0)
|
|
|
6ae9ed |
+ return -1;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ src->type = type;
|
|
|
6ae9ed |
+ return 0;
|
|
|
6ae9ed |
+}
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+struct virStorageSourceJSONDriverParser {
|
|
|
6ae9ed |
+ const char *drvname;
|
|
|
6ae9ed |
+ int (*func)(virStorageSourcePtr src, virJSONValuePtr json, int opaque);
|
|
|
6ae9ed |
+ int opaque;
|
|
|
6ae9ed |
+};
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+static const struct virStorageSourceJSONDriverParser jsonParsers[] = {
|
|
|
6ae9ed |
+ {"file", virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_FILE},
|
|
|
6ae9ed |
+};
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+static int
|
|
|
6ae9ed |
+virStorageSourceParseBackingJSONDeflattenWorker(const char *key,
|
|
|
6ae9ed |
+ const virJSONValue *value,
|
|
|
6ae9ed |
+ void *opaque)
|
|
|
6ae9ed |
+{
|
|
|
6ae9ed |
+ virJSONValuePtr retobj = opaque;
|
|
|
6ae9ed |
+ virJSONValuePtr newval = NULL;
|
|
|
6ae9ed |
+ const char *newkey;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (!(newkey = STRSKIP(key, "file."))) {
|
|
|
6ae9ed |
+ virReportError(VIR_ERR_INVALID_ARG, "%s",
|
|
|
6ae9ed |
+ _("JSON backing file syntax is neither nested nor "
|
|
|
6ae9ed |
+ "flattened"));
|
|
|
6ae9ed |
+ return -1;
|
|
|
6ae9ed |
+ }
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (!(newval = virJSONValueCopy(value)))
|
|
|
6ae9ed |
+ return -1;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (virJSONValueObjectAppend(retobj, newkey, newval) < 0) {
|
|
|
6ae9ed |
+ virJSONValueFree(newval);
|
|
|
6ae9ed |
+ return -1;
|
|
|
6ae9ed |
+ }
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ return 0;
|
|
|
6ae9ed |
+}
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+/**
|
|
|
6ae9ed |
+ * virStorageSourceParseBackingJSONDeflatten:
|
|
|
6ae9ed |
+ *
|
|
|
6ae9ed |
+ * The json: pseudo-protocol syntax in qemu allows multiple approaches to
|
|
|
6ae9ed |
+ * describe nesting of the values. This is due to the lax handling of the string
|
|
|
6ae9ed |
+ * in qemu and the fact that internally qemu is flattening the values using '.'.
|
|
|
6ae9ed |
+ *
|
|
|
6ae9ed |
+ * This allows to specify nested json strings either using nested json objects
|
|
|
6ae9ed |
+ * or prefixing object members with the parent object name followed by the dot.
|
|
|
6ae9ed |
+ *
|
|
|
6ae9ed |
+ * This function will attempt to reverse the process and provide a nested json
|
|
|
6ae9ed |
+ * hierarchy so that the parsers can be kept simple and we still can use the
|
|
|
6ae9ed |
+ * weird syntax some users might use.
|
|
|
6ae9ed |
+ *
|
|
|
6ae9ed |
+ * Currently this function will flatten out just the 'file.' prefix into a new
|
|
|
6ae9ed |
+ * tree. Any other syntax will be rejected.
|
|
|
6ae9ed |
+ */
|
|
|
6ae9ed |
+static virJSONValuePtr
|
|
|
6ae9ed |
+virStorageSourceParseBackingJSONDeflatten(virJSONValuePtr json)
|
|
|
6ae9ed |
+{
|
|
|
6ae9ed |
+ virJSONValuePtr ret;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (!(ret = virJSONValueNewObject()))
|
|
|
6ae9ed |
+ return NULL;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (virJSONValueObjectForeachKeyValue(json,
|
|
|
6ae9ed |
+ virStorageSourceParseBackingJSONDeflattenWorker,
|
|
|
6ae9ed |
+ ret) < 0) {
|
|
|
6ae9ed |
+ virJSONValueFree(ret);
|
|
|
6ae9ed |
+ return NULL;
|
|
|
6ae9ed |
+ }
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ return ret;
|
|
|
6ae9ed |
+}
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+static int
|
|
|
6ae9ed |
+virStorageSourceParseBackingJSON(virStorageSourcePtr src,
|
|
|
6ae9ed |
+ const char *json)
|
|
|
6ae9ed |
+{
|
|
|
6ae9ed |
+ virJSONValuePtr root = NULL;
|
|
|
6ae9ed |
+ virJSONValuePtr fixedroot = NULL;
|
|
|
6ae9ed |
+ virJSONValuePtr file;
|
|
|
6ae9ed |
+ const char *drvname;
|
|
|
6ae9ed |
+ size_t i;
|
|
|
6ae9ed |
+ int ret = -1;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (!(root = virJSONValueFromString(json)))
|
|
|
6ae9ed |
+ return -1;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (!(file = virJSONValueObjectGetObject(root, "file"))) {
|
|
|
6ae9ed |
+ if (!(fixedroot = virStorageSourceParseBackingJSONDeflatten(root)))
|
|
|
6ae9ed |
+ goto cleanup;
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ file = fixedroot;
|
|
|
6ae9ed |
+ }
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (!(drvname = virJSONValueObjectGetString(file, "driver"))) {
|
|
|
6ae9ed |
+ virReportError(VIR_ERR_INVALID_ARG, _("JSON backing volume defintion "
|
|
|
6ae9ed |
+ "'%s' lacks driver name"), json);
|
|
|
6ae9ed |
+ goto cleanup;
|
|
|
6ae9ed |
+ }
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ for (i = 0; i < ARRAY_CARDINALITY(jsonParsers); i++) {
|
|
|
6ae9ed |
+ if (STREQ(drvname, jsonParsers[i].drvname)) {
|
|
|
6ae9ed |
+ ret = jsonParsers[i].func(src, file, jsonParsers[i].opaque);
|
|
|
6ae9ed |
+ goto cleanup;
|
|
|
6ae9ed |
+ }
|
|
|
6ae9ed |
+ }
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
6ae9ed |
+ _("missing parser implementation for JSON backing volume "
|
|
|
6ae9ed |
+ "driver '%s'"), drvname);
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ cleanup:
|
|
|
6ae9ed |
+ virJSONValueFree(root);
|
|
|
6ae9ed |
+ virJSONValueFree(fixedroot);
|
|
|
6ae9ed |
+ return ret;
|
|
|
6ae9ed |
+}
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
virStorageSourcePtr
|
|
|
6ae9ed |
virStorageSourceNewFromBackingAbsolute(const char *path)
|
|
|
6ae9ed |
{
|
|
|
6ae9ed |
+ const char *json;
|
|
|
6ae9ed |
virStorageSourcePtr ret;
|
|
|
6ae9ed |
+ int rc;
|
|
|
6ae9ed |
|
|
|
6ae9ed |
if (VIR_ALLOC(ret) < 0)
|
|
|
6ae9ed |
return NULL;
|
|
|
6ae9ed |
@@ -2540,13 +2685,15 @@ virStorageSourceNewFromBackingAbsolute(const char *path)
|
|
|
6ae9ed |
ret->type = VIR_STORAGE_TYPE_NETWORK;
|
|
|
6ae9ed |
|
|
|
6ae9ed |
/* handle URI formatted backing stores */
|
|
|
6ae9ed |
- if (strstr(path, "://")) {
|
|
|
6ae9ed |
- if (virStorageSourceParseBackingURI(ret, path) < 0)
|
|
|
6ae9ed |
- goto error;
|
|
|
6ae9ed |
- } else {
|
|
|
6ae9ed |
- if (virStorageSourceParseBackingColon(ret, path) < 0)
|
|
|
6ae9ed |
- goto error;
|
|
|
6ae9ed |
- }
|
|
|
6ae9ed |
+ if ((json = STRSKIP(path, "json:")))
|
|
|
6ae9ed |
+ rc = virStorageSourceParseBackingJSON(ret, json);
|
|
|
6ae9ed |
+ else if (strstr(path, "://"))
|
|
|
6ae9ed |
+ rc = virStorageSourceParseBackingURI(ret, path);
|
|
|
6ae9ed |
+ else
|
|
|
6ae9ed |
+ rc = virStorageSourceParseBackingColon(ret, path);
|
|
|
6ae9ed |
+
|
|
|
6ae9ed |
+ if (rc < 0)
|
|
|
6ae9ed |
+ goto error;
|
|
|
6ae9ed |
}
|
|
|
6ae9ed |
|
|
|
6ae9ed |
return ret;
|
|
|
6ae9ed |
diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c
|
|
|
6ae9ed |
index 6016a3b..04575f2 100644
|
|
|
6ae9ed |
--- a/tests/virstoragetest.c
|
|
|
6ae9ed |
+++ b/tests/virstoragetest.c
|
|
|
6ae9ed |
@@ -1355,6 +1355,21 @@ mymain(void)
|
|
|
6ae9ed |
"<source protocol='nbd' name='blah'>\n"
|
|
|
6ae9ed |
" <host name='example.org' port='6000'/>\n"
|
|
|
6ae9ed |
"</source>\n");
|
|
|
6ae9ed |
+ TEST_BACKING_PARSE("json:", NULL);
|
|
|
6ae9ed |
+ TEST_BACKING_PARSE("json:asdgsdfg", NULL);
|
|
|
6ae9ed |
+ TEST_BACKING_PARSE("json:{}", NULL);
|
|
|
6ae9ed |
+ TEST_BACKING_PARSE("json: { \"file.driver\":\"blah\"}", NULL);
|
|
|
6ae9ed |
+ TEST_BACKING_PARSE("json:{\"file.driver\":\"file\"}", NULL);
|
|
|
6ae9ed |
+ TEST_BACKING_PARSE("json:{\"file.driver\":\"file\", "
|
|
|
6ae9ed |
+ "\"file.filename\":\"/path/to/file\"}",
|
|
|
6ae9ed |
+ "<source file='/path/to/file'/>\n");
|
|
|
6ae9ed |
+ TEST_BACKING_PARSE("json:{\"file.driver\":\"file\", "
|
|
|
6ae9ed |
+ "\"filename\":\"/path/to/file\"}", NULL);
|
|
|
6ae9ed |
+ TEST_BACKING_PARSE("json:{\"file\" : { \"driver\":\"file\","
|
|
|
6ae9ed |
+ "\"filename\":\"/path/to/file\""
|
|
|
6ae9ed |
+ "}"
|
|
|
6ae9ed |
+ "}",
|
|
|
6ae9ed |
+ "<source file='/path/to/file'/>\n");
|
|
|
6ae9ed |
|
|
|
6ae9ed |
cleanup:
|
|
|
6ae9ed |
/* Final cleanup */
|
|
|
6ae9ed |
--
|
|
|
6ae9ed |
2.9.2
|
|
|
6ae9ed |
|