[libvirt] [PATCHv2 00/10] JSON pseudo-protocol support for backing chains

This part is extracted from the gluster series since Eric's comment on the original RFC triggered a rework of certain parts. This version allows to handle both flattened and hierarchical syntaxes (see patch 1/10) for details. Peter Krempa (10): util: storage: Add parser for qemu's json backing pseudo-protocol util: json: Make first argument of virJSONValueCopy const util: storage: Add support for host device backing specified via JSON util: storage: Add support for URI based backing volumes in qemu's JSON pseudo-protocol util: storage: Add json pseudo protocol support for gluster volumes util: storage: Add json pseudo protocol support for iSCSI volumes util: storage: Add JSON backing volume parser for 'nbd' protocol util: storage: Add JSON backing store parser for 'sheepdog' protocol util: storage: Add 'ssh' network storage protocol util: storage: Add JSON backing volume parser for 'ssh' protocol src/libxl/libxl_conf.c | 1 + src/qemu/qemu_command.c | 7 + src/qemu/qemu_driver.c | 3 + src/qemu/qemu_parse_command.c | 1 + src/util/virjson.c | 2 +- src/util/virjson.h | 2 +- src/util/virstoragefile.c | 447 +++++++++++++++++++++++++++++++++++++++++- src/util/virstoragefile.h | 1 + src/xenconfig/xen_xl.c | 1 + tests/virstoragetest.c | 131 +++++++++++++ 10 files changed, 586 insertions(+), 10 deletions(-) -- 2.9.0

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

It's just read. --- src/util/virjson.c | 2 +- src/util/virjson.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/util/virjson.c b/src/util/virjson.c index afc98e3..b6d9a34 100644 --- a/src/util/virjson.c +++ b/src/util/virjson.c @@ -1241,7 +1241,7 @@ virJSONValueObjectForeachKeyValue(const virJSONValue *object, virJSONValuePtr -virJSONValueCopy(virJSONValuePtr in) +virJSONValueCopy(const virJSONValue *in) { size_t i; virJSONValuePtr out = NULL; diff --git a/src/util/virjson.h b/src/util/virjson.h index a5aef39..64cae88 100644 --- a/src/util/virjson.h +++ b/src/util/virjson.h @@ -171,6 +171,6 @@ int virJSONValueObjectForeachKeyValue(const virJSONValue *object, virJSONValueObjectIteratorFunc cb, void *opaque); -virJSONValuePtr virJSONValueCopy(virJSONValuePtr in); +virJSONValuePtr virJSONValueCopy(const virJSONValue *in); #endif /* __VIR_JSON_H_ */ -- 2.9.0

JSON pseudo protocol for qemu allows to explicitly specify devices. Add convertor to the internal type. --- src/util/virstoragefile.c | 2 ++ tests/virstoragetest.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 763bec6..766ae28 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2545,6 +2545,8 @@ struct virStorageSourceJSONDriverParser { static const struct virStorageSourceJSONDriverParser jsonParsers[] = { {"file", virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_FILE}, + {"host_device", virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, + {"host_cdrom", virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, }; diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c index 04575f2..6873180 100644 --- a/tests/virstoragetest.c +++ b/tests/virstoragetest.c @@ -1370,6 +1370,12 @@ mymain(void) "}" "}", "<source file='/path/to/file'/>\n"); + TEST_BACKING_PARSE("json:{\"file.driver\":\"host_device\", " + "\"file.filename\":\"/path/to/dev\"}", + "<source dev='/path/to/dev'/>\n"); + TEST_BACKING_PARSE("json:{\"file.driver\":\"host_cdrom\", " + "\"file.filename\":\"/path/to/cdrom\"}", + "<source dev='/path/to/cdrom'/>\n"); cleanup: /* Final cleanup */ -- 2.9.0

http(s), ftp(s) and tftp use URIs for volume definitions in the JSON pseudo protocol so it's pretty straightforward to add support for them. --- src/util/virstoragefile.c | 43 +++++++++++++++++++++++++++++++++++++++++++ tests/virstoragetest.c | 15 +++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 766ae28..af79f79 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2537,6 +2537,44 @@ virStorageSourceParseBackingJSONPath(virStorageSourcePtr src, } +static int +virStorageSourceParseBackingJSONUriStr(virStorageSourcePtr src, + const char *uri, + int protocol) +{ + if (virStorageSourceParseBackingURI(src, uri) < 0) + return -1; + + if (src->protocol != protocol) { + virReportError(VIR_ERR_INVALID_ARG, + _("expected protocol '%s' but got '%s' in URI JSON volume " + "definition"), + virStorageNetProtocolTypeToString(protocol), + virStorageNetProtocolTypeToString(src->protocol)); + return -1; + } + + return 0; +} + + +static int +virStorageSourceParseBackingJSONUri(virStorageSourcePtr src, + virJSONValuePtr json, + int protocol) +{ + const char *uri; + + if (!(uri = virJSONValueObjectGetString(json, "uri"))) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing URI in JSON backing volume definition")); + return -1; + } + + return virStorageSourceParseBackingJSONUriStr(src, uri, protocol); +} + + struct virStorageSourceJSONDriverParser { const char *drvname; int (*func)(virStorageSourcePtr src, virJSONValuePtr json, int opaque); @@ -2547,6 +2585,11 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = { {"file", virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_FILE}, {"host_device", virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, {"host_cdrom", virStorageSourceParseBackingJSONPath, VIR_STORAGE_TYPE_BLOCK}, + {"http", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTP}, + {"https", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_HTTPS}, + {"ftp", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTP}, + {"ftps", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTPS}, + {"tftp", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP}, }; diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c index 6873180..f8e6e9a 100644 --- a/tests/virstoragetest.c +++ b/tests/virstoragetest.c @@ -1376,6 +1376,21 @@ mymain(void) TEST_BACKING_PARSE("json:{\"file.driver\":\"host_cdrom\", " "\"file.filename\":\"/path/to/cdrom\"}", "<source dev='/path/to/cdrom'/>\n"); + TEST_BACKING_PARSE("json:{\"file.driver\":\"http\", " + "\"file.uri\":\"http://example.com/file\"}", + "<source protocol='http' name='file'>\n" + " <host name='example.com'/>\n" + "</source>\n"); + TEST_BACKING_PARSE("json:{\"file\":{ \"driver\":\"http\"," + "\"uri\":\"http://example.com/file\"" + "}" + "}", + "<source protocol='http' name='file'>\n" + " <host name='example.com'/>\n" + "</source>\n"); + TEST_BACKING_PARSE("json:{\"file.driver\":\"ftp\", " + "\"file.uri\":\"http://example.com/file\"}", + NULL); cleanup: /* Final cleanup */ -- 2.9.0

Along with the legacy URI based syntax add support for the brand-new fully object based syntax. --- src/util/virstoragefile.c | 108 ++++++++++++++++++++++++++++++++++++++++++++++ tests/virstoragetest.c | 46 ++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index af79f79..c9fd475 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2575,6 +2575,113 @@ virStorageSourceParseBackingJSONUri(virStorageSourcePtr src, } +static int +virStorageSourceParseBackingJSONGlusterHost(virStorageNetHostDefPtr host, + virJSONValuePtr json) +{ + const char *type = virJSONValueObjectGetString(json, "type"); + const char *hostname = virJSONValueObjectGetString(json, "host"); + const char *port = virJSONValueObjectGetString(json, "port"); + const char *socket = virJSONValueObjectGetString(json, "socket"); + int transport; + + if ((transport = virStorageNetHostTransportTypeFromString(type)) < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, + _("unknown backing store transport protocol '%s'"), type); + return -1; + } + + host->transport = transport; + + switch ((virStorageNetHostTransport) transport) { + case VIR_STORAGE_NET_HOST_TRANS_TCP: + if (!hostname) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing hostname for tcp backing server in " + "JSON backing definition for gluster volume")); + return -1; + } + + if (VIR_STRDUP(host->name, hostname) < 0 || + VIR_STRDUP(host->port, port) < 0) + return -1; + break; + + case VIR_STORAGE_NET_HOST_TRANS_UNIX: + if (!socket) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing socket path for udp backing server in " + "JSON backing definition for gluster volume")); + return -1; + } + + + if (VIR_STRDUP(host->socket, socket) < 0) + return -1; + break; + + case VIR_STORAGE_NET_HOST_TRANS_RDMA: + case VIR_STORAGE_NET_HOST_TRANS_LAST: + virReportError(VIR_ERR_INTERNAL_ERROR, + _("backing store protocol '%s' is not yet supported"), + type); + return -1; + } + + return 0; +} + + +static int +virStorageSourceParseBackingJSONGluster(virStorageSourcePtr src, + virJSONValuePtr json, + int opaque ATTRIBUTE_UNUSED) +{ + const char *uri = virJSONValueObjectGetString(json, "filename"); + const char *volume = virJSONValueObjectGetString(json, "volume"); + const char *path = virJSONValueObjectGetString(json, "path"); + virJSONValuePtr server = virJSONValueObjectGetArray(json, "server"); + size_t nservers; + size_t i; + + /* legacy URI based syntax passed via 'filename' option */ + if (uri) + return virStorageSourceParseBackingJSONUriStr(src, uri, + VIR_STORAGE_NET_PROTOCOL_GLUSTER); + + if (!volume || !path || !server) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing 'volume', 'path' or 'server' attribute in " + "JSON backing definition for gluster volume")); + return -1; + } + + if (VIR_STRDUP(src->volume, volume) < 0 || + virAsprintf(&src->path, "/%s", path) < 0) + return -1; + + nservers = virJSONValueArraySize(server); + + if (nservers < 1) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("at least 1 server is necessary in " + "JSON backing definition for gluster volume")); + } + + if (VIR_ALLOC_N(src->hosts, nservers) < 0) + return -1; + src->nhosts = nservers; + + for (i = 0; i < nservers; i++) { + if (virStorageSourceParseBackingJSONGlusterHost(src->hosts + i, + virJSONValueArrayGet(server, i)) < 0) + return -1; + } + + return 0; +} + + struct virStorageSourceJSONDriverParser { const char *drvname; int (*func)(virStorageSourcePtr src, virJSONValuePtr json, int opaque); @@ -2590,6 +2697,7 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = { {"ftp", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTP}, {"ftps", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTPS}, {"tftp", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP}, + {"gluster", virStorageSourceParseBackingJSONGluster, 0}, }; diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c index f8e6e9a..22aae47 100644 --- a/tests/virstoragetest.c +++ b/tests/virstoragetest.c @@ -1391,6 +1391,52 @@ mymain(void) TEST_BACKING_PARSE("json:{\"file.driver\":\"ftp\", " "\"file.uri\":\"http://example.com/file\"}", NULL); + TEST_BACKING_PARSE("json:{\"file.driver\":\"gluster\", " + "\"file.filename\":\"gluster://example.com/vol/file\"}", + "<source protocol='gluster' name='vol/file'>\n" + " <host name='example.com'/>\n" + "</source>\n"); + TEST_BACKING_PARSE("json:{\"file\":{\"driver\":\"gluster\"," + "\"volume\":\"testvol\"," + "\"path\":\"img.qcow2\"," + "\"server\":[ { \"type\":\"tcp\"," + "\"host\":\"example.com\"," + "\"port\":\"1234\"" + "}," + "{ \"type\":\"unix\"," + "\"socket\":\"/path/socket\"" + "}," + "{ \"type\":\"tcp\"," + "\"host\":\"example.com\"" + "}" + "]" + "}" + "}", + "<source protocol='none' name='testvol/img.qcow2'>\n" + " <host name='example.com' port='1234'/>\n" + " <host transport='unix' socket='/path/socket'/>\n" + " <host name='example.com'/>\n" + "</source>\n"); + TEST_BACKING_PARSE("json:{\"file.driver\":\"gluster\"," + "\"file.volume\":\"testvol\"," + "\"file.path\":\"img.qcow2\"," + "\"file.server\":[ { \"type\":\"tcp\"," + "\"host\":\"example.com\"," + "\"port\":\"1234\"" + "}," + "{ \"type\":\"unix\"," + "\"socket\":\"/path/socket\"" + "}," + "{ \"type\":\"tcp\"," + "\"host\":\"example.com\"" + "}" + "]" + "}", + "<source protocol='none' name='testvol/img.qcow2'>\n" + " <host name='example.com' port='1234'/>\n" + " <host transport='unix' socket='/path/socket'/>\n" + " <host name='example.com'/>\n" + "</source>\n"); cleanup: /* Final cleanup */ -- 2.9.0

iSCSI is a bit odd in this aspect since it only supports URIs but using the 'filename' property and does not have any alternative syntax. --- src/util/virstoragefile.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index c9fd475..66dbbef 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2682,6 +2682,26 @@ virStorageSourceParseBackingJSONGluster(virStorageSourcePtr src, } +static int +virStorageSourceParseBackingJSONiSCSI(virStorageSourcePtr src, + virJSONValuePtr json, + int opaque ATTRIBUTE_UNUSED) +{ + const char *uri; + + /* legacy URI based syntax passed via 'filename' option */ + if ((uri = virJSONValueObjectGetString(json, "filename"))) + return virStorageSourceParseBackingJSONUriStr(src, uri, + VIR_STORAGE_NET_PROTOCOL_ISCSI); + + /* iSCSI currently supports only URI syntax passed in as filename */ + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing iSCSI URI in JSON backing volume definition")); + + return -1; +} + + struct virStorageSourceJSONDriverParser { const char *drvname; int (*func)(virStorageSourcePtr src, virJSONValuePtr json, int opaque); @@ -2698,6 +2718,7 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = { {"ftps", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_FTPS}, {"tftp", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP}, {"gluster", virStorageSourceParseBackingJSONGluster, 0}, + {"iscsi", virStorageSourceParseBackingJSONiSCSI, 0}, }; -- 2.9.0

--- src/util/virstoragefile.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ tests/virstoragetest.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 66dbbef..53ff710 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2702,6 +2702,50 @@ virStorageSourceParseBackingJSONiSCSI(virStorageSourcePtr src, } +static int +virStorageSourceParseBackingJSONNbd(virStorageSourcePtr src, + virJSONValuePtr json, + int opaque ATTRIBUTE_UNUSED) +{ + const char *path = virJSONValueObjectGetString(json, "path"); + const char *host = virJSONValueObjectGetString(json, "host"); + const char *port = virJSONValueObjectGetString(json, "port"); + const char *export = virJSONValueObjectGetString(json, "export"); + + if (!path && !host) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing path or host of NBD server in JSON backing " + "volume definition")); + return -1; + } + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_NBD; + + if (VIR_STRDUP(src->path, export) < 0) + return -1; + + if (VIR_ALLOC_N(src->hosts, 1) < 0) + return -1; + src->nhosts = 1; + + if (path) { + src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_UNIX; + if (VIR_STRDUP(src->hosts[0].socket, path) < 0) + return -1; + } else { + src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + if (VIR_STRDUP(src->hosts[0].name, host) < 0) + return -1; + + if (VIR_STRDUP(src->hosts[0].port, port) < 0) + return -1; + } + + return 0; +} + + struct virStorageSourceJSONDriverParser { const char *drvname; int (*func)(virStorageSourcePtr src, virJSONValuePtr json, int opaque); @@ -2719,6 +2763,7 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = { {"tftp", virStorageSourceParseBackingJSONUri, VIR_STORAGE_NET_PROTOCOL_TFTP}, {"gluster", virStorageSourceParseBackingJSONGluster, 0}, {"iscsi", virStorageSourceParseBackingJSONiSCSI, 0}, + {"nbd", virStorageSourceParseBackingJSONNbd, 0}, }; diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c index 22aae47..4ddcac0 100644 --- a/tests/virstoragetest.c +++ b/tests/virstoragetest.c @@ -1437,6 +1437,36 @@ mymain(void) " <host transport='unix' socket='/path/socket'/>\n" " <host name='example.com'/>\n" "</source>\n"); + TEST_BACKING_PARSE("json:{\"file\":{\"driver\":\"nbd\"," + "\"path\":\"/path/to/socket\"" + "}" + "}", + "<source protocol='nbd'>\n" + " <host transport='unix' socket='/path/to/socket'/>\n" + "</source>\n"); + TEST_BACKING_PARSE("json:{\"file.driver\":\"nbd\"," + "\"file.path\":\"/path/to/socket\"" + "}", + "<source protocol='nbd'>\n" + " <host transport='unix' socket='/path/to/socket'/>\n" + "</source>\n"); + TEST_BACKING_PARSE("json:{\"file\":{\"driver\":\"nbd\"," + "\"export\":\"blah\"," + "\"host\":\"example.org\"," + "\"port\":\"6000\"" + "}" + "}", + "<source protocol='nbd' name='blah'>\n" + " <host name='example.org' port='6000'/>\n" + "</source>\n"); + TEST_BACKING_PARSE("json:{\"file.driver\":\"nbd\"," + "\"file.export\":\"blah\"," + "\"file.host\":\"example.org\"," + "\"file.port\":\"6000\"" + "}", + "<source protocol='nbd' name='blah'>\n" + " <host name='example.org' port='6000'/>\n" + "</source>\n"); cleanup: /* Final cleanup */ -- 2.9.0

--- src/util/virstoragefile.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 53ff710..169d70e 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2746,6 +2746,30 @@ virStorageSourceParseBackingJSONNbd(virStorageSourcePtr src, } +static int +virStorageSourceParseBackingJSONSheepdog(virStorageSourcePtr src, + virJSONValuePtr json, + int opaque ATTRIBUTE_UNUSED) +{ + const char *filename; + + /* legacy URI based syntax passed via 'filename' option */ + if ((filename = virJSONValueObjectGetString(json, "filename"))) { + if (strstr(filename, "://")) + return virStorageSourceParseBackingJSONUriStr(src, filename, + VIR_STORAGE_NET_PROTOCOL_SHEEPDOG); + + /* libvirt doesn't implement a parser for the legacy non-URI syntax */ + } + + /* Sheepdog currently supports only URI and legacy syntax passed in as filename */ + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing sheepdog URI in JSON backing volume definition")); + + return -1; +} + + struct virStorageSourceJSONDriverParser { const char *drvname; int (*func)(virStorageSourcePtr src, virJSONValuePtr json, int opaque); @@ -2764,6 +2788,7 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = { {"gluster", virStorageSourceParseBackingJSONGluster, 0}, {"iscsi", virStorageSourceParseBackingJSONiSCSI, 0}, {"nbd", virStorageSourceParseBackingJSONNbd, 0}, + {"sheepdog", virStorageSourceParseBackingJSONSheepdog, 0}, }; -- 2.9.0

Allow using 'ssh' protocol in backing chains and later for disks themselves. --- src/libxl/libxl_conf.c | 1 + src/qemu/qemu_command.c | 7 +++++++ src/qemu/qemu_driver.c | 3 +++ src/qemu/qemu_parse_command.c | 1 + src/util/virstoragefile.c | 4 +++- src/util/virstoragefile.h | 1 + src/xenconfig/xen_xl.c | 1 + 7 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/libxl/libxl_conf.c b/src/libxl/libxl_conf.c index 146e08a..2d6d5da 100644 --- a/src/libxl/libxl_conf.c +++ b/src/libxl/libxl_conf.c @@ -581,6 +581,7 @@ libxlMakeNetworkDiskSrcStr(virStorageSourcePtr src, case VIR_STORAGE_NET_PROTOCOL_ISCSI: case VIR_STORAGE_NET_PROTOCOL_GLUSTER: case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_SSH: case VIR_STORAGE_NET_PROTOCOL_LAST: case VIR_STORAGE_NET_PROTOCOL_NONE: virReportError(VIR_ERR_NO_SUPPORT, diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c index 3dc131b..5ac0725 100644 --- a/src/qemu/qemu_command.c +++ b/src/qemu/qemu_command.c @@ -480,6 +480,9 @@ qemuNetworkDriveGetPort(int protocol, case VIR_STORAGE_NET_PROTOCOL_NBD: return 10809; + case VIR_STORAGE_NET_PROTOCOL_SSH: + return 22; + case VIR_STORAGE_NET_PROTOCOL_ISCSI: case VIR_STORAGE_NET_PROTOCOL_GLUSTER: /* no default port specified */ @@ -878,6 +881,10 @@ qemuBuildNetworkDriveURI(virStorageSourcePtr src, ret = virBufferContentAndReset(&buf); break; + case VIR_STORAGE_NET_PROTOCOL_SSH: + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("'ssh' protocol is not yet supported")); + goto cleanup; case VIR_STORAGE_NET_PROTOCOL_LAST: case VIR_STORAGE_NET_PROTOCOL_NONE: diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 0501003..dd5a9ff 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -13372,6 +13372,7 @@ qemuDomainSnapshotPrepareDiskExternalBackingInactive(virDomainDiskDefPtr disk) case VIR_STORAGE_NET_PROTOCOL_FTP: case VIR_STORAGE_NET_PROTOCOL_FTPS: case VIR_STORAGE_NET_PROTOCOL_TFTP: + case VIR_STORAGE_NET_PROTOCOL_SSH: case VIR_STORAGE_NET_PROTOCOL_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, _("external inactive snapshots are not supported on " @@ -13434,6 +13435,7 @@ qemuDomainSnapshotPrepareDiskExternalOverlayActive(virDomainSnapshotDiskDefPtr d case VIR_STORAGE_NET_PROTOCOL_FTP: case VIR_STORAGE_NET_PROTOCOL_FTPS: case VIR_STORAGE_NET_PROTOCOL_TFTP: + case VIR_STORAGE_NET_PROTOCOL_SSH: case VIR_STORAGE_NET_PROTOCOL_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, _("external active snapshots are not supported on " @@ -13578,6 +13580,7 @@ qemuDomainSnapshotPrepareDiskInternal(virConnectPtr conn, case VIR_STORAGE_NET_PROTOCOL_FTP: case VIR_STORAGE_NET_PROTOCOL_FTPS: case VIR_STORAGE_NET_PROTOCOL_TFTP: + case VIR_STORAGE_NET_PROTOCOL_SSH: case VIR_STORAGE_NET_PROTOCOL_LAST: virReportError(VIR_ERR_INTERNAL_ERROR, _("internal inactive snapshots are not supported on " diff --git a/src/qemu/qemu_parse_command.c b/src/qemu/qemu_parse_command.c index 3f7e445..82d1621 100644 --- a/src/qemu/qemu_parse_command.c +++ b/src/qemu/qemu_parse_command.c @@ -2014,6 +2014,7 @@ qemuParseCommandLine(virCapsPtr caps, case VIR_STORAGE_NET_PROTOCOL_FTP: case VIR_STORAGE_NET_PROTOCOL_FTPS: case VIR_STORAGE_NET_PROTOCOL_TFTP: + case VIR_STORAGE_NET_PROTOCOL_SSH: case VIR_STORAGE_NET_PROTOCOL_LAST: case VIR_STORAGE_NET_PROTOCOL_NONE: /* ignored for now */ diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 169d70e..3e22b0c 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -84,7 +84,8 @@ VIR_ENUM_IMPL(virStorageNetProtocol, VIR_STORAGE_NET_PROTOCOL_LAST, "https", "ftp", "ftps", - "tftp") + "tftp", + "ssh") VIR_ENUM_IMPL(virStorageNetHostTransport, VIR_STORAGE_NET_HOST_TRANS_LAST, "tcp", @@ -2501,6 +2502,7 @@ virStorageSourceParseBackingColon(virStorageSourcePtr src, case VIR_STORAGE_NET_PROTOCOL_TFTP: case VIR_STORAGE_NET_PROTOCOL_ISCSI: case VIR_STORAGE_NET_PROTOCOL_GLUSTER: + case VIR_STORAGE_NET_PROTOCOL_SSH: virReportError(VIR_ERR_INTERNAL_ERROR, _("malformed backing store path for protocol %s"), protocol); diff --git a/src/util/virstoragefile.h b/src/util/virstoragefile.h index 1a76fad..3ea3a60 100644 --- a/src/util/virstoragefile.h +++ b/src/util/virstoragefile.h @@ -132,6 +132,7 @@ typedef enum { VIR_STORAGE_NET_PROTOCOL_FTP, VIR_STORAGE_NET_PROTOCOL_FTPS, VIR_STORAGE_NET_PROTOCOL_TFTP, + VIR_STORAGE_NET_PROTOCOL_SSH, VIR_STORAGE_NET_PROTOCOL_LAST } virStorageNetProtocol; diff --git a/src/xenconfig/xen_xl.c b/src/xenconfig/xen_xl.c index dcd4849..25a3621 100644 --- a/src/xenconfig/xen_xl.c +++ b/src/xenconfig/xen_xl.c @@ -727,6 +727,7 @@ xenFormatXLDiskSrcNet(virStorageSourcePtr src) case VIR_STORAGE_NET_PROTOCOL_ISCSI: case VIR_STORAGE_NET_PROTOCOL_GLUSTER: case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG: + case VIR_STORAGE_NET_PROTOCOL_SSH: case VIR_STORAGE_NET_PROTOCOL_LAST: case VIR_STORAGE_NET_PROTOCOL_NONE: virReportError(VIR_ERR_NO_SUPPORT, -- 2.9.0

--- src/util/virstoragefile.c | 38 ++++++++++++++++++++++++++++++++++++++ tests/virstoragetest.c | 19 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/src/util/virstoragefile.c b/src/util/virstoragefile.c index 3e22b0c..fa12b28 100644 --- a/src/util/virstoragefile.c +++ b/src/util/virstoragefile.c @@ -2772,6 +2772,43 @@ virStorageSourceParseBackingJSONSheepdog(virStorageSourcePtr src, } +static int +virStorageSourceParseBackingJSONSSH(virStorageSourcePtr src, + virJSONValuePtr json, + int opaque ATTRIBUTE_UNUSED) +{ + const char *path = virJSONValueObjectGetString(json, "path"); + const char *host = virJSONValueObjectGetString(json, "host"); + const char *port = virJSONValueObjectGetString(json, "port"); + + if (!host || !path) { + virReportError(VIR_ERR_INVALID_ARG, "%s", + _("missing host or path of SSH JSON backing " + "volume definition")); + return -1; + } + + src->type = VIR_STORAGE_TYPE_NETWORK; + src->protocol = VIR_STORAGE_NET_PROTOCOL_SSH; + + if (VIR_STRDUP(src->path, path) < 0) + return -1; + + if (VIR_ALLOC_N(src->hosts, 1) < 0) + return -1; + src->nhosts = 1; + + src->hosts[0].transport = VIR_STORAGE_NET_HOST_TRANS_TCP; + if (VIR_STRDUP(src->hosts[0].name, host) < 0) + return -1; + + if (VIR_STRDUP(src->hosts[0].port, port) < 0) + return -1; + + return 0; +} + + struct virStorageSourceJSONDriverParser { const char *drvname; int (*func)(virStorageSourcePtr src, virJSONValuePtr json, int opaque); @@ -2791,6 +2828,7 @@ static const struct virStorageSourceJSONDriverParser jsonParsers[] = { {"iscsi", virStorageSourceParseBackingJSONiSCSI, 0}, {"nbd", virStorageSourceParseBackingJSONNbd, 0}, {"sheepdog", virStorageSourceParseBackingJSONSheepdog, 0}, + {"ssh", virStorageSourceParseBackingJSONSSH, 0}, }; diff --git a/tests/virstoragetest.c b/tests/virstoragetest.c index 4ddcac0..3b19f59 100644 --- a/tests/virstoragetest.c +++ b/tests/virstoragetest.c @@ -1467,6 +1467,25 @@ mymain(void) "<source protocol='nbd' name='blah'>\n" " <host name='example.org' port='6000'/>\n" "</source>\n"); + TEST_BACKING_PARSE("json:{\"file\":{\"driver\":\"ssh\"," + "\"host\":\"example.org\"," + "\"port\":\"6000\"," + "\"path\":\"blah\"," + "\"user\":\"user\"" + "}" + "}", + "<source protocol='ssh' name='blah'>\n" + " <host name='example.org' port='6000'/>\n" + "</source>\n"); + TEST_BACKING_PARSE("json:{\"file.driver\":\"ssh\"," + "\"file.host\":\"example.org\"," + "\"file.port\":\"6000\"," + "\"file.path\":\"blah\"," + "\"file.user\":\"user\"" + "}", + "<source protocol='ssh' name='blah'>\n" + " <host name='example.org' port='6000'/>\n" + "</source>\n"); cleanup: /* Final cleanup */ -- 2.9.0

On Wed, Jul 27, 2016 at 12:50:07PM +0200, Peter Krempa wrote:
This part is extracted from the gluster series since Eric's comment on the original RFC triggered a rework of certain parts.
This version allows to handle both flattened and hierarchical syntaxes (see patch 1/10) for details.
Peter Krempa (10): util: storage: Add parser for qemu's json backing pseudo-protocol util: json: Make first argument of virJSONValueCopy const util: storage: Add support for host device backing specified via JSON util: storage: Add support for URI based backing volumes in qemu's JSON pseudo-protocol util: storage: Add json pseudo protocol support for gluster volumes util: storage: Add json pseudo protocol support for iSCSI volumes util: storage: Add JSON backing volume parser for 'nbd' protocol util: storage: Add JSON backing store parser for 'sheepdog' protocol util: storage: Add 'ssh' network storage protocol util: storage: Add JSON backing volume parser for 'ssh' protocol
src/libxl/libxl_conf.c | 1 + src/qemu/qemu_command.c | 7 + src/qemu/qemu_driver.c | 3 + src/qemu/qemu_parse_command.c | 1 + src/util/virjson.c | 2 +- src/util/virjson.h | 2 +- src/util/virstoragefile.c | 447 +++++++++++++++++++++++++++++++++++++++++- src/util/virstoragefile.h | 1 + src/xenconfig/xen_xl.c | 1 + tests/virstoragetest.c | 131 +++++++++++++ 10 files changed, 586 insertions(+), 10 deletions(-)
ACK series with the first two patches swapped. Jan
participants (2)
-
Ján Tomko
-
Peter Krempa