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;