def _baseRawVolumeMerge(self, sdDom, srcVolParams, volParams, chain): """ Merge snapshot with base RAW volume """ # In this case we need convert ancestor->successor subchain to new # volume and rebase successor's children (if exists) on top of it. # Step 1: Create an empty volume named sucessor_MERGE similar to # ancestor volume. # Step 2: qemuConvert successor -> sucessor_MERGE # Step 3: Rename successor to _remove_me__successor # Step 4: Rename successor_MERGE to successor # Step 5: Unsafely rebase successor's children on top of temporary # volume srcVol = chain[-1] with srcVol.scopedPrepare(rw=True, chainrw=True, setrw=True): # Find out successor's children list chList = srcVolParams['children'] # Step 1: Create an empty volume named sucessor_MERGE with # destination volume's parent parameters newUUID = srcVol.volUUID + "_MERGE" sdDom.createVolume( imgUUID=srcVolParams['imgUUID'], size=srcVolParams['size'], volFormat=volParams['volFormat'], preallocate=volParams['prealloc'], diskType=volParams['disktype'], volUUID=newUUID, desc=srcVolParams['descr'], srcImgUUID=volume.BLANK_UUID, srcVolUUID=volume.BLANK_UUID) newVol = sdDom.produceVolume(imgUUID=srcVolParams['imgUUID'], volUUID=newUUID) with newVol.scopedPrepare(rw=True, justme=True, setrw=True): # Step 2: Convert successor to new volume # qemu-img convert -f qcow2 successor -O raw newUUID (rc, out, err) = volume.qemuConvert( srcVolParams['path'], newVol.getVolumePath(), srcVolParams['volFormat'], volParams['volFormat'], vars.task.aborting, size=volParams['apparentsize'], dstvolType=newVol.getType()) if rc: self.log.error("qemu-img convert failed: rc=%s, out=%s, " "err=%s", rc, out, err) raise se.MergeSnapshotsError(newUUID) if chList: newVol.setInternal() # Step 3: Rename successor as to _remove_me__successor tmpUUID = self.deletedVolumeName(srcVol.volUUID) srcVol.rename(tmpUUID) # Step 4: Rename successor_MERGE to successor newVol.rename(srcVolParams['volUUID']) # Step 5: Rebase children 'unsafely' on top of new volume # qemu-img rebase -u -b tmpBackingFile -F backingFormat -f srcFormat # src for ch in chList: ch.prepare(rw=True, chainrw=True, setrw=True, force=True) backingVolPath = os.path.join('..', srcVolParams['imgUUID'], srcVolParams['volUUID']) try: ch.rebase(srcVolParams['volUUID'], backingVolPath, volParams['volFormat'], unsafe=True, rollback=True) finally: ch.teardown(sdUUID=ch.sdUUID, volUUID=ch.volUUID) ch.recheckIfLeaf() # Prepare chain for future erase rmChain = [vol.volUUID for vol in chain if vol.volUUID != srcVolParams['volUUID']] rmChain.append(tmpUUID) return rmChain
def copy(self, sdUUID, vmUUID, srcImgUUID, srcVolUUID, dstImgUUID, dstVolUUID, descr, dstSdUUID, volType, volFormat, preallocate, postZero, force): """ Create new template/volume from VM. Do it by collapse and copy the whole chain (baseVolUUID->srcVolUUID) """ self.log.info("sdUUID=%s vmUUID=%s srcImgUUID=%s srcVolUUID=%s " "dstImgUUID=%s dstVolUUID=%s dstSdUUID=%s volType=%s " "volFormat=%s preallocate=%s force=%s postZero=%s", sdUUID, vmUUID, srcImgUUID, srcVolUUID, dstImgUUID, dstVolUUID, dstSdUUID, volType, volume.type2name(volFormat), volume.type2name(preallocate), str(force), str(postZero)) try: srcVol = dstVol = None # Find out dest sdUUID if dstSdUUID == sd.BLANK_UUID: dstSdUUID = sdUUID volclass = sdCache.produce(sdUUID).getVolumeClass() destDom = sdCache.produce(dstSdUUID) # find src volume try: srcVol = volclass(self.repoPath, sdUUID, srcImgUUID, srcVolUUID) except se.StorageException: raise except Exception as e: self.log.error(e, exc_info=True) raise se.SourceImageActionError(srcImgUUID, sdUUID, str(e)) # Create dst volume try: # find out src volume parameters volParams = srcVol.getVolumeParams() if volParams['parent'] and \ volParams['parent'] != volume.BLANK_UUID: # Volume has parent and therefore is a part of a chain # in that case we can not know what is the exact size of # the space target file (chain ==> cow ==> sparse). # Therefore compute an estimate of the target volume size # using the sum of the actual size of the chain's volumes if volParams['volFormat'] != volume.COW_FORMAT or \ volParams['prealloc'] != volume.SPARSE_VOL: raise se.IncorrectFormat(self) volParams['apparentsize'] = self.__chainSizeCalc( sdUUID, srcImgUUID, srcVolUUID, volParams['size']) # Find out dest volume parameters if preallocate in [volume.PREALLOCATED_VOL, volume.SPARSE_VOL]: volParams['prealloc'] = preallocate if volFormat in [volume.COW_FORMAT, volume.RAW_FORMAT]: dstVolFormat = volFormat else: dstVolFormat = volParams['volFormat'] self.log.info("copy source %s:%s:%s vol size %s destination " "%s:%s:%s apparentsize %s" % (sdUUID, srcImgUUID, srcVolUUID, volParams['size'], dstSdUUID, dstImgUUID, dstVolUUID, volParams['apparentsize'])) # If image already exists check whether it illegal/fake, # overwrite it if not self.isLegal(dstSdUUID, dstImgUUID): force = True # We must first remove the previous instance of image (if # exists) in destination domain, if we got the overwrite # command if force: self.log.info("delete image %s on domain %s before " "overwriting", dstImgUUID, dstSdUUID) _deleteImage(destDom, dstImgUUID, postZero) # To avoid 'prezeroing' preallocated volume on NFS domain, # we create the target volume with minimal size and after that # we'll change its metadata back to the original size. tmpSize = TEMPORARY_VOLUME_SIZE # in sectors (10M) destDom.createVolume( imgUUID=dstImgUUID, size=tmpSize, volFormat=dstVolFormat, preallocate=volParams['prealloc'], diskType=volParams['disktype'], volUUID=dstVolUUID, desc=descr, srcImgUUID=volume.BLANK_UUID, srcVolUUID=volume.BLANK_UUID) dstVol = sdCache.produce(dstSdUUID).produceVolume( imgUUID=dstImgUUID, volUUID=dstVolUUID) # For convert to 'raw' we need use the virtual disk size # instead of apparent size if dstVolFormat == volume.RAW_FORMAT: newsize = volParams['size'] else: newsize = volParams['apparentsize'] dstVol.extend(newsize) dstPath = dstVol.getVolumePath() # Change destination volume metadata back to the original size. dstVol.setSize(volParams['size']) 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.CopyImageError("Destination volume %s error: %s" % (dstVolUUID, str(e))) try: # Start the actual copy image procedure srcVol.prepare(rw=False) dstVol.prepare(rw=True, setrw=True) try: (rc, out, err) = volume.qemuConvert( volParams['path'], dstPath, volParams['volFormat'], dstVolFormat, vars.task.aborting, size=srcVol.getVolumeSize(bs=1), dstvolType=dstVol.getType()) if rc: raise se.StorageException("rc: %s, err: %s" % (rc, err)) except ActionStopped: raise except se.StorageException as e: raise se.CopyImageError(str(e)) # Mark volume as SHARED if volType == volume.SHARED_VOL: dstVol.setShared() dstVol.setLegality(volume.LEGAL_VOL) if force: # Now we should re-link all deleted hardlinks, if exists destDom.templateRelink(dstImgUUID, dstVolUUID) 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.CopyImageError("src image=%s, dst image=%s: msg=%s" % (srcImgUUID, dstImgUUID, str(e))) self.log.info("Finished copying %s:%s -> %s:%s", sdUUID, srcVolUUID, dstSdUUID, dstVolUUID) #TODO: handle return status return dstVolUUID finally: self.__cleanupCopy(srcVol=srcVol, dstVol=dstVol)