From 186bb67c6e8496d04a6f5646df9b2fb483cdc189 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 19 Mar 2013 12:01:24 +0000 Subject: [PATCH] Add support for: Gluster, Ceph (rbd), Sheepdog. These are not tested at all, and so probably won't work. However the code and infrastructure is in place so we can fix bugs easily as they arise. --- generator/actions.ml | 40 +++++-- src/drives.c | 232 +++++++++++++++++++++++++++++++++++------ src/guestfs-internal.h | 6 ++ src/guestfs.pod | 59 ++++++++++- src/launch-libvirt.c | 39 +++---- 5 files changed, 317 insertions(+), 59 deletions(-) diff --git a/generator/actions.ml b/generator/actions.ml index 11a20c0fd..b3ab1e4d5 100644 --- a/generator/actions.ml +++ b/generator/actions.ml @@ -1322,7 +1322,9 @@ See L. =item C The optional protocol argument can be used to select an alternate -source protocol: +source protocol. + +See also: L. =over 4 @@ -1332,12 +1334,33 @@ C is interpreted as a local file or device. This is the default if the optional protocol parameter is omitted. +=item C + +Connect to the GlusterFS server. +The C parameter must also be supplied - see below. + +See also: L + =item C Connect to the Network Block Device server. The C parameter must also be supplied - see below. -See also: L. +See also: L. + +=item C + +Connect to the Ceph (librbd/RBD) server. +The C parameter must also be supplied - see below. + +See also: L. + +=item C + +Connect to the Sheepdog server. +The C parameter may also be supplied - see below. + +See also: L. =back @@ -1346,11 +1369,16 @@ See also: L. For protocols which require access to a remote server, this is a list of server(s). -For protocol C<\"file\">, this list must be empty (or the optional -argument not passed at all). For protocol C<\"nbd\">, this list -must contain exactly one element. + Protocol Number of servers required + -------- -------------------------- + file List must be empty or param not used at all + gluster Exactly one + nbd Exactly one + rbd One or more + sheepdog Zero or more -Each element is a string in one of the following formats: +Each list element is a string specifying a server. The string must be +in one of the following formats: hostname hostname:port diff --git a/src/drives.c b/src/drives.c index 1a7e9c806..58e381491 100644 --- a/src/drives.c +++ b/src/drives.c @@ -104,20 +104,18 @@ create_drive_file (guestfs_h *g, const char *path, } static struct drive * -create_drive_nbd (guestfs_h *g, - struct drive_server *servers, size_t nr_servers, - const char *exportname, - bool readonly, const char *format, - const char *iface, const char *name, - const char *disk_label, - bool use_cache_none) +create_drive_non_file (guestfs_h *g, + enum drive_protocol protocol, + struct drive_server *servers, size_t nr_servers, + const char *exportname, + bool readonly, const char *format, + const char *iface, const char *name, + const char *disk_label, + bool use_cache_none) { struct drive *drv = safe_calloc (g, 1, sizeof *drv); - /* Should have been checked by the calling code. */ - assert (nr_servers == 1); - - drv->src.protocol = drive_protocol_nbd; + drv->src.protocol = protocol; drv->src.servers = servers; drv->src.nr_servers = nr_servers; drv->src.u.exportname = safe_strdup (g, exportname); @@ -134,6 +132,146 @@ create_drive_nbd (guestfs_h *g, return drv; } +static struct drive * +create_drive_gluster (guestfs_h *g, + struct drive_server *servers, size_t nr_servers, + const char *exportname, + bool readonly, const char *format, + const char *iface, const char *name, + const char *disk_label, + bool use_cache_none) +{ + if (nr_servers != 1) { + error (g, _("gluster: you must specify exactly one server")); + return NULL; + } + + if ((servers[0].transport == drive_transport_none || + servers[0].transport == drive_transport_tcp) && + servers[0].port == 0) { + error (g, _("gluster: port number must be specified")); + return NULL; + } + + if (STREQ (exportname, "")) { + error (g, _("gluster: volume name parameter should not be an empty string")); + return NULL; + } + + return create_drive_non_file (g, drive_protocol_gluster, + servers, nr_servers, exportname, + readonly, format, iface, name, disk_label, + use_cache_none); +} + +static int +nbd_port (void) +{ + struct servent *servent; + + servent = getservbyname ("nbd", "tcp"); + if (servent) + return ntohs (servent->s_port); + else + return 10809; +} + +static struct drive * +create_drive_nbd (guestfs_h *g, + struct drive_server *servers, size_t nr_servers, + const char *exportname, + bool readonly, const char *format, + const char *iface, const char *name, + const char *disk_label, + bool use_cache_none) +{ + if (nr_servers != 1) { + error (g, _("nbd: you must specify exactly one server")); + return NULL; + } + + if (servers[0].port == 0) + servers[0].port = nbd_port (); + + return create_drive_non_file (g, drive_protocol_nbd, + servers, nr_servers, exportname, + readonly, format, iface, name, disk_label, + use_cache_none); +} + +static struct drive * +create_drive_rbd (guestfs_h *g, + struct drive_server *servers, size_t nr_servers, + const char *exportname, + bool readonly, const char *format, + const char *iface, const char *name, + const char *disk_label, + bool use_cache_none) +{ + size_t i; + + if (nr_servers == 0) { + error (g, _("rbd: you must specify one or more servers")); + return NULL; + } + + for (i = 0; i < nr_servers; ++i) { + if (servers[i].transport != drive_transport_none && + servers[i].transport != drive_transport_tcp) { + error (g, _("rbd: only tcp transport is supported")); + return NULL; + } + if (servers[i].port == 0) { + error (g, _("rbd: port number must be specified")); + return NULL; + } + } + + if (STREQ (exportname, "")) { + error (g, _("rbd: image name parameter should not be an empty string")); + return NULL; + } + + return create_drive_non_file (g, drive_protocol_rbd, + servers, nr_servers, exportname, + readonly, format, iface, name, disk_label, + use_cache_none); +} + +static struct drive * +create_drive_sheepdog (guestfs_h *g, + struct drive_server *servers, size_t nr_servers, + const char *exportname, + bool readonly, const char *format, + const char *iface, const char *name, + const char *disk_label, + bool use_cache_none) +{ + size_t i; + + for (i = 0; i < nr_servers; ++i) { + if (servers[i].transport != drive_transport_none && + servers[i].transport != drive_transport_tcp) { + error (g, _("sheepdog: only tcp transport is supported")); + return NULL; + } + if (servers[i].port == 0) { + error (g, _("sheepdog: port number must be specified")); + return NULL; + } + } + + if (STREQ (exportname, "")) { + error (g, _("sheepdog: volume parameter should not be an empty string")); + return NULL; + } + + return create_drive_non_file (g, drive_protocol_sheepdog, + servers, nr_servers, exportname, + readonly, format, iface, name, disk_label, + use_cache_none); +} + /* Traditionally you have been able to use /dev/null as a filename, as * many times as you like. Ancient KVM (RHEL 5) cannot handle adding * /dev/null readonly. qemu 1.2 + virtio-scsi segfaults when you use @@ -487,18 +625,6 @@ parse_servers (guestfs_h *g, char *const *strs, return n; } -static int -nbd_port (void) -{ - struct servent *servent; - - servent = getservbyname ("nbd", "tcp"); - if (servent) - return ntohs (servent->s_port); - else - return 10809; -} - int guestfs__add_drive_opts (guestfs_h *g, const char *filename, const struct guestfs_add_drive_opts_argv *optargs) @@ -588,24 +714,29 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename, disk_label, use_cache_none); } } + else if (STREQ (protocol, "gluster")) { + drv = create_drive_gluster (g, servers, nr_servers, filename, + readonly, format, iface, name, + disk_label, false); + } else if (STREQ (protocol, "nbd")) { - if (nr_servers == 0 || nr_servers > 1) { - error (g, _("protocol nbd: you must specify exactly one server")); - free_drive_servers (servers, nr_servers); - return -1; - } - - if (servers[0].port == 0) - servers[0].port = nbd_port (); - drv = create_drive_nbd (g, servers, nr_servers, filename, readonly, format, iface, name, disk_label, false); } + else if (STREQ (protocol, "rbd")) { + drv = create_drive_rbd (g, servers, nr_servers, filename, + readonly, format, iface, name, + disk_label, false); + } + else if (STREQ (protocol, "sheepdog")) { + drv = create_drive_sheepdog (g, servers, nr_servers, filename, + readonly, format, iface, name, + disk_label, false); + } else { error (g, _("unknown protocol '%s'"), protocol); - free_drive_servers (servers, nr_servers); - return -1; + drv = NULL; /*FALLTHROUGH*/ } if (drv == NULL) { @@ -843,6 +974,24 @@ guestfs___drive_source_qemu_param (guestfs_h *g, const struct drive_source *src) case drive_protocol_file: return safe_strdup (g, src->u.path); + case drive_protocol_gluster: + switch (src->servers[0].transport) { + case drive_transport_none: + /* XXX quoting */ + return safe_asprintf (g, "gluster://%s:%d/%s", + src->servers[0].u.hostname, src->servers[0].port, + src->u.exportname); + case drive_transport_tcp: + /* XXX quoting */ + return safe_asprintf (g, "gluster+tcp://%s:%d/%s", + src->servers[0].u.hostname, src->servers[0].port, + src->u.exportname); + case drive_transport_unix: + /* XXX quoting */ + return safe_asprintf (g, "gluster+unix:///%s?socket=%s", + src->u.exportname, src->servers[0].u.socket); + } + case drive_protocol_nbd: { CLEANUP_FREE char *p = NULL; char *ret; @@ -866,6 +1015,21 @@ guestfs___drive_source_qemu_param (guestfs_h *g, const struct drive_source *src) return ret; } + + case drive_protocol_rbd: + /* XXX Although libvirt allows multiple hosts to be specified, + * it's unclear how these are ever passed to Ceph. Perhaps via + * environment variables? + */ + return safe_asprintf (g, "rbd:%s", src->u.exportname); + + case drive_protocol_sheepdog: + if (src->nr_servers == 0) + return safe_asprintf (g, "sheepdog:%s", src->u.exportname); + else /* XXX How to pass multiple hosts? */ + return safe_asprintf (g, "sheepdog:%s:%d:%s", + src->servers[0].u.hostname, src->servers[0].port, + src->u.exportname); } abort (); diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h index 3254d2442..044f9bff6 100644 --- a/src/guestfs-internal.h +++ b/src/guestfs-internal.h @@ -115,13 +115,19 @@ struct event { /* Drives added to the handle. */ enum drive_protocol { drive_protocol_file, + drive_protocol_gluster, drive_protocol_nbd, + drive_protocol_rbd, + drive_protocol_sheepdog, }; enum drive_transport { drive_transport_none = 0, /* no transport specified */ drive_transport_tcp, /* +tcp */ drive_transport_unix, /* +unix */ + /* XXX In theory gluster+rdma could be supported here, but + * I have no idea what gluster+rdma URLs would look like. + */ }; struct drive_server { diff --git a/src/guestfs.pod b/src/guestfs.pod index 426ae9b7e..53cabdefe 100644 --- a/src/guestfs.pod +++ b/src/guestfs.pod @@ -637,7 +637,46 @@ Backends that support hotplugging do not require that you add E 1 disk before calling launch. When hotplugging is supported you don't need to add any disks. -=head2 NETWORK BLOCK DEVICES +=head2 REMOTE STORAGE + +=head3 CEPH + +Libguestfs can access Ceph (librbd/RBD) disks. + +To do this, set the optional C and C parameters of +L like this: + + char **servers = { "ceph1.example.org:3000", /* ... */, NULL }; + guestfs_add_drive_opts (g, "pool/image", + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "rbd", + GUESTFS_ADD_DRIVE_OPTS_SERVER, servers, + -1); + +C (the C parameter) is a list of one or more Ceph +servers. The server string is documented in +L. + +=head3 GLUSTER + +Libguestfs can access Gluster disks. + +To do this, set the optional C and C parameters of +L like this: + + char **servers = { "gluster.example.org:3000", NULL }; + guestfs_add_drive_opts (g, "volname/image", + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "gluster", + GUESTFS_ADD_DRIVE_OPTS_SERVER, servers, + -1); + +C (the C parameter) is a list which must have a +single element. The single element is a string defining the Gluster +server. The format of this string is documented in +L. + +=head3 NETWORK BLOCK DEVICE Libguestfs can access Network Block Device (NBD) disks remotely. @@ -700,6 +739,24 @@ L =back +=head3 SHEEPDOG + +Libguestfs can access Sheepdog disks. + +To do this, set the optional C and C parameters of +L like this: + + char **servers = { /* optional servers ... */ NULL }; + guestfs_add_drive_opts (g, "volume", + GUESTFS_ADD_DRIVE_OPTS_FORMAT, "raw", + GUESTFS_ADD_DRIVE_OPTS_PROTOCOL, "sheepdog", + GUESTFS_ADD_DRIVE_OPTS_SERVER, servers, + -1); + +The optional list of C may be zero or more server addresses +(C<"hostname:port">). The format of the server strings is documented +in L. + =head2 INSPECTION Libguestfs has APIs for inspecting an unknown disk image to find out diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c index 9ac81d602..db7cc1ba7 100644 --- a/src/launch-libvirt.c +++ b/src/launch-libvirt.c @@ -1045,6 +1045,7 @@ construct_libvirt_xml_disk (guestfs_h *g, struct drive *drv, size_t drv_index) { char drive_name[64] = "sd"; + const char *protocol_str; char scsi_target[64]; struct drive_libvirt *drv_priv = (struct drive_libvirt *) drv->priv; CLEANUP_FREE char *format = NULL; @@ -1104,24 +1105,31 @@ construct_libvirt_xml_disk (guestfs_h *g, } break; - case drive_protocol_nbd: - /* For NBD: + /* For network protocols: * - * + * + * and then zero or more of: * * or: - * - * * */ + case drive_protocol_gluster: + protocol_str = "gluster"; goto network_protocols; + case drive_protocol_nbd: + protocol_str = "nbd"; goto network_protocols; + case drive_protocol_rbd: + protocol_str = "rbd"; goto network_protocols; + case drive_protocol_sheepdog: + protocol_str = "sheepdog"; + /*FALLTHROUGH*/ + network_protocols: XMLERROR (-1, xmlTextWriterWriteAttribute (xo, BAD_CAST "type", BAD_CAST "network")); - XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source")); XMLERROR (-1, xmlTextWriterWriteAttribute (xo, BAD_CAST "protocol", - BAD_CAST "nbd")); + BAD_CAST protocol_str)); if (STRNEQ (drv_priv->real_src.u.exportname, "")) XMLERROR (-1, xmlTextWriterWriteAttribute (xo, BAD_CAST "name", @@ -1132,7 +1140,6 @@ construct_libvirt_xml_disk (guestfs_h *g, if (construct_libvirt_xml_disk_source_seclabel (g, xo) == -1) return -1; XMLERROR (-1, xmlTextWriterEndElement (xo)); - break; } XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target")); @@ -1498,12 +1505,6 @@ make_qcow2_overlay (guestfs_h *g, const char *backing_device, CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g); int r; - /* Assert that backing_device is an absolute path, or it's an - * nbd device name. - */ - assert (backing_device); - assert (backing_device[0] == '/' || STRPREFIX (backing_device, "nbd:")); - tmpfile = safe_asprintf (g, "%s/snapshot%d", g->tmpdir, ++g->unique); guestfs___cmd_add_arg (cmd, "qemu-img"); @@ -1587,24 +1588,26 @@ make_drive_priv (guestfs_h *g, struct drive *drv, } break; + case drive_protocol_gluster: case drive_protocol_nbd: + case drive_protocol_rbd: + case drive_protocol_sheepdog: if (!drv->readonly) { guestfs___copy_drive_source (g, &drv->src, &drv_priv->real_src); drv_priv->format = drv->format ? safe_strdup (g, drv->format) : NULL; } else { - CLEANUP_FREE char *nbd_device; + CLEANUP_FREE char *qemu_device; drv_priv->real_src.protocol = drive_protocol_file; - nbd_device = guestfs___drive_source_qemu_param (g, &drv->src); - drv_priv->real_src.u.path = make_qcow2_overlay (g, nbd_device, + qemu_device = guestfs___drive_source_qemu_param (g, &drv->src); + drv_priv->real_src.u.path = make_qcow2_overlay (g, qemu_device, drv->format, selinux_imagelabel); if (!drv_priv->real_src.u.path) return -1; drv_priv->format = safe_strdup (g, "qcow2"); } - break; } return 0;