[libvirt] [PATCH v2 0/2] FS backend VolCreateXMLFrom implementation.

The following 2 patches implement virStorageVolCreateXMLFrom for the storage driver and the FS storage backend. Changes from v1: - Mark origvol as building in driver implementation - Make sure driver is locked when relocking pool object - Simplify sparse file detection with memcmp - Allocate read buffer on the heap. Thanks, Cole Cole Robinson (2): Storage driver implementation for CreateXMLFrom VolumeCreateXMLFrom FS storage backend implementation. src/storage_backend.h | 2 + src/storage_backend_fs.c | 247 +++++++++++++++++++++++++++++++++++++++------- src/storage_driver.c | 159 +++++++++++++++++++++++++++++- 3 files changed, 369 insertions(+), 39 deletions(-)

There is some funkiness here, since we are either dealing with 2 different pools (which means validation x 2) or the same pool, where we have to be careful not to deadlock. --- src/storage_backend.h | 2 + src/storage_driver.c | 159 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 157 insertions(+), 4 deletions(-) diff --git a/src/storage_backend.h b/src/storage_backend.h index c9c1e35..7bf8814 100644 --- a/src/storage_backend.h +++ b/src/storage_backend.h @@ -38,6 +38,7 @@ typedef int (*virStorageBackendBuildVol)(virConnectPtr conn, virStorageVolDefPtr typedef int (*virStorageBackendCreateVol)(virConnectPtr conn, virStoragePoolObjPtr pool, virStorageVolDefPtr vol); typedef int (*virStorageBackendRefreshVol)(virConnectPtr conn, virStoragePoolObjPtr pool, virStorageVolDefPtr vol); typedef int (*virStorageBackendDeleteVol)(virConnectPtr conn, virStoragePoolObjPtr pool, virStorageVolDefPtr vol, unsigned int flags); +typedef int (*virStorageBackendBuildVolFrom)(virConnectPtr conn, virStorageVolDefPtr origvol, virStorageVolDefPtr newvol, unsigned int flags); typedef struct _virStorageBackend virStorageBackend; @@ -54,6 +55,7 @@ struct _virStorageBackend { virStorageBackendDeletePool deletePool; virStorageBackendBuildVol buildVol; + virStorageBackendBuildVolFrom buildVolFrom; virStorageBackendCreateVol createVol; virStorageBackendRefreshVol refreshVol; virStorageBackendDeleteVol deleteVol; diff --git a/src/storage_driver.c b/src/storage_driver.c index b72f0a0..807fd1d 100644 --- a/src/storage_driver.c +++ b/src/storage_driver.c @@ -1303,6 +1303,160 @@ cleanup: return ret; } +static virStorageVolPtr +storageVolumeCreateXMLFrom(virStoragePoolPtr obj, + const char *xmldesc, + virStorageVolPtr vobj, + unsigned int flags ATTRIBUTE_UNUSED) { + virStorageDriverStatePtr driver = obj->conn->storagePrivateData; + virStoragePoolObjPtr pool, origpool = NULL; + virStorageBackendPtr backend; + virStorageVolDefPtr origvol = NULL, newvol = NULL; + virStorageVolPtr ret = NULL, volobj = NULL; + int buildret, diffpool; + + diffpool = !STREQ(obj->name, vobj->pool); + + storageDriverLock(driver); + pool = virStoragePoolObjFindByUUID(&driver->pools, obj->uuid); + if (diffpool) + origpool = virStoragePoolObjFindByName(&driver->pools, vobj->pool); + else + origpool = pool; + storageDriverUnlock(driver); + + if (!pool) { + virStorageReportError(obj->conn, VIR_ERR_INVALID_STORAGE_POOL, + "%s", _("no storage pool with matching uuid")); + goto cleanup; + } + + if (diffpool && !origpool) { + virStorageReportError(obj->conn, VIR_ERR_INVALID_STORAGE_POOL, + "%s", _("no storage pool with matching name")); + goto cleanup; + } + + if (!virStoragePoolObjIsActive(pool)) { + virStorageReportError(obj->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("storage pool is not active")); + goto cleanup; + } + + if (diffpool && !virStoragePoolObjIsActive(origpool)) { + virStorageReportError(obj->conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("storage pool is not active")); + goto cleanup; + } + + if ((backend = virStorageBackendForType(pool->def->type)) == NULL) + goto cleanup; + + origvol = virStorageVolDefFindByName(origpool, vobj->name); + if (!origvol) { + virStorageReportError(obj->conn, VIR_ERR_INVALID_STORAGE_POOL, + "%s", _("no storage vol with matching name")); + goto cleanup; + } + + newvol = virStorageVolDefParse(obj->conn, pool->def, xmldesc, NULL); + if (newvol == NULL) + goto cleanup; + + if (virStorageVolDefFindByName(pool, newvol->name)) { + virStorageReportError(obj->conn, VIR_ERR_INVALID_STORAGE_POOL, + _("storage volume name '%s' already in use."), + newvol->name); + goto cleanup; + } + + /* Is there ever a valid case for this? */ + if (newvol->capacity < origvol->capacity) + newvol->capacity = origvol->capacity; + + if (!backend->buildVolFrom) { + virStorageReportError(obj->conn, VIR_ERR_NO_SUPPORT, + "%s", _("storage pool does not support volume creation from an existing volume")); + goto cleanup; + } + + if (origvol->building) { + virStorageReportError(obj->conn, VIR_ERR_INTERNAL_ERROR, + _("volume '%s' is still being allocated."), + origvol->name); + goto cleanup; + } + + if (backend->refreshVol && + backend->refreshVol(obj->conn, pool, origvol) < 0) + goto cleanup; + + if (VIR_REALLOC_N(pool->volumes.objs, + pool->volumes.count+1) < 0) { + virReportOOMError(obj->conn); + goto cleanup; + } + + /* 'Define' the new volume so we get async progress reporting */ + if (backend->createVol(obj->conn, pool, newvol) < 0) { + goto cleanup; + } + + pool->volumes.objs[pool->volumes.count++] = newvol; + volobj = virGetStorageVol(obj->conn, pool->def->name, newvol->name, + newvol->key); + + /* Drop the pool lock during volume allocation */ + pool->asyncjobs++; + origvol->building = 1; + newvol->building = 1; + virStoragePoolObjUnlock(pool); + + if (diffpool) { + origpool->asyncjobs++; + virStoragePoolObjUnlock(origpool); + } + + buildret = backend->buildVolFrom(obj->conn, newvol, origvol, flags); + + storageDriverLock(driver); + virStoragePoolObjLock(pool); + if (diffpool) + virStoragePoolObjLock(origpool); + storageDriverUnlock(driver); + + origvol->building = 0; + newvol->building = 0; + newvol = NULL; + pool->asyncjobs--; + + if (diffpool) { + origpool->asyncjobs--; + virStoragePoolObjUnlock(origpool); + origpool = NULL; + } + + if (buildret < 0) { + virStoragePoolObjUnlock(pool); + storageVolumeDelete(volobj, 0); + pool = NULL; + goto cleanup; + } + + ret = volobj; + volobj = NULL; + +cleanup: + if (volobj) + virUnrefStorageVol(volobj); + virStorageVolDefFree(newvol); + if (pool) + virStoragePoolObjUnlock(pool); + if (diffpool && origpool) + virStoragePoolObjUnlock(origpool); + return ret; +} + static int storageVolumeDelete(virStorageVolPtr obj, unsigned int flags) { @@ -1523,10 +1677,6 @@ cleanup: return ret; } - - - - static virStorageDriver storageDriver = { .name = "storage", .open = storageOpen, @@ -1558,6 +1708,7 @@ static virStorageDriver storageDriver = { .volLookupByKey = storageVolumeLookupByKey, .volLookupByPath = storageVolumeLookupByPath, .volCreateXML = storageVolumeCreateXML, + .volCreateXMLFrom = storageVolumeCreateXMLFrom, .volDelete = storageVolumeDelete, .volGetInfo = storageVolumeGetInfo, .volGetXMLDesc = storageVolumeGetXMLDesc, -- 1.6.2.2

On Mon, May 18, 2009 at 01:30:32PM -0400, Cole Robinson wrote:
There is some funkiness here, since we are either dealing with 2 different pools (which means validation x 2) or the same pool, where we have to be careful not to deadlock.
ACK, this looks good now. Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

Add an extra 'inputvol' parameter to the file volume building routines. This allows us to easily reuse the duplicate functionality. --- src/storage_backend_fs.c | 247 +++++++++++++++++++++++++++++++++++++++------- 1 files changed, 212 insertions(+), 35 deletions(-) diff --git a/src/storage_backend_fs.c b/src/storage_backend_fs.c index fac43df..4d6f9b5 100644 --- a/src/storage_backend_fs.c +++ b/src/storage_backend_fs.c @@ -62,7 +62,9 @@ static int qcowXGetBackingStore(virConnectPtr, char **, static int vmdk4GetBackingStore(virConnectPtr, char **, const unsigned char *, size_t); -typedef int (*createFile)(virConnectPtr conn, virStorageVolDefPtr vol); +typedef int (*createFile)(virConnectPtr conn, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol); static int track_allocation_progress = 0; @@ -1014,15 +1016,29 @@ virStorageBackendFileSystemVolCreate(virConnectPtr conn, } static int createRaw(virConnectPtr conn, - virStorageVolDefPtr vol) { - int fd; + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol) { + int fd = -1; + int inputfd = -1; + int ret = -1; + unsigned long long remain; + char *buf = NULL; if ((fd = open(vol->target.path, O_RDWR | O_CREAT | O_EXCL, vol->target.perms.mode)) < 0) { virReportSystemError(conn, errno, _("cannot create path '%s'"), vol->target.path); - return -1; + goto cleanup; + } + + if (inputvol) { + if ((inputfd = open(inputvol->target.path, O_RDONLY)) < 0) { + virReportSystemError(conn, errno, + _("could not open input path '%s'"), + inputvol->target.path); + goto cleanup; + } } /* Seek to the final size, so the capacity is available upfront @@ -1031,15 +1047,66 @@ static int createRaw(virConnectPtr conn, virReportSystemError(conn, errno, _("cannot extend file '%s'"), vol->target.path); - close(fd); - return -1; + goto cleanup; + } + + remain = vol->capacity; + + if (inputfd != -1) { + int amtread = -1; + size_t bytes = 1024 * 1024; + char zerobuf[512]; + + bzero(&zerobuf, sizeof(zerobuf)); + + if (VIR_ALLOC_N(buf, bytes) < 0) { + virReportOOMError(conn); + goto cleanup; + } + + while (amtread != 0) { + int amtleft; + + if (remain < bytes) + bytes = remain; + + if ((amtread = saferead(inputfd, buf, bytes)) < 0) { + virReportSystemError(conn, errno, + _("failed reading from file '%s'"), + inputvol->target.path); + goto cleanup; + } + remain -= amtread; + + /* Loop over amt read in 512 byte increments, looking for sparse + * blocks */ + amtleft = amtread; + do { + int interval = ((512 > amtleft) ? amtleft : 512); + int offset = amtread - amtleft; + + if (memcmp(buf+offset, zerobuf, interval) == 0) { + if (lseek(fd, interval, SEEK_CUR) < 0) { + virReportSystemError(conn, errno, + _("cannot extend file '%s'"), + vol->target.path); + goto cleanup; + } + } else if (safewrite(fd, buf+offset, interval) < 0) { + virReportSystemError(conn, errno, + _("failed writing to file '%s'"), + vol->target.path); + goto cleanup; + + } + } while ((amtleft -= 512) > 0); + } } /* Pre-allocate any data if requested */ /* XXX slooooooooooooooooow on non-extents-based file systems */ - if (vol->allocation) { + if (remain) { if (track_allocation_progress) { - unsigned long long remain = vol->allocation; while (remain) { /* Allocate in chunks of 512MiB: big-enough chunk @@ -1056,20 +1123,18 @@ static int createRaw(virConnectPtr conn, virReportSystemError(conn, r, _("cannot fill file '%s'"), vol->target.path); - close(fd); - return -1; + goto cleanup; } remain -= bytes; } } else { /* No progress bars to be shown */ int r; - if ((r = safezero(fd, 0, 0, vol->allocation)) != 0) { + if ((r = safezero(fd, 0, 0, remain)) != 0) { virReportSystemError(conn, r, _("cannot fill file '%s'"), vol->target.path); - close(fd); - return -1; + goto cleanup; } } } @@ -1078,14 +1143,39 @@ static int createRaw(virConnectPtr conn, virReportSystemError(conn, errno, _("cannot close file '%s'"), vol->target.path); - return -1; + goto cleanup; } + fd = -1; - return 0; + if (inputfd != -1 && close(inputfd) < 0) { + virReportSystemError(conn, errno, + _("cannot close file '%s'"), + inputvol->target.path); + goto cleanup; + } + inputfd = -1; + + ret = 0; +cleanup: + if (fd != -1) + close(fd); + if (inputfd != -1) + close(inputfd); + VIR_FREE(buf); + + return ret; } static int createFileDir(virConnectPtr conn, - virStorageVolDefPtr vol) { + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol) { + if (inputvol) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", + _("cannot copy from volume to a directory volume")); + return -1; + } + if (mkdir(vol->target.path, vol->target.perms.mode) < 0) { virReportSystemError(conn, errno, _("cannot create path '%s'"), @@ -1098,20 +1188,56 @@ static int createFileDir(virConnectPtr conn, #if HAVE_QEMU_IMG static int createQemuImg(virConnectPtr conn, - virStorageVolDefPtr vol) { + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol) { + char size[100]; + const char *type = virStorageVolFormatFileSystemTypeToString(vol->target.format); const char *backingType = vol->backingStore.path ? virStorageVolFormatFileSystemTypeToString(vol->backingStore.format) : NULL; - char size[100]; + + const char *inputBackingPath = (inputvol ? inputvol->backingStore.path + : NULL); + const char *inputPath = inputvol ? inputvol->target.path : NULL; + /* Treat input block devices as 'raw' format */ + const char *inputType = inputPath ? + virStorageVolFormatFileSystemTypeToString(inputvol->type == VIR_STORAGE_VOL_BLOCK ? VIR_STORAGE_VOL_FILE_RAW : inputvol->target.format) : + NULL; + const char **imgargv; const char *imgargvnormal[] = { - QEMU_IMG, "create", "-f", type, vol->target.path, size, NULL, + QEMU_IMG, "create", + "-f", type, + vol->target.path, + size, + NULL, }; /* XXX including "backingType" here too, once QEMU accepts * the patches to specify it. It'll probably be -F backingType */ const char *imgargvbacking[] = { - QEMU_IMG, "create", "-f", type, "-b", vol->backingStore.path, vol->target.path, size, NULL, + QEMU_IMG, "create", + "-f", type, + "-b", vol->backingStore.path, + vol->target.path, + size, + NULL, }; + const char *convargv[] = { + QEMU_IMG, "convert", + "-f", inputType, + "-O", type, + inputPath, + vol->target.path, + NULL, + }; + + if (inputvol) { + imgargv = convargv; + } else if (vol->backingStore.path) { + imgargv = imgargvbacking; + } else { + imgargv = imgargvnormal; + } if (type == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, @@ -1119,9 +1245,27 @@ static int createQemuImg(virConnectPtr conn, vol->target.format); return -1; } - if (vol->backingStore.path == NULL) { - imgargv = imgargvnormal; - } else { + if (inputvol && inputType == NULL) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + _("unknown storage vol type %d"), + inputvol->target.format); + return -1; + } + + if (vol->backingStore.path) { + + /* XXX: Not strictly required: qemu-img has an option a different + * backing store, not really sure what use it serves though, and it + * may cause issues with lvm. Untested essentially. + */ + if (!inputBackingPath || + !STREQ(inputBackingPath, vol->backingStore.path)) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", _("a different backing store can not " + "be specified.")); + return -1; + } + if (backingType == NULL) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, _("unknown storage vol backing store type %d"), @@ -1134,8 +1278,6 @@ static int createQemuImg(virConnectPtr conn, vol->backingStore.path); return -1; } - - imgargv = imgargvbacking; } /* Size in KB */ @@ -1154,10 +1296,18 @@ static int createQemuImg(virConnectPtr conn, * with a partially functional qcow-create. Go figure ??!? */ static int createQemuCreate(virConnectPtr conn, - virStorageVolDefPtr vol) { + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol) { char size[100]; const char *imgargv[4]; + if (inputvol) { + virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, + "%s", + _("cannot copy from volume with qcow-create")); + return -1; + } + if (vol->target.format != VIR_STORAGE_VOL_FILE_QCOW2) { virStorageReportError(conn, VIR_ERR_INTERNAL_ERROR, _("unsupported storage vol type %d"), @@ -1187,19 +1337,22 @@ static int createQemuCreate(virConnectPtr conn, } #endif /* HAVE_QEMU_IMG, elif HAVE_QCOW_CREATE */ -/** - * Allocate a new file as a volume. This is either done directly - * for raw/sparse files, or by calling qemu-img/qcow-create for - * special kinds of files - */ static int -virStorageBackendFileSystemVolBuild(virConnectPtr conn, - virStorageVolDefPtr vol) +_virStorageBackendFileSystemVolBuild(virConnectPtr conn, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol) { int fd; createFile create_func; - if (vol->target.format == VIR_STORAGE_VOL_FILE_RAW) { + if (vol->target.format == VIR_STORAGE_VOL_FILE_RAW && + (!inputvol || + (inputvol->type == VIR_STORAGE_VOL_BLOCK || + inputvol->target.format == VIR_STORAGE_VOL_FILE_RAW))) { + /* Raw file creation + * Raw -> Raw copying + * Block dev -> Raw copying + */ create_func = createRaw; } else if (vol->target.format == VIR_STORAGE_VOL_FILE_DIR) { create_func = createFileDir; @@ -1216,7 +1369,7 @@ virStorageBackendFileSystemVolBuild(virConnectPtr conn, #endif } - if (create_func(conn, vol) < 0) + if (create_func(conn, vol, inputvol) < 0) return -1; if ((fd = open(vol->target.path, O_RDONLY)) < 0) { @@ -1262,6 +1415,27 @@ virStorageBackendFileSystemVolBuild(virConnectPtr conn, return 0; } +/** + * Allocate a new file as a volume. This is either done directly + * for raw/sparse files, or by calling qemu-img/qcow-create for + * special kinds of files + */ +static int +virStorageBackendFileSystemVolBuild(virConnectPtr conn, + virStorageVolDefPtr vol) { + return _virStorageBackendFileSystemVolBuild(conn, vol, NULL); +} + +/* + * Create a storage vol using 'inputvol' as input + */ +static int +virStorageBackendFileSystemVolBuildFrom(virConnectPtr conn, + virStorageVolDefPtr vol, + virStorageVolDefPtr inputvol, + unsigned int flags ATTRIBUTE_UNUSED) { + return _virStorageBackendFileSystemVolBuild(conn, vol, inputvol); +} /** * Remove a volume - just unlinks for now @@ -1304,6 +1478,7 @@ virStorageBackend virStorageBackendDirectory = { .refreshPool = virStorageBackendFileSystemRefresh, .deletePool = virStorageBackendFileSystemDelete, .buildVol = virStorageBackendFileSystemVolBuild, + .buildVolFrom = virStorageBackendFileSystemVolBuildFrom, .createVol = virStorageBackendFileSystemVolCreate, .refreshVol = virStorageBackendFileSystemVolRefresh, .deleteVol = virStorageBackendFileSystemVolDelete, @@ -1319,6 +1494,7 @@ virStorageBackend virStorageBackendFileSystem = { .stopPool = virStorageBackendFileSystemStop, .deletePool = virStorageBackendFileSystemDelete, .buildVol = virStorageBackendFileSystemVolBuild, + .buildVolFrom = virStorageBackendFileSystemVolBuildFrom, .createVol = virStorageBackendFileSystemVolCreate, .refreshVol = virStorageBackendFileSystemVolRefresh, .deleteVol = virStorageBackendFileSystemVolDelete, @@ -1333,6 +1509,7 @@ virStorageBackend virStorageBackendNetFileSystem = { .stopPool = virStorageBackendFileSystemStop, .deletePool = virStorageBackendFileSystemDelete, .buildVol = virStorageBackendFileSystemVolBuild, + .buildVolFrom = virStorageBackendFileSystemVolBuildFrom, .createVol = virStorageBackendFileSystemVolCreate, .refreshVol = virStorageBackendFileSystemVolRefresh, .deleteVol = virStorageBackendFileSystemVolDelete, -- 1.6.2.2

On Mon, May 18, 2009 at 01:30:33PM -0400, Cole Robinson wrote:
Add an extra 'inputvol' parameter to the file volume building routines. This allows us to easily reuse the duplicate functionality.
ACK Now you just have to implement it for LVM, and disk partitions too ;-P Daniel -- |: Red Hat, Engineering, London -o- http://people.redhat.com/berrange/ :| |: http://libvirt.org -o- http://virt-manager.org -o- http://ovirt.org :| |: http://autobuild.org -o- http://search.cpan.org/~danberr/ :| |: GnuPG: 7D3B9505 -o- F3C9 553F A1DA 4AC2 5648 23C1 B3DF F742 7D3B 9505 :|

On Mon, May 18, 2009 at 01:30:31PM -0400, Cole Robinson wrote:
The following 2 patches implement virStorageVolCreateXMLFrom for the storage driver and the FS storage backend.
Changes from v1: - Mark origvol as building in driver implementation - Make sure driver is locked when relocking pool object - Simplify sparse file detection with memcmp - Allocate read buffer on the heap.
Thanks, Cole
Cole Robinson (2): Storage driver implementation for CreateXMLFrom VolumeCreateXMLFrom FS storage backend implementation.
Okidoc, both patches looks fine, ACK ! Daniel -- Daniel Veillard | libxml Gnome XML XSLT toolkit http://xmlsoft.org/ daniel@veillard.com | Rpmfind RPM search engine http://rpmfind.net/ http://veillard.com/ | virtualization library http://libvirt.org/

Daniel Veillard wrote:
On Mon, May 18, 2009 at 01:30:31PM -0400, Cole Robinson wrote:
The following 2 patches implement virStorageVolCreateXMLFrom for the storage driver and the FS storage backend.
Changes from v1: - Mark origvol as building in driver implementation - Make sure driver is locked when relocking pool object - Simplify sparse file detection with memcmp - Allocate read buffer on the heap.
Thanks, Cole
Cole Robinson (2): Storage driver implementation for CreateXMLFrom VolumeCreateXMLFrom FS storage backend implementation.
Okidoc, both patches looks fine, ACK !
Daniel
Thanks, I've pushed both now. - Cole
participants (3)
-
Cole Robinson
-
Daniel P. Berrange
-
Daniel Veillard