def rename(self, newUUID, recovery=True): """ Rename volume """ self.log.info("Rename volume %s as %s ", self.volUUID, newUUID) if not self.imagePath: self.validateImagePath() volPath = os.path.join(self.imagePath, newUUID) metaPath = self._getMetaVolumePath(volPath) prevMetaPath = self._getMetaVolumePath() if recovery: name = "Rename volume rollback: " + volPath vars.task.pushRecovery( task.Recovery(name, "fileVolume", "FileVolume", "renameVolumeRollback", [volPath, self.volumePath])) self.oop.os.rename(self.volumePath, volPath) if recovery: name = "Rename meta-volume rollback: " + metaPath vars.task.pushRecovery( task.Recovery(name, "fileVolume", "FileVolume", "renameVolumeRollback", [metaPath, prevMetaPath])) self.oop.os.rename(prevMetaPath, metaPath) self.volUUID = newUUID self.volumePath = volPath
def clone(self, dstPath, volFormat): """ Clone self volume to the specified dst_image_dir/dst_volUUID """ wasleaf = False taskName = "parent volume rollback: " + self.volUUID vars.task.pushRecovery( task.Recovery(taskName, "volume", "Volume", "parentVolumeRollback", [self.sdUUID, self.imgUUID, self.volUUID])) if self.isLeaf(): wasleaf = True self.setInternal() try: self.prepare(rw=False) self.log.debug('cloning volume %s to %s', self.volumePath, dstPath) parent = getBackingVolumePath(self.imgUUID, self.volUUID) domain = sdCache.produce(self.sdUUID) qemuimg.create(dstPath, backing=parent, format=sc.fmt2str(volFormat), qcow2Compat=domain.qcow2_compat(), backingFormat=sc.fmt2str(self.getFormat())) self.teardown(self.sdUUID, self.volUUID) except Exception as e: self.log.exception('cannot clone image %s volume %s to %s', self.imgUUID, self.volUUID, dstPath) # FIXME: might race with other clones if wasleaf: self.setLeaf() self.teardown(self.sdUUID, self.volUUID) raise se.CannotCloneVolume(self.volumePath, dstPath, str(e))
def rebase(self, backingVol, backingVolPath, backingFormat, unsafe, rollback): """ Rebase volume on top of new backing volume """ if rollback: pvol = self.getParentVolume() if not pvol: self.log.warn("Can't rebase volume %s, parent missing", self.volUUID) return name = "Merge volume: " + self.volUUID vars.task.pushRecovery( task.Recovery(name, "volume", "Volume", "rebaseVolumeRollback", [ self.sdUUID, self.getImage(), self.volUUID, str(pvol.getFormat()), pvol.volUUID, str(True) ])) volumePath = self.getVolumePath() try: qemuimg.rebase(volumePath, backingVolPath, fmt2str(self.getFormat()), fmt2str(backingFormat), unsafe, vars.task.aborting) except qemuimg.QImgError: self.log.exception('cannot rebase volume %s on %s', volumePath, backingVolPath) raise se.MergeSnapshotsError(self.volUUID) self.setParent(backingVol) self.recheckIfLeaf()
def share(self, dst_image_dir, hard=True): """ Share this volume to dst_image_dir 'hard' - link type: file volumes should use hardlinks (default behaviour) block volumes should use softlinks (explicitly hard=False) """ self.log.debug("Volume.share)share %s to %s hard %s" % (self.volUUID, dst_image_dir, hard)) if not self.isShared(): raise se.VolumeNonShareable(self) if os.path.basename(dst_image_dir) == os.path.basename(self.imagePath): raise se.VolumeOwnershipError(self) try: src = self.getDevPath() dst = os.path.join(dst_image_dir, self.volUUID) taskName = "share volume rollback: " + dst vars.task.pushRecovery(task.Recovery(taskName, "volume", "Volume", "shareVolumeRollback", [dst])) if os.path.lexists(dst): os.unlink(dst) if hard: os.link(src, dst) else: os.symlink(src, dst) except Exception, e: raise se.CannotShareVolume(src, dst, str(e))
def clone(self, dst_image_dir, dst_volUUID, volFormat, preallocate): """ Clone self volume to the specified dst_image_dir/dst_volUUID """ wasleaf = False dst_path = None taskName = "parent volume rollback: " + self.volUUID vars.task.pushRecovery(task.Recovery(taskName, "volume", "Volume", "parentVolumeRollback", [self.sdUUID, self.imgUUID, self.volUUID])) if self.isLeaf(): wasleaf = True self.setInternal() try: self.prepare(rw=False) dst_path = os.path.join(dst_image_dir, dst_volUUID) self.log.debug("Volume.clone: %s to %s" % (self.volumePath, dst_path)) size = int(self.getMetaParam(SIZE)) parent = self.getVolumePath() parent_format = fmt2str(self.getFormat()) # We should use parent's relative path instead of full path parent = os.path.join(os.path.basename(os.path.dirname(parent)), os.path.basename(parent)) createVolume(parent, parent_format, dst_path, size, volFormat, preallocate) self.teardown(self.sdUUID, self.volUUID) except Exception, e: # FIXME: might race with other clones if wasleaf: self.setLeaf() self.teardown(self.sdUUID, self.volUUID) self.log.error("Volume.clone: can't clone: %s to %s" % (self.volumePath, dst_path)) raise se.CannotCloneVolume(self.volumePath, dst_path, str(e))
def share(self, dstImgPath): """ Share this volume to dstImgPath """ self.log.debug("Share volume %s to %s", self.volUUID, dstImgPath) if not self.isShared(): raise se.VolumeNonShareable(self) if os.path.basename(dstImgPath) == os.path.basename(self.imagePath): raise se.VolumeOwnershipError(self) dstPath = os.path.join(dstImgPath, self.volUUID) clsModule, clsName = self._getModuleAndClass() try: vars.task.pushRecovery( task.Recovery("Share volume rollback: %s" % dstPath, clsModule, clsName, "shareVolumeRollback", [dstPath]) ) self._share(dstImgPath) except Exception as e: raise se.CannotShareVolume(self.getVolumePath(), dstPath, str(e))
def rebase(self, backingVol, backingVolPath, backingFormat, unsafe, rollback): """ Rebase volume on top of new backing volume """ if rollback: pvol = self.getParentVolume() if not pvol: self.log.warn("Can't rebase volume %s, parent missing", self.volUUID) return name = "Merge volume: " + self.volUUID vars.task.pushRecovery( task.Recovery(name, "volume", "Volume", "rebaseVolumeRollback", [self.sdUUID, self.getImage(), self.volUUID, str(pvol.getFormat()), pvol.volUUID, str(True)])) (rc, out, err) = qemuRebase(self.getVolumePath(), self.getFormat(), backingVolPath, backingFormat, unsafe, vars.task.aborting, rollback) if rc: raise se.MergeSnapshotsError(self.volUUID) self.setParent(backingVol) self.recheckIfLeaf()
def rename(self, newUUID, recovery=True): """ Rename volume """ self.log.info("Rename volume %s as %s ", self.volUUID, newUUID) if not self.imagePath: self._manifest.validateImagePath() volPath = os.path.join(self.imagePath, newUUID) metaPath = self._getMetaVolumePath(volPath) prevMetaPath = self._getMetaVolumePath() leasePath = self._getLeaseVolumePath(volPath) prevLeasePath = self._getLeaseVolumePath() if recovery: name = "Rename volume rollback: " + volPath vars.task.pushRecovery( task.Recovery(name, "fileVolume", "FileVolume", "renameVolumeRollback", [volPath, self.volumePath])) self.log.debug("Renaming %s to %s", self.volumePath, volPath) self.oop.os.rename(self.volumePath, volPath) if recovery: name = "Rename meta-volume rollback: " + metaPath vars.task.pushRecovery( task.Recovery(name, "fileVolume", "FileVolume", "renameVolumeRollback", [metaPath, prevMetaPath])) self.log.debug("Renaming %s to %s", prevMetaPath, metaPath) self.oop.os.rename(prevMetaPath, metaPath) if recovery: name = "Rename lease-volume rollback: " + leasePath vars.task.pushRecovery( task.Recovery(name, "fileVolume", "FileVolume", "renameVolumeRollback", [leasePath, prevLeasePath])) self.log.debug("Renaming %s to %s", prevLeasePath, leasePath) try: self.oop.os.rename(prevLeasePath, leasePath) except OSError as e: if e.errno != os.errno.ENOENT: raise self._manifest.volUUID = newUUID self._manifest.volumePath = volPath
def create(cls, repoPath, sdUUID, imgUUID, size, volFormat, preallocate, diskType, volUUID, desc, srcImgUUID, srcVolUUID): """ Create a new volume with given size or snapshot 'size' - in sectors 'volFormat' - volume format COW / RAW 'preallocate' - Prealocate / Sparse 'diskType' - string that describes disk type System|Data|Shared|Swap|Temp 'srcImgUUID' - source image UUID 'srcVolUUID' - source volume UUID """ if not volUUID: volUUID = str(uuid.uuid4()) if volUUID == volume.BLANK_UUID: raise se.InvalidParameterException("volUUID", volUUID) # Validate volume parameters should be checked here for all # internal flows using volume creation. cls.validateCreateVolumeParams(volFormat, preallocate, srcVolUUID) imageDir = image.Image(repoPath).create(sdUUID, imgUUID) vol_path = os.path.join(imageDir, volUUID) voltype = "LEAF" pvol = None # Check if volume already exists if oop.getProcessPool(sdUUID).fileUtils.pathExists(vol_path): raise se.VolumeAlreadyExists(vol_path) # Check if snapshot creation required if srcVolUUID != volume.BLANK_UUID: if srcImgUUID == volume.BLANK_UUID: srcImgUUID = imgUUID pvol = FileVolume(repoPath, sdUUID, srcImgUUID, srcVolUUID) # Cannot create snapshot for ILLEGAL volume if not pvol.isLegal(): raise se.createIllegalVolumeSnapshotError(pvol.volUUID) # create volume rollback vars.task.pushRecovery( task.Recovery("halfbaked volume rollback", "fileVolume", "FileVolume", "halfbakedVolumeRollback", [vol_path])) if preallocate == volume.PREALLOCATED_VOL: try: # ddWatchCopy expects size to be in bytes misc.ddWatchCopy("/dev/zero", vol_path, vars.task.aborting, (int(size) * 512)) except se.ActionStopped, e: raise e except Exception, e: cls.log.error("Unexpected error", exc_info=True) raise se.VolumesZeroingError(vol_path)
def extendSize(self, newSize): """ Extend the size (virtual disk size seen by the guest) of the volume. """ if self.isShared(): raise se.VolumeNonWritable(self.volUUID) volFormat = self.getFormat() if volFormat == sc.COW_FORMAT: self.log.debug( "skipping cow size extension for volume %s to " "size %s", self.volUUID, newSize) return elif volFormat != sc.RAW_FORMAT: raise se.IncorrectFormat(self.volUUID) # Note: This function previously prohibited extending non-leaf volumes. # If a disk is enlarged a volume may become larger than its parent. In # order to support live merge of a larger volume into its raw parent we # must permit extension of this raw volume prior to starting the merge. isBase = self.getParent() == sc.BLANK_UUID if not (isBase or self.isLeaf()): raise se.VolumeNonWritable(self.volUUID) curRawSize = self.getVolumeSize() if (newSize < curRawSize): self.log.error( "current size of volume %s is larger than the " "size requested in the extension (%s > %s)", self.volUUID, curRawSize, newSize) raise se.VolumeResizeValueError(newSize) if (newSize == curRawSize): self.log.debug( "the requested size %s is equal to the current " "size %s, skipping extension", newSize, curRawSize) else: self.log.info( "executing a raw size extension for volume %s " "from size %s to size %s", self.volUUID, curRawSize, newSize) vars.task.pushRecovery( task.Recovery("Extend size for volume: " + self.volUUID, "volume", "Volume", "extendSizeFinalize", [self.sdUUID, self.imgUUID, self.volUUID])) self._extendSizeRaw(newSize) self.syncMetadata() # update the metadata
def rename(self, newUUID, recovery=True): """ Rename volume """ self.log.info("Rename volume %s as %s ", self.volUUID, newUUID) if not self.imagePath: self.validateImagePath() if os.path.lexists(self.getVolumePath()): os.unlink(self.getVolumePath()) if recovery: name = "Rename volume rollback: " + newUUID vars.task.pushRecovery( task.Recovery(name, "blockVolume", "BlockVolume", "renameVolumeRollback", [self.sdUUID, newUUID, self.volUUID])) lvm.renameLV(self.sdUUID, self.volUUID, newUUID) self.volUUID = newUUID self.volumePath = os.path.join(self.imagePath, newUUID)
def extendSize(self, newSize): """ Extend the size (virtual disk size seen by the guest) of the volume. """ if not self.isLeaf() or self.isShared(): raise se.VolumeNonWritable(self.volUUID) volFormat = self.getFormat() if volFormat == COW_FORMAT: self.log.debug( "skipping cow size extension for volume %s to " "size %s", self.volUUID, newSize) return elif volFormat != RAW_FORMAT: raise se.IncorrectFormat(self.volUUID) curRawSize = self.getVolumeSize() if (newSize < curRawSize): self.log.error( "current size of volume %s is larger than the " "size requested in the extension (%s > %s)", self.volUUID, curRawSize, newSize) raise se.VolumeResizeValueError(newSize) if (newSize == curRawSize): self.log.debug( "the requested size %s is equal to the current " "size %s, skipping extension", newSize, curRawSize) else: self.log.info( "executing a raw size extension for volume %s " "from size %s to size %s", self.volUUID, curRawSize, newSize) vars.task.pushRecovery( task.Recovery("Extend size for volume: " + self.volUUID, "volume", "Volume", "extendSizeFinalize", [self.sdUUID, self.imgUUID, self.volUUID])) self._extendSizeRaw(newSize) self.syncMetadata() # update the metadata
def create(cls, repoPath, sdUUID, imgUUID, size, volFormat, preallocate, diskType, volUUID, desc, srcImgUUID, srcVolUUID, initialSize=None): """ Create a new volume with given size or snapshot 'size' - in sectors 'volFormat' - volume format COW / RAW 'preallocate' - Preallocate / Sparse 'diskType' - enum (API.Image.DiskTypes) 'srcImgUUID' - source image UUID 'srcVolUUID' - source volume UUID 'initialSize' - initial volume size in sectors, in case of thin provisioning """ dom = sdCache.produce(sdUUID) dom.validateCreateVolumeParams(volFormat, srcVolUUID, preallocate=preallocate) imgPath = image.Image(repoPath).create(sdUUID, imgUUID) volPath = os.path.join(imgPath, volUUID) volParent = None volType = sc.type2name(sc.LEAF_VOL) # Get the specific class name and class module to be used in the # Recovery tasks. clsModule, clsName = cls._getModuleAndClass() try: if srcVolUUID != sc.BLANK_UUID: # When the srcImgUUID isn't specified we assume it's the same # as the imgUUID if srcImgUUID == sc.BLANK_UUID: srcImgUUID = imgUUID volParent = cls(repoPath, sdUUID, srcImgUUID, srcVolUUID) if not volParent.isLegal(): raise se.createIllegalVolumeSnapshotError( volParent.volUUID) if imgUUID != srcImgUUID: volParent.share(imgPath) volParent = cls(repoPath, sdUUID, imgUUID, srcVolUUID) # Override the size with the size of the parent size = volParent.getSize() except se.StorageException: cls.log.error("Unexpected error", exc_info=True) raise except Exception as e: cls.log.error("Unexpected error", exc_info=True) raise se.VolumeCannotGetParent( "Couldn't get parent %s for volume %s: %s" % (srcVolUUID, volUUID, e)) try: cls.log.info("Creating volume %s", volUUID) # Rollback sentinel to mark the start of the task vars.task.pushRecovery( task.Recovery(task.ROLLBACK_SENTINEL, clsModule, clsName, "startCreateVolumeRollback", [sdUUID, imgUUID, volUUID])) # Create volume rollback vars.task.pushRecovery( task.Recovery("Halfbaked volume rollback", clsModule, clsName, "halfbakedVolumeRollback", [sdUUID, volUUID, volPath])) # Specific volume creation (block, file, etc...) try: metaId = cls._create(dom, imgUUID, volUUID, size, volFormat, preallocate, volParent, srcImgUUID, srcVolUUID, volPath, initialSize=initialSize) except (se.VolumeAlreadyExists, se.CannotCreateLogicalVolume, se.VolumeCreationError, se.InvalidParameterException) as e: cls.log.error("Failed to create volume %s: %s", volPath, e) vars.task.popRecovery() raise # When the volume format is raw what the guest sees is the apparent # size of the file/device therefore if the requested size doesn't # match the apparent size (eg: physical extent granularity in LVM) # we need to update the size value so that the metadata reflects # the correct state. if volFormat == sc.RAW_FORMAT: apparentSize = int( dom.getVSize(imgUUID, volUUID) / sc.BLOCK_SIZE) if apparentSize < size: cls.log.error( "The volume %s apparent size %s is smaller " "than the requested size %s", volUUID, apparentSize, size) raise se.VolumeCreationError() if apparentSize > size: cls.log.info( "The requested size for volume %s doesn't " "match the granularity on domain %s, " "updating the volume size from %s to %s", volUUID, sdUUID, size, apparentSize) size = apparentSize vars.task.pushRecovery( task.Recovery("Create volume metadata rollback", clsModule, clsName, "createVolumeMetadataRollback", map(str, metaId))) cls.newMetadata(metaId, sdUUID, imgUUID, srcVolUUID, size, sc.type2name(volFormat), sc.type2name(preallocate), volType, diskType, desc, sc.LEGAL_VOL) if dom.hasVolumeLeases(): cls.newVolumeLease(metaId, sdUUID, volUUID) except se.StorageException: cls.log.error("Unexpected error", exc_info=True) raise except Exception as e: cls.log.error("Unexpected error", exc_info=True) raise se.VolumeCreationError("Volume creation %s failed: %s" % (volUUID, e)) # Remove the rollback for the halfbaked volume vars.task.replaceRecoveries( task.Recovery("Create volume rollback", clsModule, clsName, "createVolumeRollback", [repoPath, sdUUID, imgUUID, volUUID, imgPath])) return volUUID
def baseAsyncTasksRollback(proc): name = "Kill-" + str(proc.pid) vars.task.pushRecovery(task.Recovery(name, "volume", "Volume", "killProcRollback", [str(proc.pid), str(misc.getProcCtime(proc.pid))]))
if preallocate == volume.SPARSE_VOL or volFormat == volume.COW_FORMAT: volume.createVolume(None, None, vol_path, size, volFormat, preallocate) else: # Create hardlink to template and its meta file if imgUUID != srcImgUUID: pvol.share(imageDir, hard=True) # Make clone to link the new volume against the local shared volume pvol = FileVolume(repoPath, sdUUID, imgUUID, srcVolUUID) pvol.clone(imageDir, volUUID, volFormat, preallocate) size = pvol.getMetaParam(volume.SIZE) try: vars.task.pushRecovery( task.Recovery("create file volume metadata rollback", "fileVolume", "FileVolume", "createVolumeMetadataRollback", [vol_path])) # By definition volume is now a leaf cls.file_setrw(vol_path, rw=True) # Set metadata and mark volume as legal.\ cls.newMetadata(vol_path, sdUUID, imgUUID, srcVolUUID, size, volume.type2name(volFormat), volume.type2name(preallocate), voltype, diskType, desc, volume.LEGAL_VOL) except Exception, e: cls.log.error("Unexpected error", exc_info=True) raise se.VolumeMetadataWriteError(vol_path + ":" + str(e)) # Remove all previous rollbacks for 'halfbaked' volume and add rollback for 'real' volume creation vars.task.replaceRecoveries( task.Recovery("create file volume rollback", "fileVolume",
cls.log.error("Unexpected error", exc_info=True) raise se.VolumeCannotGetParent( "blockVolume can't get parent %s for volume %s: %s" % (srcVolUUID, volUUID, str(e))) try: cls.log.info("blockVolume: creating LV: volUUID %s" % (volUUID)) if preallocate == volume.SPARSE_VOL: volsize = "%s" % config.get("irs", "volume_utilization_chunk_mb") else: # should stay %d and size should be int(size) volsize = "%s" % (size / 2 / 1024) vars.task.pushRecovery( task.Recovery("halfbaked volume rollback", "blockVolume", "BlockVolume", "halfbakedVolumeRollback", [sdUUID, volUUID, vol_path])) lvm.createLV(sdUUID, volUUID, volsize, activate=True) if os.path.exists(vol_path): os.unlink(vol_path) os.symlink(lvm.lvPath(sdUUID, volUUID), vol_path) except se.StorageException: cls.log.error("Unexpected error", exc_info=True) raise except Exception, e: cls.log.error("Unexpected error", exc_info=True) raise se.VolumeCreationError( "blockVolume create/link lv %s failed: %s" % (volUUID, str(e))) # By definition volume is now a leaf and should be writeable. # Default permission for lvcreate is read and write. No need to set permission.