def syncVolumeChain(self, sdUUID, imgUUID, volUUID, actualChain): """ Fix volume metadata to reflect the given actual chain. This function is used to correct the volume chain linkage after a live merge. """ curChain = self.getChain(sdUUID, imgUUID, volUUID) log_str = logutils.volume_chain_to_str(vol.volUUID for vol in curChain) self.log.info("Current chain=%s ", log_str) subChain = [] for vol in curChain: if vol.volUUID not in actualChain: subChain.insert(0, vol.volUUID) elif len(subChain) > 0: break if len(subChain) == 0: return self.log.info("Unlinking subchain: %s", subChain) sdDom = sdCache.produce(sdUUID=sdUUID) dstParent = sdDom.produceVolume(imgUUID, subChain[0]).getParent() subChainTailVol = sdDom.produceVolume(imgUUID, subChain[-1]) if subChainTailVol.isLeaf(): self.log.info( "Leaf volume %s is being removed from the chain. " "Marking it ILLEGAL to prevent data corruption", subChainTailVol.volUUID) subChainTailVol.setLegality(sc.ILLEGAL_VOL) else: for childID in subChainTailVol.getChildren(): self.log.info("Setting parent of volume %s to %s", childID, dstParent) sdDom.produceVolume(imgUUID, childID). \ setParentMeta(dstParent)
def syncData(self, sdUUID, imgUUID, dstSdUUID, syncType): srcChain = self.getChain(sdUUID, imgUUID) log_str = logutils.volume_chain_to_str(vol.volUUID for vol in srcChain) self.log.info("Source chain=%s ", log_str) dstChain = self.getChain(dstSdUUID, imgUUID) log_str = logutils.volume_chain_to_str(vol.volUUID for vol in dstChain) self.log.info("Dest chain=%s ", log_str) if syncType == SYNC_VOLUMES_INTERNAL: try: # Removing the leaf volumes del srcChain[-1], dstChain[-1] except IndexError: raise se.ImageIsNotLegalChain() elif syncType == SYNC_VOLUMES_LEAF: try: # Removing all the internal volumes del srcChain[:-1], dstChain[:-1] except IndexError: raise se.ImageIsNotLegalChain() elif syncType != SYNC_VOLUMES_ALL: raise se.MiscNotImplementedException() if len(srcChain) != len(dstChain): raise se.DestImageActionError(imgUUID, dstSdUUID) # Checking the volume uuids (after removing the leaves to allow # different uuids for the current top layer, see previous check). for i, v in enumerate(srcChain): if v.volUUID != dstChain[i].volUUID: raise se.DestImageActionError(imgUUID, dstSdUUID) dstDom = sdCache.produce(dstSdUUID) self._interImagesCopy(dstDom, sdUUID, imgUUID, { 'srcChain': srcChain, 'dstChain': dstChain }) self._finalizeDestinationImage(dstDom, imgUUID, { 'srcChain': srcChain, 'dstChain': dstChain }, False)
def _activateVolumeForImportExport(self, domain, imgUUID, volUUID=None): chain = self.getChain(domain.sdUUID, imgUUID, volUUID) log_str = logutils.volume_chain_to_str(vol.volUUID for vol in chain) self.log.info("chain=%s ", log_str) template = chain[0].getParentVolume() if template or len(chain) > 1: self.log.error("Importing and exporting an image with more " "than one volume is not supported") raise se.CopyImageError() domain.activateVolumes(imgUUID, volUUIDs=[chain[0].volUUID]) return chain[0]
def validateVolumeChain(self, sdUUID, imgUUID): """ Check correctness of the whole chain (including template if exists) """ if not self.isLegal(sdUUID, imgUUID): raise se.ImageIsNotLegalChain(imgUUID) chain = self.getChain(sdUUID, imgUUID) log_str = logutils.volume_chain_to_str(vol.volUUID for vol in chain) self.log.info("Current chain=%s ", log_str) # check if the chain is build above a template, or it is a standalone pvol = chain[0].getParentVolume() if pvol: if not pvol.isLegal() or pvol.isFake(): raise se.ImageIsNotLegalChain(imgUUID)
def estimateChainSize(self, sdUUID, imgUUID, volUUID, capacity): """ Compute an estimate of the whole chain size using the sum of the actual size of the chain's volumes """ chain = self.getChain(sdUUID, imgUUID, volUUID) log_str = logutils.volume_chain_to_str(vol.volUUID for vol in chain) self.log.info("chain=%s ", log_str) chain_allocation = 0 template = chain[0].getParentVolume() if template: chain_allocation = template.getVolumeSize() for vol in chain: chain_allocation += vol.getVolumeSize() if chain_allocation > capacity: chain_allocation = capacity # allocate %10 more for cow metadata chain_allocation = int(chain_allocation * sc.COW_OVERHEAD) return chain_allocation
def _createTargetImage(self, destDom, srcSdUUID, imgUUID): # Before actual data copying we need perform several operation # such as: create all volumes, create fake template if needed, ... try: # Find all volumes of source image srcChain = self.getChain(srcSdUUID, imgUUID) log_str = logutils.volume_chain_to_str(vol.volUUID for vol in srcChain) self.log.info("Source chain=%s ", log_str) except se.StorageException: self.log.error("Unexpected error", exc_info=True) raise except Exception as e: self.log.error("Unexpected error", exc_info=True) raise se.SourceImageActionError(imgUUID, srcSdUUID, str(e)) fakeTemplate = False pimg = sc.BLANK_UUID # standalone chain # check if the chain is build above a template, or it is a standalone pvol = srcChain[0].getParentVolume() if pvol: # find out parent volume parameters volParams = pvol.getVolumeParams() pimg = volParams['imgUUID'] # pimg == template image if destDom.isBackup(): # FIXME: This workaround help as copy VM to the backup domain # without its template. We will create fake template # for future VM creation and mark it as FAKE volume. # This situation is relevant for backup domain only. fakeTemplate = True @contextmanager def justLogIt(img): self.log.debug("You don't really need lock parent of image %s", img) yield dstImageResourcesNamespace = rm.getNamespace(sc.IMAGE_NAMESPACE, destDom.sdUUID) # In destination domain we need to lock image's template if exists with rm.acquireResource(dstImageResourcesNamespace, pimg, rm.SHARED) \ if pimg != sc.BLANK_UUID else justLogIt(imgUUID): if fakeTemplate: self.createFakeTemplate(destDom.sdUUID, volParams) dstChain = [] for srcVol in srcChain: # Create the dst volume try: # find out src volume parameters volParams = srcVol.getVolumeParams() # To avoid prezeroing preallocated volumes on NFS domains # we create the target as a sparse volume (since it will be # soon filled with the data coming from the copy) and then # we change its metadata back to the original value. if (destDom.supportsSparseness or volParams['volFormat'] != sc.RAW_FORMAT): tmpVolPreallocation = sc.SPARSE_VOL else: tmpVolPreallocation = sc.PREALLOCATED_VOL destDom.createVolume(imgUUID=imgUUID, capacity=volParams['capacity'], volFormat=volParams['volFormat'], preallocate=tmpVolPreallocation, diskType=volParams['disktype'], volUUID=srcVol.volUUID, desc=volParams['descr'], srcImgUUID=pimg, srcVolUUID=volParams['parent']) dstVol = destDom.produceVolume(imgUUID=imgUUID, volUUID=srcVol.volUUID) # Extend volume (for LV only) size to the actual size dstVol.extend(volParams['apparentsize']) # Change destination volume metadata to preallocated in # case we've used a sparse volume to accelerate the # volume creation if volParams['prealloc'] == sc.PREALLOCATED_VOL \ and tmpVolPreallocation != sc.PREALLOCATED_VOL: dstVol.setType(sc.PREALLOCATED_VOL) dstChain.append(dstVol) except se.StorageException: self.log.error("Unexpected error", exc_info=True) raise except Exception as e: self.log.error("Unexpected error", exc_info=True) raise se.DestImageActionError(imgUUID, destDom.sdUUID, str(e)) # only base may have a different parent image pimg = imgUUID return {'srcChain': srcChain, 'dstChain': dstChain}
def _start_commit(self, drive, job): """ Start libvirt blockCommit block job. Must be called under self._lock. """ # Persist the job before starting the commit, to ensure that vdsm will # know about the commit if it was killed after the block job was # started. job.state = Job.COMMIT self._persist_jobs() # Check that libvirt exposes full volume chain information actual_chain = self._vm.query_drive_volume_chain(drive) if actual_chain is None: self._untrack_job(job.id) raise exception.MergeFailed("Cannot get drive actual volume chain", drive=drive.name, alias=drive.alias, job=job.id) try: base_target = drive.volume_target(job.base, actual_chain) top_target = drive.volume_target(job.top, actual_chain) except VolumeNotFound as e: self._untrack_job(job.id) raise exception.MergeFailed(str(e), top=job.top, base=job.base, chain=actual_chain, job=job.id) # Indicate that we expect libvirt to maintain the relative paths of # backing files. This is necessary to ensure that a volume chain is # visible from any host even if the mountpoint is different. flags = libvirt.VIR_DOMAIN_BLOCK_COMMIT_RELATIVE if job.top == drive.volumeID: # Pass a flag to libvirt to indicate that we expect a two phase # block job. In the first phase, data is copied to base. Once # completed, an event is raised to indicate that the job has # transitioned to the second phase. We must then tell libvirt to # pivot to the new active layer (base). flags |= libvirt.VIR_DOMAIN_BLOCK_COMMIT_ACTIVE orig_chain = [entry.uuid for entry in actual_chain] chain_str = logutils.volume_chain_to_str(orig_chain) log.info( "Starting merge with job_id=%r, original chain=%s, " "disk=%r, base=%r, top=%r, bandwidth=%d, flags=%d", job.id, chain_str, drive.name, base_target, top_target, job.bandwidth, flags) try: # pylint: disable=no-member self._dom.blockCommit(drive.name, base_target, top_target, job.bandwidth, flags=flags) except libvirt.libvirtError as e: self._untrack_job(job.id) raise exception.MergeFailed(str(e), job=job.id)
def syncVolumeChain(self, sdUUID, imgUUID, volUUID, actualChain): """ Fix volume metadata to reflect the given actual chain. This function is used to correct the volume chain linkage after a live merge or for recovery after live merge failure. There are multiple cases for the usage of this function: 1. Marking leaf volume ILLEGAL before we complete a live merge of the leaf volume. In this case actual_chain will not contain the leaf volume id. actual_chain: ["base-vol", "internal-vol"] current_chain: ["base-vol", "internal-vol", "leaf-vol"] action: mark "leaf-vol" as illegal. 2. Removal of internal volume after internal live merge was completed successfully. In this case actual_chain will not contain one of the internal volumes ids. actual_chain: ["base-vol", "leaf-vol"] current_chain: ["base-vol", "internal-vol", "leaf-vol"] action: set "leaf-vol" parent to "base-vol" 3. Fixing the chain after completing live merge of leaf volume has failed. In this case actual_chain will contain all volumes ids. This reverts the change done in case 1. actual_chain: ["base-vol", "internal-vol", "leaf-vol"] current_chain: ["base-vol", "internal-vol", "leaf-vol"] action: if "leaf-vol" is ILLEGAL, mark it to LEGAL 4. Do nothing if actual and current chain matches (no subChain) and leaf volume is marked legal. I this case no change needs to be done in the current_chain. actual_chain: ["base-vol", "internal-vol", "leaf-vol"] current_chain: ["base-vol", "internal-vol", "leaf-vol"] action: if "leaf-vol" is LEGAL, do nothing """ curChain = self.getChain(sdUUID, imgUUID, volUUID) log_str = logutils.volume_chain_to_str(vol.volUUID for vol in curChain) self.log.info("Current chain=%s ", log_str) sdDom = sdCache.produce(sdUUID) subChain = [] for vol in curChain: if vol.volUUID not in actualChain: subChain.insert(0, vol.volUUID) elif len(subChain) > 0: break if len(subChain) == 0: tailVol = sdDom.produceVolume(imgUUID, volUUID) if not tailVol.isLegal(): # Case 3 - fixing the chain. self.log.info( "Leaf volume %s is ILLEGAL but is part of the actual chain" " - marking it LEGAL so it can be used again.", tailVol.volUUID) tailVol.setLegality(sc.LEGAL_VOL) # Case 4 - do nothing. return dstParent = sdDom.produceVolume(imgUUID, subChain[0]).getParent() subChainTailVol = sdDom.produceVolume(imgUUID, subChain[-1]) if subChainTailVol.isLeaf(): # Case 1 - mark leaf ILLEGAL. self.log.info( "Leaf volume %s is being removed from the actual chain. " "Marking it ILLEGAL to prevent data corruption", subChainTailVol.volUUID) subChainTailVol.setLegality(sc.ILLEGAL_VOL) else: # Case 2 - remove internal volume. for childID in subChainTailVol.getChildren(): self.log.info( "Internal volume %s removed from actual chain, linking " "child volume %s to parent volume %s", subChainTailVol, childID, dstParent) sdDom.produceVolume(imgUUID, childID). \ setParentMeta(dstParent)