[libvirt] [RFC PATCH 0/5] Allow data upload/download to/from storage volumes

This patch series introduces two new APIs to allow data upload/download to/from storage volumes, using the streams APIs. Mostly I wrote this so I had an easier way to test the streams handling in my RPC patch series than using QEMU tunnelled migration code. Cole has expressed an interest in this kind of functionality in the past to allow virt-manager to upload kernel/initrd to remote hosts for provisioning guests. Most of the code here is pretty boring & trivial. The first patch is the one that is remotely interesting. One might argue we should just spawn 'dd' instead of this, but dd has some annoying aspects (you can't control its write offset in units smaller than the block size) and this could be useful more broadly.

The O_NONBLOCK flag doesn't work as desired on plain files or block devices. Introduce an I/O helper program that does the blocking I/O operations, communicating over a pipe that can support O_NONBLOCK * src/fdstream.c, src/fdstream.h: Add non-blocking I/O on plain files/block devices * src/Makefile.am, src/util/iohelper.c: I/O helper program * src/qemu/qemu_driver.c, src/lxc/lxc_driver.c, src/uml/uml_driver.c, src/xen/xen_driver.c: Update for streams API change --- src/Makefile.am | 12 +++ src/fdstream.c | 240 ++++++++++++++++++++++++++++++++++++----------- src/fdstream.h | 5 + src/lxc/lxc_driver.c | 2 +- src/qemu/qemu_driver.c | 26 +++++- src/uml/uml_driver.c | 2 +- src/util/iohelper.c | 216 +++++++++++++++++++++++++++++++++++++++++++ src/xen/xen_driver.c | 2 +- 8 files changed, 445 insertions(+), 60 deletions(-) create mode 100644 src/util/iohelper.c diff --git a/src/Makefile.am b/src/Makefile.am index bd25b38..961913d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -375,6 +375,9 @@ STORAGE_DRIVER_DISK_SOURCES = \ STORAGE_HELPER_DISK_SOURCES = \ storage/parthelper.c +UTIL_IO_HELPER_SOURCES = \ + util/iohelper.c + # Network filters NWFILTER_DRIVER_SOURCES = \ nwfilter/nwfilter_driver.h nwfilter/nwfilter_driver.c \ @@ -1164,6 +1167,15 @@ EXTRA_DIST += $(LIBVIRT_QEMU_SYMBOL_FILE) libexec_PROGRAMS = +libexec_PROGRAMS += libvirt_iohelper +libvirt_iohelper_SOURCES = $(UTIL_IO_HELPER_SOURCES) +libvirt_iohelper_LDFLAGS = $(WARN_LDFLAGS) $(AM_LDFLAGS) +libvirt_iohelper_LDADD = \ + libvirt_util.la \ + ../gnulib/lib/libgnu.la + +libvirt_iohelper_CFLAGS = $(AM_CFLAGS) + if WITH_STORAGE_DISK if WITH_LIBVIRTD libexec_PROGRAMS += libvirt_parthelper diff --git a/src/fdstream.c b/src/fdstream.c index 701fafc..2746c05 100644 --- a/src/fdstream.c +++ b/src/fdstream.c @@ -34,10 +34,12 @@ #include "fdstream.h" #include "virterror_internal.h" #include "datatypes.h" +#include "logging.h" #include "memory.h" #include "event.h" #include "util.h" #include "files.h" +#include "configmake.h" #define VIR_FROM_THIS VIR_FROM_STREAMS #define streamsReportError(code, ...) \ @@ -47,6 +49,10 @@ /* Tunnelled migration stream support */ struct virFDStreamData { int fd; + int errfd; + virCommandPtr cmd; + unsigned long long offset; + unsigned long long length; int watch; unsigned int cbRemoved; @@ -206,6 +212,25 @@ static int virFDStreamFree(struct virFDStreamData *fdst) { int ret; ret = VIR_CLOSE(fdst->fd); + if (fdst->cmd) { + int status; + if (virCommandWait(fdst->cmd, &status) < 0) + ret = -1; + if (status != 0) { + ssize_t len = 1024; + char buf[len]; + if ((len = saferead(fdst->errfd, buf, len)) < 0) { + streamsReportError(VIR_ERR_INTERNAL_ERROR, + _("I/O helper exited with status %d"), status); + } else { + buf[len-1] = '\0'; + streamsReportError(VIR_ERR_INTERNAL_ERROR, "%s", + buf); + } + ret = -1; + } + virCommandFree(fdst->cmd); + } VIR_FREE(fdst); return ret; } @@ -217,6 +242,8 @@ virFDStreamClose(virStreamPtr st) struct virFDStreamData *fdst = st->privateData; int ret; + VIR_DEBUG("st=%p", st); + if (!fdst) return 0; @@ -250,6 +277,18 @@ static int virFDStreamWrite(virStreamPtr st, const char *bytes, size_t nbytes) virMutexLock(&fdst->lock); + if (fdst->length) { + if (fdst->length == fdst->offset) { + virReportSystemError(ENOSPC, "%s", + _("cannot write to stream")); + virMutexUnlock(&fdst->lock); + return -1; + } + + if ((fdst->length - fdst->offset) < nbytes) + nbytes = fdst->length - fdst->offset; + } + retry: ret = write(fdst->fd, bytes, nbytes); if (ret < 0) { @@ -262,6 +301,8 @@ retry: virReportSystemError(errno, "%s", _("cannot write to stream")); } + } else if (fdst->length) { + fdst->offset += ret; } virMutexUnlock(&fdst->lock); @@ -288,6 +329,16 @@ static int virFDStreamRead(virStreamPtr st, char *bytes, size_t nbytes) virMutexLock(&fdst->lock); + if (fdst->length) { + if (fdst->length == fdst->offset) { + virMutexUnlock(&fdst->lock); + return 0; + } + + if ((fdst->length - fdst->offset) < nbytes) + nbytes = fdst->length - fdst->offset; + } + retry: ret = read(fdst->fd, bytes, nbytes); if (ret < 0) { @@ -300,6 +351,8 @@ retry: virReportSystemError(errno, "%s", _("cannot read from stream")); } + } else if (fdst->length) { + fdst->offset += ret; } virMutexUnlock(&fdst->lock); @@ -317,11 +370,17 @@ static virStreamDriver virFDStreamDrv = { .streamRemoveCallback = virFDStreamRemoveCallback }; -int virFDStreamOpen(virStreamPtr st, - int fd) +static int virFDStreamOpenInternal(virStreamPtr st, + int fd, + virCommandPtr cmd, + int errfd, + unsigned long long length) { struct virFDStreamData *fdst; + VIR_DEBUG("st=%p fd=%d cmd=%p errfd=%d length=%llu", + st, fd, cmd, errfd, length); + if ((st->flags & VIR_STREAM_NONBLOCK) && virSetNonBlock(fd) < 0) return -1; @@ -332,6 +391,9 @@ int virFDStreamOpen(virStreamPtr st, } fdst->fd = fd; + fdst->cmd = cmd; + fdst->errfd = errfd; + fdst->length = length; if (virMutexInit(&fdst->lock) < 0) { VIR_FREE(fdst); streamsReportError(VIR_ERR_INTERNAL_ERROR, "%s", @@ -346,6 +408,13 @@ int virFDStreamOpen(virStreamPtr st, } +int virFDStreamOpen(virStreamPtr st, + int fd) +{ + return virFDStreamOpenInternal(st, fd, NULL, -1, 0); +} + + #if HAVE_SYS_UN_H int virFDStreamConnectUNIX(virStreamPtr st, const char *path, @@ -387,7 +456,7 @@ int virFDStreamConnectUNIX(virStreamPtr st, goto error; } while ((++i <= timeout*5) && (usleep(.2 * 1000000) <= 0)); - if (virFDStreamOpen(st, fd) < 0) + if (virFDStreamOpenInternal(st, fd, NULL, -1, 0) < 0) goto error; return 0; @@ -406,19 +475,28 @@ int virFDStreamConnectUNIX(virStreamPtr st ATTRIBUTE_UNUSED, } #endif -int virFDStreamOpenFile(virStreamPtr st, - const char *path, - int flags) +static int +virFDStreamOpenFileInternal(virStreamPtr st, + const char *path, + unsigned long long offset, + unsigned long long length, + int flags, + int mode) { - int fd; + int fd = -1; + int fds[2] = { -1, -1 }; struct stat sb; + virCommandPtr cmd = NULL; + int errfd = -1; - if (flags & O_CREAT) { - streamsReportError(VIR_ERR_INTERNAL_ERROR, "%s", - _("Unexpected O_CREAT flag when opening existing file")); - } + VIR_DEBUG("st=%p path=%s flags=%d offset=%llu length=%llu mode=%d", + st, path, flags, offset, length, mode); - if ((fd = open(path, flags)) < 0) { + if (flags & O_CREAT) + fd = open(path, flags, mode); + else + fd = open(path, flags); + if (fd < 0) { virReportSystemError(errno, _("Unable to open stream for '%s'"), path); @@ -440,64 +518,114 @@ int virFDStreamOpenFile(virStreamPtr st, if ((st->flags & VIR_STREAM_NONBLOCK) && (!S_ISCHR(sb.st_mode) && !S_ISFIFO(sb.st_mode))) { - streamsReportError(VIR_ERR_INTERNAL_ERROR, - _("Non-blocking I/O is not supported on %s"), - path); - goto error; + const char *opname; + int childfd; + char offsetstr[100]; + char lengthstr[100]; + + snprintf(offsetstr, sizeof(offsetstr), "%llu", offset); + snprintf(lengthstr, sizeof(lengthstr), "%llu", length); + + if (flags == O_RDONLY) { + opname = "read"; + } else if (flags == O_WRONLY) { + opname = "write"; + } else if (flags == (O_WRONLY|O_CREAT)) { + opname = "create"; + } else { + streamsReportError(VIR_ERR_INTERNAL_ERROR, + _("Non-blocking I/O is not supported on %s with flags %d"), + path, flags); + goto error; + } + + VIR_FORCE_CLOSE(fd); + if (pipe(fds) < 0) { + virReportSystemError(errno, "%s", + _("Unable to create pipe")); + goto error; + } + + cmd = virCommandNewArgList(LIBEXECDIR "/libvirt_iohelper", + path, + opname, + offsetstr, + lengthstr, + NULL); + + if (!cmd) + goto error; + + //virCommandDaemonize(cmd); + if (flags == O_RDONLY) { + childfd = fds[1]; + fd = fds[0]; + virCommandSetOutputFD(cmd, &childfd); + } else { + childfd = fds[0]; + fd = fds[1]; + virCommandSetInputFD(cmd, childfd); + } + virCommandSetErrorFD(cmd, &errfd); + + if (virCommandRunAsync(cmd, NULL) < 0) + goto error; + + VIR_FORCE_CLOSE(childfd); + } else { +#if 0 + if ((flags & O_CREAT) && length != 0) { + if (ftruncate(fd, length) < 0) { + virReportSystemError(errno, + _("Unable to truncate %s to %llu"), + path, length); + goto error; + } + } +#endif + if (offset && + lseek(fd, offset, SEEK_SET) != offset) { + virReportSystemError(errno, + _("Unable to seek %s to %llu"), + path, offset); + goto error; + } } - if (virFDStreamOpen(st, fd) < 0) + if (virFDStreamOpenInternal(st, fd, cmd, errfd, length) < 0) goto error; return 0; error: + virCommandFree(cmd); + VIR_FORCE_CLOSE(fds[0]); + VIR_FORCE_CLOSE(fds[1]); VIR_FORCE_CLOSE(fd); return -1; } -int virFDStreamCreateFile(virStreamPtr st, - const char *path, - int flags, - mode_t mode) +int virFDStreamOpenFile(virStreamPtr st, + const char *path, + unsigned long long offset, + unsigned long long length, + int flags) { - int fd = open(path, flags, mode); - struct stat sb; - - if (fd < 0) { - virReportSystemError(errno, - _("Unable to open stream for '%s'"), - path); - return -1; - } - - if (fstat(fd, &sb) < 0) { - virReportSystemError(errno, - _("Unable to access stream for '%s'"), - path); - goto error; - } - - /* Thanks to the POSIX i/o model, we can't reliably get - * non-blocking I/O on block devs/regular files. To - * support those we need to fork a helper process todo - * the I/O so we just have a fifo. Or use AIO :-( - */ - if ((st->flags & VIR_STREAM_NONBLOCK) && - (!S_ISCHR(sb.st_mode) && - !S_ISFIFO(sb.st_mode))) { + if (flags & O_CREAT) { streamsReportError(VIR_ERR_INTERNAL_ERROR, - _("Non-blocking I/O is not supported on %s"), + _("Attempt to create %s without specifying mode"), path); - goto error; + return -1; } + return virFDStreamOpenFileInternal(st, path, offset, length, flags, 0); +} - if (virFDStreamOpen(st, fd) < 0) - goto error; - - return 0; - -error: - VIR_FORCE_CLOSE(fd); - return -1; +int virFDStreamCreateFile(virStreamPtr st, + const char *path, + unsigned long long offset, + unsigned long long length, + int flags, + mode_t mode) +{ + return virFDStreamOpenFileInternal(st, path, offset, length, flags, mode); } diff --git a/src/fdstream.h b/src/fdstream.h index f8d22d5..45b9fe7 100644 --- a/src/fdstream.h +++ b/src/fdstream.h @@ -24,6 +24,7 @@ # define __VIR_FDSTREAM_H_ # include "internal.h" +# include "command.h" # include <stdbool.h> int virFDStreamOpen(virStreamPtr st, @@ -35,9 +36,13 @@ int virFDStreamConnectUNIX(virStreamPtr st, int virFDStreamOpenFile(virStreamPtr st, const char *path, + unsigned long long offset, + unsigned long long length, int flags); int virFDStreamCreateFile(virStreamPtr st, const char *path, + unsigned long long offset, + unsigned long long length, int flags, mode_t mode); diff --git a/src/lxc/lxc_driver.c b/src/lxc/lxc_driver.c index a17b0b6..a000aec 100644 --- a/src/lxc/lxc_driver.c +++ b/src/lxc/lxc_driver.c @@ -2810,7 +2810,7 @@ lxcDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) goto cleanup; ret = 0; diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 0f25a2a..7433049 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -4565,6 +4565,12 @@ static int qemuDomainGetMemoryParameters(virDomainPtr dom, goto cleanup; } + if (!virDomainObjIsActive (vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto cleanup; + } + if ((*nparams) == 0) { /* Current number of memory parameters supported by cgroups */ *nparams = QEMU_NB_MEM_PARAM; @@ -4749,6 +4755,24 @@ static int qemuGetSchedulerParameters(virDomainPtr dom, goto cleanup; } + if (!virDomainObjIsActive (vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto cleanup; + } + + if (!virDomainObjIsActive (vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto cleanup; + } + + if (!virDomainObjIsActive (vm)) { + qemuReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("domain is not running")); + goto cleanup; + } + if (virCgroupForDomain(driver->cgroup, vm->def->name, &group, 0) != 0) { qemuReportError(VIR_ERR_INTERNAL_ERROR, _("cannot find cgroup for domain %s"), vm->def->name); @@ -6745,7 +6769,7 @@ qemuDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) goto cleanup; ret = 0; diff --git a/src/uml/uml_driver.c b/src/uml/uml_driver.c index 2af8002..8f29f8c 100644 --- a/src/uml/uml_driver.c +++ b/src/uml/uml_driver.c @@ -2126,7 +2126,7 @@ umlDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) goto cleanup; ret = 0; diff --git a/src/util/iohelper.c b/src/util/iohelper.c new file mode 100644 index 0000000..0dac055 --- /dev/null +++ b/src/util/iohelper.c @@ -0,0 +1,216 @@ +/* + * iohelper.c: Helper program to perform I/O operations on files + * + * Copyright (C) 2011 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Daniel P. Berrange <berrange@redhat.com> + * + * Current support + * - Read existing file + * - Write existing file + * - Create & write new file + */ + +#include <config.h> + +#include <locale.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> + +#include "util.h" +#include "threads.h" +#include "files.h" +#include "memory.h" +#include "virterror_internal.h" +#include "configmake.h" + +#define VIR_FROM_THIS VIR_FROM_STORAGE + +static int runIO(const char *path, + int flags, + int mode, + int otherfd, + const char *otherfdname, + unsigned long long offset, + unsigned long long length) +{ + char *buf = NULL; + size_t buflen = 1024*1024; + int fd; + int ret = -1; + int fdin, fdout; + const char *fdinname, *fdoutname; + + if (flags & O_CREAT) { + fd = open(path, flags, mode); + } else { + fd = open(path, flags); + } + if (fd < 0) { + virReportSystemError(errno, _("Unable to open %s"), path); + goto cleanup; + } + +#if 0 + if (length && (flags & O_CREAT)) { + if (ftruncate(fd, length) < 0) { + virReportSystemError(errno, _("Unable to truncate %s to %llu"), + path, length); + goto cleanup; + } + } +#endif + + if (offset) { + if (lseek(fd, offset, SEEK_SET) < 0) { + virReportSystemError(errno, _("Unable to seek %s to %llu"), + path, offset); + goto cleanup; + } + } + + if (VIR_ALLOC_N(buf, buflen) < 0) { + virReportOOMError(); + goto cleanup; + } + + if ((flags & O_RDONLY) == O_RDONLY) { + fdin = fd; + fdinname = path; + fdout = otherfd; + fdoutname = otherfdname; + } else { + fdin = otherfd; + fdinname = otherfdname; + fdout = fd; + fdoutname = path; + } + + offset = 0; + while (1) { + ssize_t got; + + if (length && + (length - offset) < buflen) + buflen = length - offset; + + if (buflen == 0) + break; + + if ((got = saferead(fdin, buf, buflen)) < 0) { + virReportSystemError(errno, _("Unable to read %s"), fdinname); + goto cleanup; + } + if (got == 0) + break; + offset += got; + if (safewrite(fdout, buf, got) < 0) { + virReportSystemError(errno, _("Unable to write %s"), fdoutname); + goto cleanup; + } + } + + ret = 0; + +cleanup: + if (VIR_CLOSE(fd) < 0 && + ret == 0) { + virReportSystemError(errno, _("Unable to close %s"), path); + ret = -1; + } + + VIR_FREE(buf); + return ret; +} + +int main(int argc, char **argv) +{ + const char *path; + const char *op; + virErrorPtr err; + unsigned long long offset; + unsigned long long length; + int flags; + int mode; + int otherfd; + const char *otherfdname; + + if (setlocale(LC_ALL, "") == NULL || + bindtextdomain(PACKAGE, LOCALEDIR) == NULL || + textdomain(PACKAGE) == NULL) { + fprintf(stderr, _("%s: initialization failed\n"), argv[0]); + exit(EXIT_FAILURE); + } + + if (virThreadInitialize() < 0 || + virErrorInitialize() < 0 || + virRandomInitialize(time(NULL) ^ getpid())) { + fprintf(stderr, _("%s: initialization failed\n"), argv[0]); + exit(EXIT_FAILURE); + } + + if (argc != 5) { + fprintf(stderr, _("%s: syntax FILENAME OPERATION OFFSET LENGTH\n"), argv[0]); + exit(EXIT_FAILURE); + } + + path = argv[1]; + op = argv[2]; + + if (virStrToLong_ull(argv[3], NULL, 10, &offset) < 0) { + fprintf(stderr, _("%s: malformed file offset %s"), argv[0], argv[3]); + exit(EXIT_FAILURE); + } + if (virStrToLong_ull(argv[4], NULL, 10, &length) < 0) { + fprintf(stderr, _("%s: malformed file length %s"), argv[0], argv[4]); + exit(EXIT_FAILURE); + } + + if (STREQ(op, "read")) { + flags = O_RDONLY; + otherfd = STDOUT_FILENO; + otherfdname = "stdout"; + } else if (STREQ(op, "write")) { + flags = O_WRONLY; + otherfd = STDIN_FILENO; + otherfdname = "stdin"; + } else if (STREQ(op, "create")) { + flags = O_WRONLY|O_CREAT; + mode = 0644; + otherfd = STDIN_FILENO; + otherfdname = "stdin"; + } else { + fprintf(stderr, _("%s: unknown file operation %s\n"), argv[0], op); + exit(EXIT_FAILURE); + } + + if (runIO(path, flags, mode, otherfd, otherfdname, offset, length) < 0) + goto error; + + return 0; + +error: + err = virGetLastError(); + if (err) { + fprintf(stderr, "%s: %s\n", argv[0], err->message); + } else { + fprintf(stderr, _("%s: unknown failure with %s\n"), argv[0], path); + } + exit(EXIT_FAILURE); +} diff --git a/src/xen/xen_driver.c b/src/xen/xen_driver.c index 979f9e0..9fec810 100644 --- a/src/xen/xen_driver.c +++ b/src/xen/xen_driver.c @@ -1994,7 +1994,7 @@ xenUnifiedDomainOpenConsole(virDomainPtr dom, goto cleanup; } - if (virFDStreamOpenFile(st, chr->source.data.file.path, O_RDWR) < 0) + if (virFDStreamOpenFile(st, chr->source.data.file.path, 0, 0, O_RDWR) < 0) goto cleanup; ret = 0; -- 1.7.4

New APIs are added allowing streaming of content to/from storage volumes. A new API for creating volumes is also added allowing the content to be provided immediately at time of creation * include/libvirt/libvirt.h.in: Add virStorageVolUpload and virStorageVolDownload, virStorageVolCreateUpload APIs * src/driver.h, src/libvirt.c, src/libvirt_public.syms: Stub code for new APIs * src/storage/storage_driver.c, src/esx/esx_storage_driver.c: Add dummy entries in driver table for new APIs --- include/libvirt/libvirt.h.in | 10 ++++ include/libvirt/virterror.h | 1 + src/driver.h | 14 +++++ src/esx/esx_storage_driver.c | 2 + src/libvirt.c | 120 ++++++++++++++++++++++++++++++++++++++++++ src/libvirt_public.syms | 7 +++ src/storage/storage_driver.c | 2 + src/util/virterror.c | 6 ++ 8 files changed, 162 insertions(+), 0 deletions(-) diff --git a/include/libvirt/libvirt.h.in b/include/libvirt/libvirt.h.in index 5dfb752..32f4820 100644 --- a/include/libvirt/libvirt.h.in +++ b/include/libvirt/libvirt.h.in @@ -1422,6 +1422,16 @@ virStorageVolPtr virStorageVolCreateXMLFrom (virStoragePoolPtr pool, const char *xmldesc, virStorageVolPtr clonevol, unsigned int flags); +int virStorageVolDownload (virStorageVolPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long length, + unsigned int flags); +int virStorageVolUpload (virStorageVolPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long length, + unsigned int flags); int virStorageVolDelete (virStorageVolPtr vol, unsigned int flags); int virStorageVolWipe (virStorageVolPtr vol, diff --git a/include/libvirt/virterror.h b/include/libvirt/virterror.h index 5962dbf..c5db7aa 100644 --- a/include/libvirt/virterror.h +++ b/include/libvirt/virterror.h @@ -228,6 +228,7 @@ typedef enum { VIR_ERR_HOOK_SCRIPT_FAILED = 70, /* a synchronous hook script failed */ VIR_ERR_INVALID_DOMAIN_SNAPSHOT = 71,/* invalid domain snapshot */ VIR_ERR_NO_DOMAIN_SNAPSHOT = 72, /* domain snapshot not found */ + VIR_ERR_INVALID_STREAM = 73 /* stream pointer not valid */ } virErrorNumber; /** diff --git a/src/driver.h b/src/driver.h index 7451004..df9d94b 100644 --- a/src/driver.h +++ b/src/driver.h @@ -881,6 +881,18 @@ typedef virStorageVolPtr const char *xmldesc, virStorageVolPtr clone, unsigned int flags); +typedef int + (*virDrvStorageVolDownload) (virStorageVolPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long length, + unsigned int flags); +typedef int + (*virDrvStorageVolUpload) (virStorageVolPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long length, + unsigned int flags); typedef int (*virDrvStoragePoolIsActive)(virStoragePoolPtr pool); @@ -935,6 +947,8 @@ struct _virStorageDriver { virDrvStorageVolLookupByPath volLookupByPath; virDrvStorageVolCreateXML volCreateXML; virDrvStorageVolCreateXMLFrom volCreateXMLFrom; + virDrvStorageVolDownload volDownload; + virDrvStorageVolUpload volUpload; virDrvStorageVolDelete volDelete; virDrvStorageVolWipe volWipe; virDrvStorageVolGetInfo volGetInfo; diff --git a/src/esx/esx_storage_driver.c b/src/esx/esx_storage_driver.c index 136a90b..9e4dd9e 100644 --- a/src/esx/esx_storage_driver.c +++ b/src/esx/esx_storage_driver.c @@ -1671,6 +1671,8 @@ static virStorageDriver esxStorageDriver = { esxStorageVolumeLookupByPath, /* volLookupByPath */ esxStorageVolumeCreateXML, /* volCreateXML */ esxStorageVolumeCreateXMLFrom, /* volCreateXMLFrom */ + NULL, /* volDownload */ + NULL, /* volUpload */ esxStorageVolumeDelete, /* volDelete */ esxStorageVolumeWipe, /* volWipe */ esxStorageVolumeGetInfo, /* volGetInfo */ diff --git a/src/libvirt.c b/src/libvirt.c index e4b451e..05db319 100644 --- a/src/libvirt.c +++ b/src/libvirt.c @@ -8886,6 +8886,126 @@ error: /** + * virStorageVolDownload: + * @pool: pointer to volume to download + * @stream: stream to use as output + * @offset: position to start reading from + * @length: limit on amount of data to download + * @flags: flags for creation (unused, pass 0) + * + * Download the content of the volume as a stream. + * + * Returns 0, or -1 upon error. + */ +int +virStorageVolDownload(virStorageVolPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long length, + unsigned int flags) +{ + VIR_DEBUG("vol=%p stream=%p offset=%llu length=%llu flags=%u", vol, stream, offset, length, flags); + + virResetLastError(); + + if (!VIR_IS_STORAGE_VOL(vol)) { + virLibConnError(VIR_ERR_INVALID_STORAGE_VOL, __FUNCTION__); + return -1; + } + + if (!VIR_IS_STREAM(stream)) { + virLibConnError(VIR_ERR_INVALID_STREAM, __FUNCTION__); + return -1; + } + + if (vol->conn->flags & VIR_CONNECT_RO || + stream->conn->flags & VIR_CONNECT_RO) { + virLibConnError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + if (vol->conn->storageDriver && + vol->conn->storageDriver->volDownload) { + int ret; + ret = vol->conn->storageDriver->volDownload(vol, + stream, + offset, + length, + flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(vol->conn); + return -1; +} + + +/** + * virStorageVolUpload: + * @pool: pointer to volume to download + * @stream: stream to use as output + * @offset: position to start writing to + * @length: limit on amount of data to upload + * @flags: flags for creation (unused, pass 0) + * + * Upload new content to the volume from a stream. + * + * Returns 0, or -1 upon error. + */ +int +virStorageVolUpload(virStorageVolPtr vol, + virStreamPtr stream, + unsigned long long offset, + unsigned long long length, + unsigned int flags) +{ + VIR_DEBUG("vol=%p stream=%p offset=%llu length=%llu flags=%u", vol, stream, offset, length, flags); + + virResetLastError(); + + if (!VIR_IS_STORAGE_VOL(vol)) { + virLibConnError(VIR_ERR_INVALID_STORAGE_VOL, __FUNCTION__); + return -1; + } + + if (!VIR_IS_STREAM(stream)) { + virLibConnError(VIR_ERR_INVALID_STREAM, __FUNCTION__); + return -1; + } + + if (vol->conn->flags & VIR_CONNECT_RO || + stream->conn->flags & VIR_CONNECT_RO) { + virLibConnError(VIR_ERR_OPERATION_DENIED, __FUNCTION__); + goto error; + } + + if (vol->conn->storageDriver && + vol->conn->storageDriver->volUpload) { + int ret; + ret = vol->conn->storageDriver->volUpload(vol, + stream, + offset, + length, + flags); + if (ret < 0) + goto error; + return ret; + } + + virLibConnError(VIR_ERR_NO_SUPPORT, __FUNCTION__); + +error: + virDispatchError(vol->conn); + return -1; +} + + +/** * virStorageVolDelete: * @vol: pointer to storage volume * @flags: future flags, use 0 for now diff --git a/src/libvirt_public.syms b/src/libvirt_public.syms index 1a45be1..65f846b 100644 --- a/src/libvirt_public.syms +++ b/src/libvirt_public.syms @@ -424,4 +424,11 @@ LIBVIRT_0.8.8 { virConnectGetSysinfo; } LIBVIRT_0.8.6; +LIBVIRT_0.8.9 { + global: + virStorageVolCreateXMLUpload; + virStorageVolDownload; + virStorageVolUpload; +} LIBVIRT_0.8.8; + # .... define new API here using predicted next version number .... diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c index 5373025..ce528cf 100644 --- a/src/storage/storage_driver.c +++ b/src/storage/storage_driver.c @@ -1989,6 +1989,8 @@ static virStorageDriver storageDriver = { .volLookupByPath = storageVolumeLookupByPath, .volCreateXML = storageVolumeCreateXML, .volCreateXMLFrom = storageVolumeCreateXMLFrom, + .volDownload = NULL, + .volUpload = NULL, .volDelete = storageVolumeDelete, .volWipe = storageVolumeWipe, .volGetInfo = storageVolumeGetInfo, diff --git a/src/util/virterror.c b/src/util/virterror.c index e45b582..383a9ea 100644 --- a/src/util/virterror.c +++ b/src/util/virterror.c @@ -1195,6 +1195,12 @@ virErrorMsg(virErrorNumber error, const char *info) else errmsg = _("Domain snapshot not found: %s"); break; + case VIR_ERR_INVALID_STREAM: + if (info == NULL) + errmsg = _("invalid stream pointer in"); + else + errmsg = _("invalid stream pointer in %s"); + break; } return (errmsg); } -- 1.7.4

* tools/virsh.c: Add vol-create-upload, vol-upload and vol-download commands --- .x-sc_avoid_write | 1 + tools/virsh.c | 230 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 0 deletions(-) diff --git a/.x-sc_avoid_write b/.x-sc_avoid_write index f6fc1b2..0784984 100644 --- a/.x-sc_avoid_write +++ b/.x-sc_avoid_write @@ -5,5 +5,6 @@ ^src/util/util\.c$ ^src/xen/xend_internal\.c$ ^daemon/libvirtd.c$ +^tools/virsh\.c$ ^gnulib/ ^tools/console.c$ diff --git a/tools/virsh.c b/tools/virsh.c index 2837e0f..9075c0e 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -259,6 +259,8 @@ static char *vshCommandOptString(const vshCmd *cmd, const char *name, int *found); static long long vshCommandOptLongLong(const vshCmd *cmd, const char *name, int *found); +static unsigned long long vshCommandOptULongLong(const vshCmd *cmd, const char *name, + int *found); static int vshCommandOptBool(const vshCmd *cmd, const char *name); static char *vshCommandOptArgv(const vshCmd *cmd, int count); @@ -6934,6 +6936,216 @@ cleanup: return ret; } + +static int +cmdVolUploadSource(virStreamPtr st ATTRIBUTE_UNUSED, + char *bytes, size_t nbytes, void *opaque) +{ + int *fd = opaque; + + return read(*fd, bytes, nbytes); +} + + +/* + * "vol-upload" command + */ +static const vshCmdInfo info_vol_upload[] = { + {"help", gettext_noop("upload a file into a volume")}, + {"desc", gettext_noop("Upload a file into a volume")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_vol_upload[] = { + {"pool", VSH_OT_STRING, 0, N_("pool name or uuid")}, + {"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")}, + {"file", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("file")}, + {"offset", VSH_OT_INT, 0, N_("volume offset to upload to") }, + {"length", VSH_OT_INT, 0, N_("amount of data to upload") }, + {NULL, 0, 0, NULL} +}; + +/* + */ +static int +cmdVolUpload (vshControl *ctl, const vshCmd *cmd) +{ + char *file = NULL; + virStorageVolPtr vol = NULL; + int ret = FALSE; + int fd = -1; + virStreamPtr st = NULL; + char *name = NULL; + int found; + unsigned long long offset, length; + + if (!vshConnectionUsability(ctl, ctl->conn)) + goto cleanup; + + if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", &name))) { + return FALSE; + } + + offset = vshCommandOptULongLong(cmd, "offset", &found); + if (!found) + offset = 0; + + length = vshCommandOptULongLong(cmd, "length", &found); + if (!found) + length = 0; + + file = vshCommandOptString (cmd, "file", NULL); + if (file == NULL) + goto cleanup; + + if ((fd = open(file, O_RDONLY)) < 0) { + vshError(ctl, "cannot read %s", file); + goto cleanup; + } + + st = virStreamNew(ctl->conn, 0); + if (virStorageVolUpload(vol, st, offset, length, 0) < 0) { + vshError(ctl, "cannot upload to volume %s", name); + goto cleanup; + } + + if (virStreamSendAll(st, cmdVolUploadSource, &fd) < 0) { + vshError(ctl, "cannot send data to volume %s", name); + goto cleanup; + } + + if (close(fd) < 0) { + vshError(ctl, "cannot close file %s", file); + virStreamAbort(st); + goto cleanup; + } + fd = -1; + if (virStreamFinish(st) < 0) { + vshError(ctl, "cannot close volume %s", name); + goto cleanup; + } + + ret = TRUE; + +cleanup: + if (vol) + virStorageVolFree(vol); + if (st) + virStreamFree(st); + free(name); + free(file); + if (fd != -1) + close(fd); + return ret; +} + + + +/* + * "vol-download" command + */ +static const vshCmdInfo info_vol_download[] = { + {"help", gettext_noop("Download a volume to a file")}, + {"desc", gettext_noop("Download a volume to a file")}, + {NULL, NULL} +}; + +static const vshCmdOptDef opts_vol_download[] = { + {"pool", VSH_OT_STRING, 0, N_("pool name or uuid")}, + {"vol", VSH_OT_DATA, VSH_OFLAG_REQ, N_("vol name, key or path")}, + {"file", VSH_OT_DATA, VSH_OFLAG_REQ, gettext_noop("file")}, + {"offset", VSH_OT_INT, 0, N_("volume offset to download from") }, + {"length", VSH_OT_INT, 0, N_("amount of data to download") }, + {NULL, 0, 0, NULL} +}; + + +static int +cmdVolDownloadSink(virStreamPtr st ATTRIBUTE_UNUSED, + const char *bytes, size_t nbytes, void *opaque) +{ + int *fd = opaque; + + return write(*fd, bytes, nbytes); +} + +/* + */ +static int +cmdVolDownload (vshControl *ctl, const vshCmd *cmd) +{ + char *file = NULL; + virStorageVolPtr vol = NULL; + int ret = FALSE; + int fd = -1; + virStreamPtr st = NULL; + char *name = NULL; + int found; + unsigned long long offset, length; + + if (!vshConnectionUsability(ctl, ctl->conn)) + goto cleanup; + + if (!(vol = vshCommandOptVol(ctl, cmd, "vol", "pool", &name))) { + return FALSE; + } + + offset = vshCommandOptULongLong(cmd, "offset", &found); + if (!found) + offset = 0; + + length = vshCommandOptULongLong(cmd, "length", &found); + if (!found) + length = 0; + + file = vshCommandOptString (cmd, "file", NULL); + if (file == NULL) + goto cleanup; + + if ((fd = open(file, O_WRONLY|O_TRUNC|O_CREAT, 0600)) < 0) { + vshError(ctl, "cannot create %s", file); + goto cleanup; + } + + st = virStreamNew(ctl->conn, 0); + if (virStorageVolDownload(vol, st, offset, length, 0) < 0) { + vshError(ctl, "cannot download from volume %s", name); + goto cleanup; + } + + if (virStreamRecvAll(st, cmdVolDownloadSink, &fd) < 0) { + vshError(ctl, "cannot receive data from volume %s", name); + goto cleanup; + } + + if (close(fd) < 0) { + vshError(ctl, "cannot close file %s", file); + virStreamAbort(st); + goto cleanup; + } + fd = -1; + if (virStreamFinish(st) < 0) { + vshError(ctl, "cannot close volume %s", name); + goto cleanup; + } + + ret = TRUE; + +cleanup: + if (ret == FALSE) + unlink(file); + if (vol) + virStorageVolFree(vol); + if (st) + virStreamFree(st); + free(name); + free(file); + if (fd != -1) + close(fd); + return ret; +} + + /* * "vol-delete" command */ @@ -9750,6 +9962,7 @@ cmdEdit (vshControl *ctl, const vshCmd *cmd) return ret; } + /* * "net-edit" command */ @@ -10391,6 +10604,7 @@ static const vshCmdDef storageVolCmds[] = { {"vol-create", cmdVolCreate, opts_vol_create, info_vol_create}, {"vol-create-from", cmdVolCreateFrom, opts_vol_create_from, info_vol_create_from}, {"vol-delete", cmdVolDelete, opts_vol_delete, info_vol_delete}, + {"vol-download", cmdVolDownload, opts_vol_download, info_vol_download }, {"vol-dumpxml", cmdVolDumpXML, opts_vol_dumpxml, info_vol_dumpxml}, {"vol-info", cmdVolInfo, opts_vol_info, info_vol_info}, {"vol-key", cmdVolKey, opts_vol_key, info_vol_key}, @@ -10398,6 +10612,7 @@ static const vshCmdDef storageVolCmds[] = { {"vol-name", cmdVolName, opts_vol_name, info_vol_name}, {"vol-path", cmdVolPath, opts_vol_path, info_vol_path}, {"vol-pool", cmdVolPool, opts_vol_pool, info_vol_pool}, + {"vol-upload", cmdVolUpload, opts_vol_upload, info_vol_upload }, {"vol-wipe", cmdVolWipe, opts_vol_wipe, info_vol_wipe}, {NULL, NULL, NULL, NULL} }; @@ -10867,6 +11082,21 @@ vshCommandOptLongLong(const vshCmd *cmd, const char *name, int *found) return res; } +static unsigned long long +vshCommandOptULongLong(const vshCmd *cmd, const char *name, int *found) +{ + vshCmdOpt *arg = vshCommandOpt(cmd, name); + int num_found = FALSE; + unsigned long long res = 0; + char *end_p = NULL; + + if ((arg != NULL) && (arg->data != NULL)) + num_found = !virStrToLong_ull(arg->data, &end_p, 10, &res); + if (found) + *found = num_found; + return res; +} + /* * Returns TRUE/FALSE if the option exists */ -- 1.7.4

* src/storage/storage_driver.c: Wire up upload/download APIs --- src/storage/storage_driver.c | 133 +++++++++++++++++++++++++++++++++++++++++- 1 files changed, 131 insertions(+), 2 deletions(-) diff --git a/src/storage/storage_driver.c b/src/storage/storage_driver.c index ce528cf..706db74 100644 --- a/src/storage/storage_driver.c +++ b/src/storage/storage_driver.c @@ -46,6 +46,7 @@ #include "storage_backend.h" #include "logging.h" #include "files.h" +#include "fdstream.h" #include "configmake.h" #define VIR_FROM_THIS VIR_FROM_STORAGE @@ -1528,6 +1529,134 @@ cleanup: } +static int +storageVolumeDownload(virStorageVolPtr obj, + virStreamPtr stream, + unsigned long long offset, + unsigned long long length, + unsigned int flags) +{ + virStorageDriverStatePtr driver = obj->conn->storagePrivateData; + virStoragePoolObjPtr pool = NULL; + virStorageVolDefPtr vol = NULL; + int ret = -1; + + virCheckFlags(0, -1); + + storageDriverLock(driver); + pool = virStoragePoolObjFindByName(&driver->pools, obj->pool); + storageDriverUnlock(driver); + + if (!pool) { + virStorageReportError(VIR_ERR_NO_STORAGE_POOL, + "%s", _("no storage pool with matching uuid")); + goto out; + } + + if (!virStoragePoolObjIsActive(pool)) { + virStorageReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("storage pool is not active")); + goto out; + } + + vol = virStorageVolDefFindByName(pool, obj->name); + + if (vol == NULL) { + virStorageReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), + obj->name); + goto out; + } + + if (vol->building) { + virStorageReportError(VIR_ERR_INTERNAL_ERROR, + _("volume '%s' is still being allocated."), + vol->name); + goto out; + } + + if (virFDStreamOpenFile(stream, + vol->target.path, + offset, length, + O_RDONLY) < 0) + goto out; + + ret = 0; + +out: + if (pool) + virStoragePoolObjUnlock(pool); + + return ret; +} + + +static int +storageVolumeUpload(virStorageVolPtr obj, + virStreamPtr stream, + unsigned long long offset, + unsigned long long length, + unsigned int flags) +{ + virStorageDriverStatePtr driver = obj->conn->storagePrivateData; + virStoragePoolObjPtr pool = NULL; + virStorageVolDefPtr vol = NULL; + int ret = -1; + + virCheckFlags(0, -1); + + storageDriverLock(driver); + pool = virStoragePoolObjFindByName(&driver->pools, obj->pool); + storageDriverUnlock(driver); + + if (!pool) { + virStorageReportError(VIR_ERR_NO_STORAGE_POOL, + "%s", _("no storage pool with matching uuid")); + goto out; + } + + if (!virStoragePoolObjIsActive(pool)) { + virStorageReportError(VIR_ERR_OPERATION_INVALID, + "%s", _("storage pool is not active")); + goto out; + } + + vol = virStorageVolDefFindByName(pool, obj->name); + + if (vol == NULL) { + virStorageReportError(VIR_ERR_NO_STORAGE_VOL, + _("no storage vol with matching name '%s'"), + obj->name); + goto out; + } + + if (vol->building) { + virStorageReportError(VIR_ERR_INTERNAL_ERROR, + _("volume '%s' is still being allocated."), + vol->name); + goto out; + } + + + /* Not using O_CREAT because the file is required to + * already exist at this point */ + if (virFDStreamOpenFile(stream, + vol->target.path, + offset, length, + O_WRONLY) < 0) + goto out; + + ret = 0; + +out: + if (pool) + virStoragePoolObjUnlock(pool); + + return ret; +} + + + /* If the volume we're wiping is already a sparse file, we simply * truncate and extend it to its original size, filling it with * zeroes. This behavior is guaranteed by POSIX: @@ -1989,8 +2118,8 @@ static virStorageDriver storageDriver = { .volLookupByPath = storageVolumeLookupByPath, .volCreateXML = storageVolumeCreateXML, .volCreateXMLFrom = storageVolumeCreateXMLFrom, - .volDownload = NULL, - .volUpload = NULL, + .volDownload = storageVolumeDownload, + .volUpload = storageVolumeUpload, .volDelete = storageVolumeDelete, .volWipe = storageVolumeWipe, .volGetInfo = storageVolumeGetInfo, -- 1.7.4

* daemon/remote.c, src/remote/remote_driver.c: Implementation of storage vol upload/download APIs * src/remote/remote_protocol.x: Wire protocol definition for upload/download * daemon/remote_dispatch_args.h, daemon/remote_dispatch_prototypes.h, daemon/remote_dispatch_table.h, src/remote/remote_protocol.h, src/remote/remote_protocol.c: Re-generate --- daemon/remote.c | 92 +++++++++++++++++++++++++++++++++++ daemon/remote_dispatch_args.h | 2 + daemon/remote_dispatch_prototypes.h | 16 ++++++ daemon/remote_dispatch_table.h | 10 ++++ src/remote/remote_driver.c | 87 +++++++++++++++++++++++++++++++++ src/remote/remote_protocol.c | 30 +++++++++++ src/remote/remote_protocol.h | 22 ++++++++ src/remote/remote_protocol.x | 19 +++++++- 8 files changed, 277 insertions(+), 1 deletions(-) diff --git a/daemon/remote.c b/daemon/remote.c index c5509ad..c9b714f 100644 --- a/daemon/remote.c +++ b/daemon/remote.c @@ -5580,7 +5580,99 @@ remoteDispatchNodeDeviceDestroy(struct qemud_server *server ATTRIBUTE_UNUSED, virNodeDeviceFree(dev); return 0; } +static int remoteDispatchStorageVolUpload(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *rerr, + remote_storage_vol_upload_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + int rv = -1; + struct qemud_client_stream *stream = NULL; + virStorageVolPtr vol; + + vol = get_nonnull_storage_vol(conn, args->vol); + if (vol == NULL) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + stream = remoteCreateClientStream(conn, hdr); + if (!stream) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + if (virStorageVolUpload(vol, stream->st, + args->offset, args->length, + args->flags) < 0) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + if (remoteAddClientStream(client, stream, 0) < 0) { + remoteDispatchConnError(rerr, conn); + virStreamAbort(stream->st); + goto cleanup; + } + + rv = 0; + +cleanup: + if (vol) + virStorageVolFree(vol); + if (stream && rv != 0) + remoteFreeClientStream(client, stream); + return rv; +} +static int remoteDispatchStorageVolDownload(struct qemud_server *server ATTRIBUTE_UNUSED, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *rerr, + remote_storage_vol_download_args *args, + void *ret ATTRIBUTE_UNUSED) +{ + int rv = -1; + struct qemud_client_stream *stream = NULL; + virStorageVolPtr vol; + + vol = get_nonnull_storage_vol (conn, args->vol); + if (vol == NULL) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + stream = remoteCreateClientStream(conn, hdr); + if (!stream) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + if (virStorageVolDownload(vol, stream->st, + args->offset, args->length, + args->flags) < 0) { + remoteDispatchConnError(rerr, conn); + goto cleanup; + } + + if (remoteAddClientStream(client, stream, 1) < 0) { + remoteDispatchConnError(rerr, conn); + virStreamAbort(stream->st); + goto cleanup; + } + + rv = 0; + +cleanup: + if (vol) + virStorageVolFree(vol); + if (stream && rv != 0) + remoteFreeClientStream(client, stream); + return rv; +} /*************************** diff --git a/daemon/remote_dispatch_args.h b/daemon/remote_dispatch_args.h index 57962d1..2022cee 100644 --- a/daemon/remote_dispatch_args.h +++ b/daemon/remote_dispatch_args.h @@ -172,3 +172,5 @@ remote_domain_open_console_args val_remote_domain_open_console_args; remote_domain_is_updated_args val_remote_domain_is_updated_args; remote_get_sysinfo_args val_remote_get_sysinfo_args; + remote_storage_vol_upload_args val_remote_storage_vol_upload_args; + remote_storage_vol_download_args val_remote_storage_vol_download_args; diff --git a/daemon/remote_dispatch_prototypes.h b/daemon/remote_dispatch_prototypes.h index e59701a..b219cbd 100644 --- a/daemon/remote_dispatch_prototypes.h +++ b/daemon/remote_dispatch_prototypes.h @@ -1506,6 +1506,14 @@ static int remoteDispatchStorageVolDelete( remote_error *err, remote_storage_vol_delete_args *args, void *ret); +static int remoteDispatchStorageVolDownload( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_storage_vol_download_args *args, + void *ret); static int remoteDispatchStorageVolDumpXml( struct qemud_server *server, struct qemud_client *client, @@ -1554,6 +1562,14 @@ static int remoteDispatchStorageVolLookupByPath( remote_error *err, remote_storage_vol_lookup_by_path_args *args, remote_storage_vol_lookup_by_path_ret *ret); +static int remoteDispatchStorageVolUpload( + struct qemud_server *server, + struct qemud_client *client, + virConnectPtr conn, + remote_message_header *hdr, + remote_error *err, + remote_storage_vol_upload_args *args, + void *ret); static int remoteDispatchStorageVolWipe( struct qemud_server *server, struct qemud_client *client, diff --git a/daemon/remote_dispatch_table.h b/daemon/remote_dispatch_table.h index 5d27390..de682cd 100644 --- a/daemon/remote_dispatch_table.h +++ b/daemon/remote_dispatch_table.h @@ -1022,3 +1022,13 @@ .args_filter = (xdrproc_t) xdr_remote_get_sysinfo_args, .ret_filter = (xdrproc_t) xdr_remote_get_sysinfo_ret, }, +{ /* StorageVolUpload => 204 */ + .fn = (dispatch_fn) remoteDispatchStorageVolUpload, + .args_filter = (xdrproc_t) xdr_remote_storage_vol_upload_args, + .ret_filter = (xdrproc_t) xdr_void, +}, +{ /* StorageVolDownload => 205 */ + .fn = (dispatch_fn) remoteDispatchStorageVolDownload, + .args_filter = (xdrproc_t) xdr_remote_storage_vol_download_args, + .ret_filter = (xdrproc_t) xdr_void, +}, diff --git a/src/remote/remote_driver.c b/src/remote/remote_driver.c index 0be9c56..210e6ab 100644 --- a/src/remote/remote_driver.c +++ b/src/remote/remote_driver.c @@ -9358,6 +9358,91 @@ done: return rv; } +static int +remoteStorageVolUpload(virStorageVolPtr vol, + virStreamPtr st, + unsigned long long offset, + unsigned long long length, + unsigned int flags) +{ + struct private_data *priv = vol->conn->privateData; + struct private_stream_data *privst = NULL; + int rv = -1; + remote_storage_vol_upload_args args; + + remoteDriverLock(priv); + + if (!(privst = remoteStreamOpen(st, 1, + REMOTE_PROC_STORAGE_VOL_UPLOAD, + priv->counter))) + goto done; + + st->driver = &remoteStreamDrv; + st->privateData = privst; + + make_nonnull_storage_vol(&args.vol, vol); + args.offset = offset; + args.length = length; + args.flags = flags; + + if (call (vol->conn, priv, 0, REMOTE_PROC_STORAGE_VOL_UPLOAD, + (xdrproc_t) xdr_remote_storage_vol_upload_args, (char *) &args, + (xdrproc_t) xdr_void, NULL) == -1) { + remoteStreamRelease(st); + goto done; + } + + rv = 0; + +done: + remoteDriverUnlock(priv); + + return rv; +} + + +static int +remoteStorageVolDownload(virStorageVolPtr vol, + virStreamPtr st, + unsigned long long offset, + unsigned long long length, + unsigned int flags) +{ + struct private_data *priv = vol->conn->privateData; + struct private_stream_data *privst = NULL; + int rv = -1; + remote_storage_vol_download_args args; + + remoteDriverLock(priv); + + if (!(privst = remoteStreamOpen(st, 1, + REMOTE_PROC_STORAGE_VOL_DOWNLOAD, + priv->counter))) + goto done; + + st->driver = &remoteStreamDrv; + st->privateData = privst; + + make_nonnull_storage_vol(&args.vol, vol); + args.offset = offset; + args.length = length; + args.flags = flags; + + if (call (vol->conn, priv, 0, REMOTE_PROC_STORAGE_VOL_DOWNLOAD, + (xdrproc_t) xdr_remote_storage_vol_download_args, (char *) &args, + (xdrproc_t) xdr_void, NULL) == -1) { + remoteStreamRelease(st); + goto done; + } + + rv = 0; + +done: + remoteDriverUnlock(priv); + + return rv; +} + static int remoteDomainOpenConsole(virDomainPtr dom, @@ -11021,6 +11106,8 @@ static virStorageDriver storage_driver = { .volLookupByPath = remoteStorageVolLookupByPath, .volCreateXML = remoteStorageVolCreateXML, .volCreateXMLFrom = remoteStorageVolCreateXMLFrom, + .volDownload = remoteStorageVolDownload, + .volUpload = remoteStorageVolUpload, .volDelete = remoteStorageVolDelete, .volWipe = remoteStorageVolWipe, .volGetInfo = remoteStorageVolGetInfo, diff --git a/src/remote/remote_protocol.c b/src/remote/remote_protocol.c index bae92ca..bad3d69 100644 --- a/src/remote/remote_protocol.c +++ b/src/remote/remote_protocol.c @@ -3758,6 +3758,36 @@ xdr_remote_domain_open_console_args (XDR *xdrs, remote_domain_open_console_args } bool_t +xdr_remote_storage_vol_upload_args (XDR *xdrs, remote_storage_vol_upload_args *objp) +{ + + if (!xdr_remote_nonnull_storage_vol (xdrs, &objp->vol)) + return FALSE; + if (!xdr_uint64_t (xdrs, &objp->offset)) + return FALSE; + if (!xdr_uint64_t (xdrs, &objp->length)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t +xdr_remote_storage_vol_download_args (XDR *xdrs, remote_storage_vol_download_args *objp) +{ + + if (!xdr_remote_nonnull_storage_vol (xdrs, &objp->vol)) + return FALSE; + if (!xdr_uint64_t (xdrs, &objp->offset)) + return FALSE; + if (!xdr_uint64_t (xdrs, &objp->length)) + return FALSE; + if (!xdr_u_int (xdrs, &objp->flags)) + return FALSE; + return TRUE; +} + +bool_t xdr_remote_procedure (XDR *xdrs, remote_procedure *objp) { diff --git a/src/remote/remote_protocol.h b/src/remote/remote_protocol.h index 46d526a..aec6c13 100644 --- a/src/remote/remote_protocol.h +++ b/src/remote/remote_protocol.h @@ -2124,6 +2124,22 @@ struct remote_domain_open_console_args { u_int flags; }; typedef struct remote_domain_open_console_args remote_domain_open_console_args; + +struct remote_storage_vol_upload_args { + remote_nonnull_storage_vol vol; + uint64_t offset; + uint64_t length; + u_int flags; +}; +typedef struct remote_storage_vol_upload_args remote_storage_vol_upload_args; + +struct remote_storage_vol_download_args { + remote_nonnull_storage_vol vol; + uint64_t offset; + uint64_t length; + u_int flags; +}; +typedef struct remote_storage_vol_download_args remote_storage_vol_download_args; #define REMOTE_PROGRAM 0x20008086 #define REMOTE_PROTOCOL_VERSION 1 @@ -2331,6 +2347,8 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_OPEN_CONSOLE = 201, REMOTE_PROC_DOMAIN_IS_UPDATED = 202, REMOTE_PROC_GET_SYSINFO = 203, + REMOTE_PROC_STORAGE_VOL_UPLOAD = 204, + REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 205, }; typedef enum remote_procedure remote_procedure; @@ -2705,6 +2723,8 @@ extern bool_t xdr_remote_domain_snapshot_current_ret (XDR *, remote_domain_snap extern bool_t xdr_remote_domain_revert_to_snapshot_args (XDR *, remote_domain_revert_to_snapshot_args*); extern bool_t xdr_remote_domain_snapshot_delete_args (XDR *, remote_domain_snapshot_delete_args*); extern bool_t xdr_remote_domain_open_console_args (XDR *, remote_domain_open_console_args*); +extern bool_t xdr_remote_storage_vol_upload_args (XDR *, remote_storage_vol_upload_args*); +extern bool_t xdr_remote_storage_vol_download_args (XDR *, remote_storage_vol_download_args*); extern bool_t xdr_remote_procedure (XDR *, remote_procedure*); extern bool_t xdr_remote_message_type (XDR *, remote_message_type*); extern bool_t xdr_remote_message_status (XDR *, remote_message_status*); @@ -3053,6 +3073,8 @@ extern bool_t xdr_remote_domain_snapshot_current_ret (); extern bool_t xdr_remote_domain_revert_to_snapshot_args (); extern bool_t xdr_remote_domain_snapshot_delete_args (); extern bool_t xdr_remote_domain_open_console_args (); +extern bool_t xdr_remote_storage_vol_upload_args (); +extern bool_t xdr_remote_storage_vol_download_args (); extern bool_t xdr_remote_procedure (); extern bool_t xdr_remote_message_type (); extern bool_t xdr_remote_message_status (); diff --git a/src/remote/remote_protocol.x b/src/remote/remote_protocol.x index e77aca1..21c469a 100644 --- a/src/remote/remote_protocol.x +++ b/src/remote/remote_protocol.x @@ -1874,6 +1874,21 @@ struct remote_domain_open_console_args { unsigned int flags; }; +struct remote_storage_vol_upload_args { + remote_nonnull_storage_vol vol; + unsigned hyper offset; + unsigned hyper length; + unsigned int flags; +}; + +struct remote_storage_vol_download_args { + remote_nonnull_storage_vol vol; + unsigned hyper offset; + unsigned hyper length; + unsigned int flags; +}; + + /*----- Protocol. -----*/ /* Define the program number, protocol version and procedure numbers here. */ @@ -2103,7 +2118,9 @@ enum remote_procedure { REMOTE_PROC_DOMAIN_OPEN_CONSOLE = 201, REMOTE_PROC_DOMAIN_IS_UPDATED = 202, - REMOTE_PROC_GET_SYSINFO = 203 + REMOTE_PROC_GET_SYSINFO = 203, + REMOTE_PROC_STORAGE_VOL_UPLOAD = 204, + REMOTE_PROC_STORAGE_VOL_DOWNLOAD = 205 /* * Notice how the entries are grouped in sets of 10 ? -- 1.7.4

On 02/22/2011 08:23 AM, Daniel P. Berrange wrote:
This patch series introduces two new APIs to allow data upload/download to/from storage volumes, using the streams APIs. Mostly I wrote this so I had an easier way to test the streams handling in my RPC patch series than using QEMU tunnelled migration code. Cole has expressed an interest in this kind of functionality in the past to allow virt-manager to upload kernel/initrd to remote hosts for provisioning guests.
Most of the code here is pretty boring & trivial. The first patch is the one that is remotely interesting. One might argue we should just spawn 'dd' instead of this, but dd has some annoying aspects (you can't control its write offset in units smaller than the block size) and this could be useful more broadly.
Hmm doesn't look like any of the CreateUpload stuff is implemented? Dropped from an earlier patch or just not finished yet? If that was implemented, I think it would be all virt-manager would really need to facilitate URL installation on remote machines. Thanks, Cole

On Mon, Mar 07, 2011 at 02:50:12PM -0500, Cole Robinson wrote:
On 02/22/2011 08:23 AM, Daniel P. Berrange wrote:
This patch series introduces two new APIs to allow data upload/download to/from storage volumes, using the streams APIs. Mostly I wrote this so I had an easier way to test the streams handling in my RPC patch series than using QEMU tunnelled migration code. Cole has expressed an interest in this kind of functionality in the past to allow virt-manager to upload kernel/initrd to remote hosts for provisioning guests.
Most of the code here is pretty boring & trivial. The first patch is the one that is remotely interesting. One might argue we should just spawn 'dd' instead of this, but dd has some annoying aspects (you can't control its write offset in units smaller than the block size) and this could be useful more broadly.
Hmm doesn't look like any of the CreateUpload stuff is implemented? Dropped from an earlier patch or just not finished yet? If that was implemented, I think it would be all virt-manager would really need to facilitate URL installation on remote machines.
Yes, I was going to do a CreateUpload method, but then the actual impl in the storage driver was going to touch quite alot of code. So I dropped it for since, since you could do the same thing by just doing a CreateXML followed by Upload operation. Provided you don't pre-allocate in the CreatXML method it wouldn't have any negative performance hit todo the two separate steps. Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|

On 03/08/2011 04:31 AM, Daniel P. Berrange wrote:
On Mon, Mar 07, 2011 at 02:50:12PM -0500, Cole Robinson wrote:
On 02/22/2011 08:23 AM, Daniel P. Berrange wrote:
This patch series introduces two new APIs to allow data upload/download to/from storage volumes, using the streams APIs. Mostly I wrote this so I had an easier way to test the streams handling in my RPC patch series than using QEMU tunnelled migration code. Cole has expressed an interest in this kind of functionality in the past to allow virt-manager to upload kernel/initrd to remote hosts for provisioning guests.
Most of the code here is pretty boring & trivial. The first patch is the one that is remotely interesting. One might argue we should just spawn 'dd' instead of this, but dd has some annoying aspects (you can't control its write offset in units smaller than the block size) and this could be useful more broadly.
Hmm doesn't look like any of the CreateUpload stuff is implemented? Dropped from an earlier patch or just not finished yet? If that was implemented, I think it would be all virt-manager would really need to facilitate URL installation on remote machines.
Yes, I was going to do a CreateUpload method, but then the actual impl in the storage driver was going to touch quite alot of code. So I dropped it for since, since you could do the same thing by just doing a CreateXML followed by Upload operation. Provided you don't pre-allocate in the CreatXML method it wouldn't have any negative performance hit todo the two separate steps.
So just create a 1 byte raw volume, then upload, and it will grow as needed? Should be fine. Thanks, Cole

On Tue, Mar 08, 2011 at 09:21:15AM -0500, Cole Robinson wrote:
On 03/08/2011 04:31 AM, Daniel P. Berrange wrote:
On Mon, Mar 07, 2011 at 02:50:12PM -0500, Cole Robinson wrote:
On 02/22/2011 08:23 AM, Daniel P. Berrange wrote:
This patch series introduces two new APIs to allow data upload/download to/from storage volumes, using the streams APIs. Mostly I wrote this so I had an easier way to test the streams handling in my RPC patch series than using QEMU tunnelled migration code. Cole has expressed an interest in this kind of functionality in the past to allow virt-manager to upload kernel/initrd to remote hosts for provisioning guests.
Most of the code here is pretty boring & trivial. The first patch is the one that is remotely interesting. One might argue we should just spawn 'dd' instead of this, but dd has some annoying aspects (you can't control its write offset in units smaller than the block size) and this could be useful more broadly.
Hmm doesn't look like any of the CreateUpload stuff is implemented? Dropped from an earlier patch or just not finished yet? If that was implemented, I think it would be all virt-manager would really need to facilitate URL installation on remote machines.
Yes, I was going to do a CreateUpload method, but then the actual impl in the storage driver was going to touch quite alot of code. So I dropped it for since, since you could do the same thing by just doing a CreateXML followed by Upload operation. Provided you don't pre-allocate in the CreatXML method it wouldn't have any negative performance hit todo the two separate steps.
So just create a 1 byte raw volume, then upload, and it will grow as needed? Should be fine.
For portability you want to create a volume of the size you are planning to upload, but set allocation==0, so it doesn't preallocate. This makes it work if you want to upload to non-grow on demand volumes like LVM, or block devices, or non-raw files Regards, Daniel -- |: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :| |: http://libvirt.org -o- http://virt-manager.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
participants (2)
-
Cole Robinson
-
Daniel P. Berrange