def _deleteImage(dom, imgUUID, postZero, discard): """This ancillary function will be removed. Replaces Image.delete() in Image.[copyCollapsed(), move(), multimove()]. """ allVols = dom.getAllVolumes() imgVols = sd.getVolsOfImage(allVols, imgUUID) if not imgVols: log.warning("No volumes found for image %s. %s", imgUUID, allVols) return elif postZero: dom.zeroImage(dom.sdUUID, imgUUID, imgVols, discard) else: dom.deleteImage(dom.sdUUID, imgUUID, imgVols)
def reconcileVolumeChain(self, sdUUID, imgUUID, leafVolUUID): """ Discover and return the actual volume chain of an offline image according to the qemu-img info command and synchronize volume metadata. """ # Prepare volumes dom = sdCache.produce(sdUUID) allVols = dom.getAllVolumes() imgVolumes = sd.getVolsOfImage(allVols, imgUUID).keys() dom.activateVolumes(imgUUID, imgVolumes) # Walk the volume chain using qemu-img. Not safe for running VMs actualVolumes = [] volUUID = leafVolUUID while volUUID is not None: actualVolumes.insert(0, volUUID) vol = dom.produceVolume(imgUUID, volUUID) qemuImgFormat = sc.fmt2str(vol.getFormat()) imgInfo = qemuimg.info(vol.volumePath, qemuImgFormat) backingFile = imgInfo.get('backingfile') if backingFile is not None: volUUID = os.path.basename(backingFile) else: volUUID = None # A merge of the active layer has copy and pivot phases. # During copy, data is copied from the leaf into its parent. Writes # are mirrored to both volumes. So even after copying is complete the # volumes will remain consistent. Finally, the VM is pivoted from the # old leaf to the new leaf and mirroring to the old leaf ceases. During # mirroring and before pivoting, we mark the old leaf ILLEGAL so we # know it's safe to delete in case the operation is interrupted. vol = dom.produceVolume(imgUUID, leafVolUUID) if vol.getLegality() == sc.ILLEGAL_VOL: actualVolumes.remove(leafVolUUID) # Now that we know the correct volume chain, sync the storge metadata self.syncVolumeChain(sdUUID, imgUUID, actualVolumes[-1], actualVolumes) dom.deactivateImage(imgUUID) return actualVolumes
def v3DomainConverter(repoPath, hostId, domain, isMsd): targetVersion = 3 currentVersion = domain.getVersion() log.debug("Starting conversion for domain %s from version %s " "to version %s", domain.sdUUID, currentVersion, targetVersion) # For block domains if we're upgrading from version 0 we need to first # upgrade to version 2 and then proceed to upgrade to version 3. if domain.getStorageType() in sd.BLOCK_DOMAIN_TYPES: if currentVersion != 2: log.debug("Unsupported conversion from version %s to version %s", currentVersion, targetVersion) raise se.UnsupportedDomainVersion(currentVersion) if domain.getStorageType() in sd.FILE_DOMAIN_TYPES: log.debug("Setting permissions for domain %s", domain.sdUUID) domain.setMetadataPermissions() log.debug("Initializing the new cluster lock for domain %s", domain.sdUUID) newClusterLock = domain._makeClusterLock(targetVersion) newClusterLock.initLock(domain.getClusterLease()) log.debug("Acquiring the host id %s for domain %s", hostId, domain.sdUUID) newClusterLock.acquireHostId(hostId, wait=True) def v3UpgradeVolumePermissions(vol): log.debug("Changing permissions (read-write) for the " "volume %s", vol.volUUID) # Using the internal call to skip the domain V3 validation, # see volume.setrw for more details. vol._setrw(True) def v3ReallocateMetadataSlot(domain, allVolumes): if not domain.getStorageType() in sd.BLOCK_DOMAIN_TYPES: log.debug("The metadata reallocation check is not needed for " "domain %s", domain.sdUUID) return leasesSize = domain.getLeasesFileSize() // MiB metaMaxSlot = leasesSize - blockSD.RESERVED_LEASES - 1 log.debug("Starting metadata reallocation check for domain %s with " "metaMaxSlot %s (leases volume size %s)", domain.sdUUID, metaMaxSlot, leasesSize) # Updating the volumes one by one, doesn't require activation for volUUID, (imgUUIDs, parentUUID) in six.iteritems(allVolumes): # The first imgUUID is the imgUUID of the template or the only # imgUUID where the volUUID appears. vol = domain.produceVolume(imgUUIDs[0], volUUID) metaSlot = vol.getMetaSlot() if metaSlot < metaMaxSlot: continue log.debug("Reallocating metadata slot %s for volume %s", metaSlot, vol.volUUID) metaContent = vol.getMetadata() with domain.acquireVolumeMetadataSlot(vol.volUUID) as newMetaSlot: if newMetaSlot > metaMaxSlot: raise se.NoSpaceLeftOnDomain(domain.sdUUID) log.debug("Copying metadata for volume %s to the new slot %s", vol.volUUID, newMetaSlot) vol.createMetadata((domain.sdUUID, newMetaSlot), metaContent) log.debug("Switching the metadata slot for volume %s to %s", vol.volUUID, newMetaSlot) vol.changeVolumeTag(sc.TAG_PREFIX_MD, str(newMetaSlot)) try: if isMsd: log.debug("Acquiring the cluster lock for domain %s with " "host id: %s", domain.sdUUID, hostId) newClusterLock.acquire(hostId, domain.getClusterLease()) allVolumes = domain.getAllVolumes() allImages = {} # {images: parent_image} # Few vdsm releases (4.9 prior 496c0c3, BZ#732980) generated metadata # offsets higher than 1947 (LEASES_SIZE - RESERVED_LEASES - 1). # This function reallocates such slots to free ones in order to use the # same offsets for the volume resource leases. v3ReallocateMetadataSlot(domain, allVolumes) # Updating the volumes one by one, doesn't require activation for volUUID, (imgUUIDs, parentUUID) in six.iteritems(allVolumes): log.debug("Converting volume: %s", volUUID) # Maintaining a dict of {images: parent_image} allImages.update((i, None) for i in imgUUIDs) # The first imgUUID is the imgUUID of the template or the # only imgUUID where the volUUID appears. vol = domain.produceVolume(imgUUIDs[0], volUUID) v3UpgradeVolumePermissions(vol) log.debug("Creating the volume lease for %s", volUUID) metaId = vol.getMetadataId() vol.newVolumeLease(metaId, domain.sdUUID, volUUID) # If this volume is used as a template let's update the other # volume's permissions and share the volume lease (at the moment # of this writing this is strictly needed only on file domains). for imgUUID in imgUUIDs[1:]: allImages[imgUUID] = imgUUIDs[0] dstVol = domain.produceVolume(imgUUID, volUUID) v3UpgradeVolumePermissions(dstVol) # Sharing the original template volume lease file with the # same volume in the other images. vol._shareLease(dstVol.imagePath) # Updating the volumes to fix BZ#811880, here the activation is # required and to be more effective we do it by image (one shot). for imgUUID in allImages: log.debug("Converting image: %s", imgUUID) # XXX: The only reason to prepare the image is to verify the volume # virtual size configured in the qcow2 header (BZ#811880). # The activation and deactivation of the LVs might lead to a race # with the creation or destruction of a VM on the SPM. # # The analyzed scenarios are: # 1. A VM is currently running on the image we are preparing. # This is safe because the prepare is superfluous and the # teardown is going to fail (the LVs are in use by the VM) # 2. A VM using this image is started after the prepare. # This is safe because the prepare issued by the VM is # superfluous and our teardown is going to fail (the LVs are # in use by the VM). # 3. A VM using this image is started and the teardown is # executed before that the actual QEMU process is started. # This is safe because the VM is going to fail (the engine # should retry later) but there is no risk of corruption. # 4. A VM using this image is destroyed after the prepare and # before reading the image size. # This is safe because the upgrade process will fail (unable # to read the image virtual size) and it can be restarted # later. imgVolumes = sd.getVolsOfImage(allVolumes, imgUUID).keys() try: try: domain.activateVolumes(imgUUID, imgVolumes) except (OSError, se.CannotActivateLogicalVolumes): log.error("Image %s can't be activated.", imgUUID, exc_info=True) for volUUID in imgVolumes: try: vol = domain.produceVolume(imgUUID, volUUID) _v3_reset_meta_volsize(vol) # BZ#811880 except cmdutils.Error: log.error("It is not possible to read the volume %s " "using qemu-img, the content looks damaged", volUUID, exc_info=True) except se.VolumeDoesNotExist: log.error("It is not possible to prepare the image %s, the " "volume chain looks damaged", imgUUID, exc_info=True) except se.MetaDataKeyNotFoundError: log.error("It is not possible to prepare the image %s, the " "volume metadata looks damaged", imgUUID, exc_info=True) finally: try: domain.deactivateImage(imgUUID) except se.CannotDeactivateLogicalVolume: log.warning("Unable to teardown the image %s, this error " "is not critical since the volume might be in" " use", imgUUID, exc_info=True) log.debug("Finalizing the storage domain upgrade from version %s to " "version %s for domain %s", currentVersion, targetVersion, domain.sdUUID) domain.setMetaParam(sd.DMDK_VERSION, targetVersion) except: if isMsd: try: log.error("Releasing the cluster lock for domain %s with " "host id: %s", domain.sdUUID, hostId) newClusterLock.release(domain.getClusterLease()) except: log.error("Unable to release the cluster lock for domain " "%s with host id: %s", domain.sdUUID, hostId, exc_info=True) try: log.error("Releasing the host id %s for domain %s", hostId, domain.sdUUID) newClusterLock.releaseHostId(hostId, wait=True, unused=True) except: log.error("Unable to release the host id %s for domain %s", hostId, domain.sdUUID, exc_info=True) raise # Releasing the old cluster lock (safelease). This lock was acquired # by the regular startSpm flow and now must be replaced by the new one # (sanlock). Since we are already at the end of the process (no way to # safely rollback to version 0 or 2) we should ignore the cluster lock # release errors. if isMsd: try: domain._clusterLock.release(domain.getClusterLease()) except: log.error("Unable to release the old cluster lock for domain " "%s ", domain.sdUUID, exc_info=True) # This is not strictly required since the domain object is destroyed right # after the upgrade but let's not make assumptions about future behaviors log.debug("Switching the cluster lock for domain %s", domain.sdUUID) domain._clusterLock = newClusterLock
def v3DomainConverter(repoPath, hostId, domain, isMsd): targetVersion = 3 currentVersion = domain.getVersion() log.debug("Starting conversion for domain %s from version %s " "to version %s", domain.sdUUID, currentVersion, targetVersion) targetVersion = 3 currentVersion = domain.getVersion() # For block domains if we're upgrading from version 0 we need to first # upgrade to version 2 and then proceed to upgrade to version 3. if domain.getStorageType() in sd.BLOCK_DOMAIN_TYPES: if currentVersion == 0: log.debug("Upgrading domain %s from version %s to version 2", domain.sdUUID, currentVersion) v2DomainConverter(repoPath, hostId, domain, isMsd) currentVersion = domain.getVersion() if currentVersion != 2: log.debug("Unsupported conversion from version %s to version %s", currentVersion, targetVersion) raise se.UnsupportedDomainVersion(currentVersion) if domain.getStorageType() in sd.FILE_DOMAIN_TYPES: log.debug("Setting permissions for domain %s", domain.sdUUID) domain.setMetadataPermissions() log.debug("Initializing the new cluster lock for domain %s", domain.sdUUID) newClusterLock = domain._makeClusterLock(targetVersion) newClusterLock.initLock(domain.getClusterLease()) log.debug("Acquiring the host id %s for domain %s", hostId, domain.sdUUID) newClusterLock.acquireHostId(hostId, async=False) def v3UpgradeVolumePermissions(vol): log.debug("Changing permissions (read-write) for the " "volume %s", vol.volUUID) # Using the internal call to skip the domain V3 validation, # see volume.setrw for more details. vol._setrw(True) def v3ReallocateMetadataSlot(domain, allVolumes): if not domain.getStorageType() in sd.BLOCK_DOMAIN_TYPES: log.debug("The metadata reallocation check is not needed for " "domain %s", domain.sdUUID) return leasesSize = domain.getLeasesFileSize() / constants.MEGAB metaMaxSlot = leasesSize - blockSD.RESERVED_LEASES - 1 log.debug("Starting metadata reallocation check for domain %s with " "metaMaxSlot %s (leases volume size %s)", domain.sdUUID, metaMaxSlot, leasesSize) # Updating the volumes one by one, doesn't require activation for volUUID, (imgUUIDs, parentUUID) in allVolumes.iteritems(): # The first imgUUID is the imgUUID of the template or the only # imgUUID where the volUUID appears. vol = domain.produceVolume(imgUUIDs[0], volUUID) metaSlot = vol.getMetaSlot() if metaSlot < metaMaxSlot: continue log.debug("Reallocating metadata slot %s for volume %s", metaSlot, vol.volUUID) metaContent = vol.getMetadata() with domain.acquireVolumeMetadataSlot(vol.volUUID) as newMetaSlot: if newMetaSlot > metaMaxSlot: raise se.NoSpaceLeftOnDomain(domain.sdUUID) log.debug("Copying metadata for volume %s to the new slot %s", vol.volUUID, newMetaSlot) vol.createMetadata((domain.sdUUID, newMetaSlot), metaContent) log.debug("Switching the metadata slot for volume %s to %s", vol.volUUID, newMetaSlot) vol.changeVolumeTag(sc.TAG_PREFIX_MD, str(newMetaSlot)) try: if isMsd: log.debug("Acquiring the cluster lock for domain %s with " "host id: %s", domain.sdUUID, hostId) newClusterLock.acquire(hostId, domain.getClusterLease()) allVolumes = domain.getAllVolumes() allImages = {} # {images: parent_image} # Few vdsm releases (4.9 prior 496c0c3, BZ#732980) generated metadata # offsets higher than 1947 (LEASES_SIZE - RESERVED_LEASES - 1). # This function reallocates such slots to free ones in order to use the # same offsets for the volume resource leases. v3ReallocateMetadataSlot(domain, allVolumes) # Updating the volumes one by one, doesn't require activation for volUUID, (imgUUIDs, parentUUID) in allVolumes.iteritems(): log.debug("Converting volume: %s", volUUID) # Maintaining a dict of {images: parent_image} allImages.update((i, None) for i in imgUUIDs) # The first imgUUID is the imgUUID of the template or the # only imgUUID where the volUUID appears. vol = domain.produceVolume(imgUUIDs[0], volUUID) v3UpgradeVolumePermissions(vol) log.debug("Creating the volume lease for %s", volUUID) metaId = vol.getMetadataId() vol.newVolumeLease(metaId, domain.sdUUID, volUUID) # If this volume is used as a template let's update the other # volume's permissions and share the volume lease (at the moment # of this writing this is strictly needed only on file domains). for imgUUID in imgUUIDs[1:]: allImages[imgUUID] = imgUUIDs[0] dstVol = domain.produceVolume(imgUUID, volUUID) v3UpgradeVolumePermissions(dstVol) # Sharing the original template volume lease file with the # same volume in the other images. vol._shareLease(dstVol.imagePath) # Updating the volumes to fix BZ#811880, here the activation is # required and to be more effective we do it by image (one shot). for imgUUID in allImages: log.debug("Converting image: %s", imgUUID) # XXX: The only reason to prepare the image is to verify the volume # virtual size configured in the qcow2 header (BZ#811880). # The activation and deactivation of the LVs might lead to a race # with the creation or destruction of a VM on the SPM. # # The analyzed scenarios are: # 1. A VM is currently running on the image we are preparing. # This is safe because the prepare is superfluous and the # teardown is going to fail (the LVs are in use by the VM) # 2. A VM using this image is started after the prepare. # This is safe because the prepare issued by the VM is # superfluous and our teardown is going to fail (the LVs are # in use by the VM). # 3. A VM using this image is started and the teardown is # executed before that the actual QEMU process is started. # This is safe because the VM is going to fail (the engine # should retry later) but there is no risk of corruption. # 4. A VM using this image is destroyed after the prepare and # before reading the image size. # This is safe because the upgrade process will fail (unable # to read the image virtual size) and it can be restarted # later. imgVolumes = sd.getVolsOfImage(allVolumes, imgUUID).keys() try: try: domain.activateVolumes(imgUUID, imgVolumes) except (OSError, se.CannotActivateLogicalVolumes): log.error("Image %s can't be activated.", imgUUID, exc_info=True) for volUUID in imgVolumes: try: vol = domain.produceVolume(imgUUID, volUUID) _v3_reset_meta_volsize(vol) # BZ#811880 except cmdutils.Error: log.error("It is not possible to read the volume %s " "using qemu-img, the content looks damaged", volUUID, exc_info=True) except se.VolumeDoesNotExist: log.error("It is not possible to prepare the image %s, the " "volume chain looks damaged", imgUUID, exc_info=True) except se.MetaDataKeyNotFoundError: log.error("It is not possible to prepare the image %s, the " "volume metadata looks damaged", imgUUID, exc_info=True) finally: try: domain.deactivateImage(imgUUID) except se.CannotDeactivateLogicalVolume: log.warning("Unable to teardown the image %s, this error " "is not critical since the volume might be in" " use", imgUUID, exc_info=True) log.debug("Finalizing the storage domain upgrade from version %s to " "version %s for domain %s", currentVersion, targetVersion, domain.sdUUID) domain.setMetaParam(sd.DMDK_VERSION, targetVersion) except: if isMsd: try: log.error("Releasing the cluster lock for domain %s with " "host id: %s", domain.sdUUID, hostId) newClusterLock.release(domain.getClusterLease()) except: log.error("Unable to release the cluster lock for domain " "%s with host id: %s", domain.sdUUID, hostId, exc_info=True) try: log.error("Releasing the host id %s for domain %s", hostId, domain.sdUUID) newClusterLock.releaseHostId(hostId, async=False, unused=True) except: log.error("Unable to release the host id %s for domain %s", hostId, domain.sdUUID, exc_info=True) raise # Releasing the old cluster lock (safelease). This lock was acquired # by the regular startSpm flow and now must be replaced by the new one # (sanlock). Since we are already at the end of the process (no way to # safely rollback to version 0 or 2) we should ignore the cluster lock # release errors. if isMsd: try: domain._clusterLock.release(domain.getClusterLease()) except: log.error("Unable to release the old cluster lock for domain " "%s ", domain.sdUUID, exc_info=True) # This is not strictly required since the domain object is destroyed right # after the upgrade but let's not make assumptions about future behaviors log.debug("Switching the cluster lock for domain %s", domain.sdUUID) domain._clusterLock = newClusterLock