def calculate_volume_alloc_size(cls, preallocate, capacity, initial_size): """ Calculate the allocation size in mb of the volume 'preallocate' - Sparse or Preallocated 'capacity' - the volume size in blocks 'initial_size' - optional, if provided the initial allocated size in blocks for sparse volumes """ if initial_size and preallocate == sc.PREALLOCATED_VOL: log.error("Initial size is not supported for preallocated volumes") raise se.InvalidParameterException("initial size", initial_size) if initial_size: capacity_bytes = capacity * sc.BLOCK_SIZE initial_size_bytes = initial_size * sc.BLOCK_SIZE max_size = cls.max_size(capacity_bytes, sc.COW_FORMAT) if initial_size_bytes > max_size: log.error("The requested initial %s is bigger " "than the max size %s", initial_size_bytes, max_size) raise se.InvalidParameterException("initial size", initial_size) if preallocate == sc.SPARSE_VOL: if initial_size: initial_size = int(initial_size * QCOW_OVERHEAD_FACTOR) alloc_size = (utils.round(initial_size, BLOCKS_TO_MB) // BLOCKS_TO_MB) else: alloc_size = config.getint("irs", "volume_utilization_chunk_mb") else: alloc_size = utils.round(capacity, BLOCKS_TO_MB) // BLOCKS_TO_MB return alloc_size
def make_block_volume(lvm, sd_manifest, size, imguuid, voluuid, parent_vol_id=sc.BLANK_UUID, vol_format=sc.RAW_FORMAT, prealloc=sc.PREALLOCATED_VOL, disk_type=image.UNKNOWN_DISK_TYPE, desc='fake volume'): sduuid = sd_manifest.sdUUID image_manifest = image.ImageManifest(sd_manifest.getRepoPath()) imagedir = image_manifest.getImageDir(sduuid, imguuid) if not os.path.exists(imagedir): os.makedirs(imagedir) size_mb = utils.round(size, MB) / MB lvm.createLV(sduuid, voluuid, size_mb) lv_size = int(lvm.getLV(sduuid, voluuid).size) lv_size_blk = lv_size / sc.BLOCK_SIZE with sd_manifest.acquireVolumeMetadataSlot(voluuid, sc.VOLUME_MDNUMBLKS) as slot: lvm.addtag(sduuid, voluuid, "%s%s" % (sc.TAG_PREFIX_MD, slot)) lvm.addtag(sduuid, voluuid, "%s%s" % (sc.TAG_PREFIX_PARENT, parent_vol_id)) lvm.addtag(sduuid, voluuid, "%s%s" % (sc.TAG_PREFIX_IMAGE, imguuid)) vol_class = sd_manifest.getVolumeClass() vol_class.newMetadata( (sduuid, slot), sduuid, imguuid, parent_vol_id, lv_size_blk, sc.type2name(vol_format), sc.type2name(prealloc), sc.type2name(sc.LEAF_VOL), disk_type, desc, sc.LEGAL_VOL)
def _extendSizeRaw(self, new_capacity): # Since this method relies on lvm.extendLV (lvextend) when the # requested size is equal or smaller than the current size, the # request is siliently ignored. new_capacity_mb = (utils.round(new_capacity, constants.MEGAB) // constants.MEGAB) lvm.extendLV(self.sdUUID, self.volUUID, new_capacity_mb)
def _extend_base_allocation(base_vol, top_vol): if not (base_vol.is_block() and base_vol.getFormat() == sc.COW_FORMAT): return base_alloc = base_vol.getVolumeSize() top_alloc = top_vol.getVolumeSize() vol_chunk_size = config.getint('irs', 'volume_utilization_chunk_mb') * MiB potential_alloc = base_alloc + top_alloc + vol_chunk_size # TODO: add chunk_size only if top is leaf. capacity = base_vol.getCapacity() max_alloc = utils.round(capacity * sc.COW_OVERHEAD, MiB) actual_alloc = min(potential_alloc, max_alloc) actual_alloc = utils.round(actual_alloc, MiB) actual_alloc_mb = actual_alloc // MiB dom = sdCache.produce(base_vol.sdUUID) dom.extendVolume(base_vol.volUUID, actual_alloc_mb)
def optimal_cow_size(cls, required_size, capacity, is_leaf): """ Return the optimal size for a volume based on the required allocation, volume capacity, and volume type. This is a class method so we can use this calculation for the base volume during merge, using the mesured size for the merged instead of the actual volume size. """ # Add free space. if is_leaf: # For leaf volumes, add one chunk of free space so the VM # will not pause quickly when it is started. free_space = config.getint("irs", "volume_utilization_chunk_mb") * MiB optimal_size = required_size + free_space else: # For internal volumes, use the required size. It cannot be zero # since it includes qcow2 metadata. assert required_size > 0 optimal_size = required_size # Align to align_size (lvm extent size) so callers can compare optimal # size with the actual size of the logical volume. optimal_size = utils.round(optimal_size, cls.align_size) # Limit by maximum size. max_size = cls.max_size(capacity, sc.COW_FORMAT) optimal_size = min(optimal_size, max_size) cls.log.debug( "COW format, required_size=%s max_size=%s optimal_size=%s", required_size, max_size, optimal_size) return optimal_size
def createLV(self, vgName, lvName, size, activate=True, contiguous=False, initialTags=()): try: vg_md = self.vgmd[vgName] except KeyError: raise se.CannotCreateLogicalVolume(vgName, lvName) # Size is received as a string in MB. We need to convert it to bytes # and round it up to a multiple of the VG extent size. try: size = int(size) << 20 except ValueError: raise se.CannotCreateLogicalVolume(vgName, lvName) size = utils.round(size, int(vg_md['extent_size'])) # devices is hard to emulate properly (must have a PE allocator that # works the same as for LVM). Since we don't need this value, use None devices = None state = 'a' if activate else '-' lv_attr = dict(voltype='-', permission='w', allocations='i', fixedminor='-', state=state, devopen='-', target='-', zero='-') lv_md = dict(uuid=fake_lvm_uuid(), name=lvName, vg_name=vgName, attr=lv_attr, size=str(size), seg_start_pe='0', devices=devices, tags=initialTags, writeable=True, opened=False, active=bool(activate)) lv_count = int(vg_md['lv_count']) + 1 if (vgName, lvName) in self.lvmd: raise se.CannotCreateLogicalVolume(vgName, lvName) self.lvmd[(vgName, lvName)] = lv_md self.vgmd[vgName]['lv_count'] = str(lv_count) # Create an LV as a regular file so we have a place to write data if activate: lv_path = self.lvPath(vgName, lvName) else: lv_path = self._lvPathInactive(vgName, lvName) make_file(lv_path, size)
def getMaxVolumeSize(self, capacity): """ Returns the maximum volume size in bytes. This value is larger than drive capacity since we must allocate extra space for cow internal data. The actual lv size may be larger due to rounding to next lvm extent. """ return utils.round(capacity * self.VOLWM_COW_OVERHEAD, constants.MEGAB)
def test_lv_create_round_up_size(self): with self.base_config() as lvm: vg = lvm.getVG(self.VG_NAME) extent_size_mb = int(vg.extent_size) // MB odd_size_mb = extent_size_mb - 1 lvm.createLV(self.VG_NAME, self.LV_NAME, odd_size_mb) rounded_up_size_mb = utils.round(odd_size_mb, extent_size_mb) lv = lvm.getLV(self.VG_NAME, self.LV_NAME) self.assertEqual(int(lv.size), rounded_up_size_mb * MB)
def make_block_volume(lvm, sd_manifest, size, imguuid, voluuid, parent_vol_id=sc.BLANK_UUID, vol_format=sc.RAW_FORMAT, vol_type=sc.LEAF_VOL, prealloc=sc.PREALLOCATED_VOL, disk_type=sc.DATA_DISKTYPE, desc='fake volume', qcow2_compat='0.10'): sduuid = sd_manifest.sdUUID imagedir = sd_manifest.getImageDir(imguuid) if not os.path.exists(imagedir): os.makedirs(imagedir) lv_size = sd_manifest.getVolumeClass().calculate_volume_alloc_size( prealloc, vol_format, size, None) lv_size_mb = (utils.round(lv_size, MiB) // MiB) lvm.createLV(sduuid, voluuid, lv_size_mb) # LVM may create the volume with a larger size due to extent granularity if vol_format == sc.RAW_FORMAT: lv_size = int(lvm.getLV(sduuid, voluuid).size) if lv_size > size: size = lv_size if vol_format == sc.COW_FORMAT: volpath = lvm.lvPath(sduuid, voluuid) backing = parent_vol_id if parent_vol_id != sc.BLANK_UUID else None # Write qcow2 image to the fake block device - truncating the file. op = qemuimg.create(volpath, size=size, format=qemuimg.FORMAT.QCOW2, qcow2Compat=qcow2_compat, backing=backing) op.run() # Truncate fake block device back ot the proper size. with open(volpath, "r+") as f: f.truncate(int(lvm.getLV(sduuid, voluuid).size)) with sd_manifest.acquireVolumeMetadataSlot(voluuid) as slot: add_tags = [ "%s%s" % (sc.TAG_PREFIX_MD, slot), "%s%s" % (sc.TAG_PREFIX_PARENT, parent_vol_id), "%s%s" % (sc.TAG_PREFIX_IMAGE, imguuid), ] lvm.changeLVsTags(sduuid, (voluuid, ), addTags=add_tags) vol_class = sd_manifest.getVolumeClass() vol_class.newMetadata((sduuid, slot), sduuid, imguuid, parent_vol_id, size, sc.type2name(vol_format), sc.type2name(prealloc), sc.type2name(vol_type), disk_type, desc, sc.LEGAL_VOL)
def extend(self, newSize): """Extend a logical volume 'newSize' - new size in blocks """ self.log.info("Request to extend LV %s of image %s in VG %s with " "size = %s", self.volUUID, self.imgUUID, self.sdUUID, newSize) # we should return: Success/Failure # Backend APIs: sizemb = utils.round(newSize, BLOCKS_TO_MB) // BLOCKS_TO_MB lvm.extendLV(self.sdUUID, self.volUUID, sizemb)
def test_optimal_size_cow_leaf(self): # Optimal size calculated using actual size and chunk size. with self.make_volume(size=2 * GiB, format=sc.COW_FORMAT) as vol: chunk_size = GiB check = qemuimg.check(vol.getVolumePath(), qemuimg.FORMAT.QCOW2) optimal_size = utils.round(check['offset'] + chunk_size, vol.align_size) self.assertEqual(vol.optimal_size(), optimal_size) self.assertEqual( vol.optimal_cow_size(check["offset"], 2 * GiB, vol.isLeaf()), optimal_size)
def extend(self, new_size): """Extend a logical volume 'new_size' - new size in bytes """ self.log.info( "Request to extend LV %s of image %s in VG %s with " "size = %s", self.volUUID, self.imgUUID, self.sdUUID, new_size) # we should return: Success/Failure # Backend APIs: sizemb = utils.round(new_size, MiB) // MiB lvm.extendLV(self.sdUUID, self.volUUID, sizemb)
def extend(self, new_size_blk): """Extend a logical volume 'new_size_blk' - new size in blocks """ self.log.info( "Request to extend LV %s of image %s in VG %s with " "size = %s blocks", self.volUUID, self.imgUUID, self.sdUUID, new_size_blk) # we should return: Success/Failure # Backend APIs: sizemb = utils.round(new_size_blk, BLOCKS_TO_MB) // BLOCKS_TO_MB lvm.extendLV(self.sdUUID, self.volUUID, sizemb)
def _create(cls, dom, imgUUID, volUUID, capacity, volFormat, preallocate, volParent, srcImgUUID, srcVolUUID, volPath, initial_size=None, add_bitmaps=False): """ Class specific implementation of volumeCreate. All the exceptions are properly handled and logged in volume.create() """ lv_size = cls.calculate_volume_alloc_size( preallocate, volFormat, capacity, initial_size) lv_size_mb = utils.round(lv_size, MiB) // MiB lvm.createLV(dom.sdUUID, volUUID, lv_size_mb, activate=True, initialTags=(sc.TAG_VOL_UNINIT,)) fileutils.rm_file(volPath) lvPath = lvm.lvPath(dom.sdUUID, volUUID) cls.log.info("Creating volume symlink from %r to %r", lvPath, volPath) os.symlink(lvPath, volPath) if not volParent: cls.log.info("Request to create %s volume %s with capacity = %s", sc.type2name(volFormat), volPath, capacity) if volFormat == sc.COW_FORMAT: operation = qemuimg.create(volPath, size=capacity, format=sc.fmt2str(volFormat), qcow2Compat=dom.qcow2_compat()) operation.run() else: # Create hardlink to template and its meta file cls.log.info("Request to create snapshot %s/%s of volume %s/%s " "with capacity %s", imgUUID, volUUID, srcImgUUID, srcVolUUID, capacity) volParent.clone(volPath, volFormat, capacity) with dom.acquireVolumeMetadataSlot(volUUID) as slot: mdTags = ["%s%s" % (sc.TAG_PREFIX_MD, slot), "%s%s" % (sc.TAG_PREFIX_PARENT, srcVolUUID), "%s%s" % (sc.TAG_PREFIX_IMAGE, imgUUID)] lvm.changeLVsTags( dom.sdUUID, (volUUID,), delTags=[sc.TAG_VOL_UNINIT], addTags=mdTags) try: lvm.deactivateLVs(dom.sdUUID, [volUUID]) except se.CannotDeactivateLogicalVolume: cls.log.warn("Cannot deactivate new created volume %s/%s", dom.sdUUID, volUUID, exc_info=True) return (dom.sdUUID, slot)
def getNextVolumeSize(self, curSize, capacity): """ Returns the next volume size in bytes. This value is based on the volExtensionChunk property and it's the size that should be requested for the next LV extension. curSize is the current size of the volume to be extended. For the leaf volume curSize == self.apparentsize. For internal volumes it is discovered by calling irs.getVolumeSize(). capacity is the maximum size of the volume. It can be discovered using libvirt.virDomain.blockInfo() or qemuimg.info(). """ nextSize = utils.round(curSize + self.volExtensionChunk, MiB) return min(nextSize, self.getMaxVolumeSize(capacity))
def test_volume_size_alignment(size_param): with fake_block_env() as env: sd_id = env.sd_manifest.sdUUID img_id = make_uuid() vol_id = make_uuid() make_block_volume(env.lvm, env.sd_manifest, size_param, img_id, vol_id) vol = env.sd_manifest.produceVolume(img_id, vol_id) expected_size = utils.round(size_param, sc.VG_EXTENT_SIZE) assert expected_size == vol.getCapacity() assert expected_size == int(env.lvm.getLV(sd_id, vol_id).size) lv_file_size = os.stat(env.lvm.lvPath(sd_id, vol_id)).st_size assert expected_size == lv_file_size
def reduce(self, newSize, allowActive=False): """ Reduce the size of the logical volume. Arguments: newSize (int) - new size in blocks allowActive (boolean) - indicates whether the LV is active """ self.log.info("Request to reduce LV %s of image %s in VG %s with " "size = %s allowActive = %s", self.volUUID, self.imgUUID, self.sdUUID, newSize, allowActive) sizemb = utils.round(newSize, BLOCKS_TO_MB) // BLOCKS_TO_MB lvm.reduceLV(self.sdUUID, self.volUUID, sizemb, force=allowActive)
def getNextVolumeSize(self, curSize, capacity): """ Returns the next volume size in bytes. This value is based on the volExtensionChunk property and it's the size that should be requested for the next LV extension. curSize is the current size of the volume to be extended. For the leaf volume curSize == self.apparentsize. For internal volumes it is discovered by calling irs.getVolumeSize(). capacity is the maximum size of the volume. It can be discovered using libvirt.virDomain.blockInfo() or qemuimg.info(). """ nextSize = utils.round(curSize + self.volExtensionChunk, constants.MEGAB) return min(nextSize, self.getMaxVolumeSize(capacity))
def test_volume_size_alignment(self, size_param): with fake_block_env() as env: sd_id = env.sd_manifest.sdUUID img_id = make_uuid() vol_id = make_uuid() make_block_volume(env.lvm, env.sd_manifest, size_param, img_id, vol_id) vol = env.sd_manifest.produceVolume(img_id, vol_id) extent_size = sc.VG_EXTENT_SIZE_MB * MB expected_size = utils.round(size_param, extent_size) self.assertEqual(expected_size / sc.BLOCK_SIZE, vol.getSize()) self.assertEqual(expected_size, int(env.lvm.getLV(sd_id, vol_id).size)) lv_file_size = os.stat(env.lvm.lvPath(sd_id, vol_id)).st_size self.assertEqual(expected_size, lv_file_size)
def _extend_base_allocation(base_vol, top_vol): if not (base_vol.is_block() and base_vol.getFormat() == sc.COW_FORMAT): return base_alloc = base_vol.getVolumeSize(bs=1) top_alloc = top_vol.getVolumeSize(bs=1) vol_chunk_size = (config.getint('irs', 'volume_utilization_chunk_mb') * constants.MEGAB) potential_alloc = base_alloc + top_alloc + vol_chunk_size # TODO: add chunk_size only if top is leaf. capacity = base_vol.getSize() * sc.BLOCK_SIZE max_alloc = utils.round(capacity * sc.COW_OVERHEAD, constants.MEGAB) actual_alloc = min(potential_alloc, max_alloc) actual_alloc_mb = (actual_alloc + constants.MEGAB - 1) / constants.MEGAB dom = sdCache.produce_manifest(base_vol.sdUUID) dom.extendVolume(base_vol.volUUID, actual_alloc_mb)
def _extend_base_allocation(base_vol, top_vol): if not (base_vol.is_block() and base_vol.getFormat() == sc.COW_FORMAT): return base_alloc = base_vol.getVolumeSize(bs=1) top_alloc = top_vol.getVolumeSize(bs=1) vol_chunk_size = (config.getint('irs', 'volume_utilization_chunk_mb') * constants.MEGAB) potential_alloc = base_alloc + top_alloc + vol_chunk_size # TODO: add chunk_size only if top is leaf. capacity = base_vol.getSizeBlk() * sc.BLOCK_SIZE max_alloc = utils.round(capacity * sc.COW_OVERHEAD, constants.MEGAB) actual_alloc = min(potential_alloc, max_alloc) actual_alloc_mb = (actual_alloc + constants.MEGAB - 1) / constants.MEGAB dom = sdCache.produce(base_vol.sdUUID) dom.extendVolume(base_vol.volUUID, actual_alloc_mb)
def make_block_volume(lvm, sd_manifest, size, imguuid, voluuid, parent_vol_id=sc.BLANK_UUID, vol_format=sc.RAW_FORMAT, prealloc=sc.PREALLOCATED_VOL, disk_type=image.UNKNOWN_DISK_TYPE, desc='fake volume'): sduuid = sd_manifest.sdUUID image_manifest = image.ImageManifest(sd_manifest.getRepoPath()) imagedir = image_manifest.getImageDir(sduuid, imguuid) if not os.path.exists(imagedir): os.makedirs(imagedir) size_mb = utils.round(size, MB) / MB lvm.createLV(sduuid, voluuid, size_mb) lv_size = int(lvm.getLV(sduuid, voluuid).size) lv_size_blk = lv_size / sc.BLOCK_SIZE with sd_manifest.acquireVolumeMetadataSlot( voluuid, sc.VOLUME_MDNUMBLKS) as slot: lvm.addtag(sduuid, voluuid, "%s%s" % (sc.TAG_PREFIX_MD, slot)) lvm.addtag(sduuid, voluuid, "%s%s" % (sc.TAG_PREFIX_PARENT, sc.BLANK_UUID)) lvm.addtag(sduuid, voluuid, "%s%s" % (sc.TAG_PREFIX_IMAGE, imguuid)) vol_class = sd_manifest.getVolumeClass() vol_class.newMetadata( (sduuid, slot), sduuid, imguuid, parent_vol_id, lv_size_blk, sc.type2name(vol_format), sc.type2name(prealloc), sc.type2name(sc.LEAF_VOL), disk_type, desc, sc.LEGAL_VOL)
def _size_param_to_bytes(self, size): # Size is received as a string in MB. We need to convert it to bytes # and round it up to a multiple of the VG extent size. extent_size = sc.VG_EXTENT_SIZE_MB << 20 size = int(size) << 20 return utils.round(size, extent_size)
def test_round(self, n, size, result): self.assertEqual(utils.round(n, size), result)
def test_max_size(self): conf = drive_config(format='cow') drive = Drive({}, self.log, **conf) size = utils.round(self.CAPACITY * drive.VOLWM_COW_OVERHEAD, constants.MEGAB) self.assertEqual(drive.getMaxVolumeSize(self.CAPACITY), size)
def test_max_size(self): conf = drive_config(format='cow', diskType=DISK_TYPE.BLOCK) drive = Drive(self.log, **conf) size = utils.round(self.CAPACITY * drive.VOLWM_COW_OVERHEAD, MiB) assert drive.getMaxVolumeSize(self.CAPACITY) == size
def padToBlockSize(self, path): size = _IOProcessOs(self._iop).stat(path).st_size newSize = utils.round(size, sc.BLOCK_SIZE_4K) log.debug("Truncating file %s to %d bytes", path, newSize) truncateFile(self._iop, path, newSize)
def _extendSizeRaw(self, newSize): # Since this method relies on lvm.extendLV (lvextend) when the # requested size is equal or smaller than the current size, the # request is siliently ignored. newSizeMb = utils.round(newSize, BLOCKS_TO_MB) // BLOCKS_TO_MB lvm.extendLV(self.sdUUID, self.volUUID, newSizeMb)
def _size_param_to_bytes(self, size_mb): # Size is received in MiB. We need to convert it to bytes # and round it up to a multiple of the VG extent size. return utils.round(size_mb * MiB, sc.VG_EXTENT_SIZE)
def snapshot(self): """Live snapshot command""" def norm_snap_drive_params(drive): """Normalize snapshot parameters""" if "baseVolumeID" in drive: base_drv = { "device": "disk", "domainID": drive["domainID"], "imageID": drive["imageID"], "volumeID": drive["baseVolumeID"] } target_drv = base_drv.copy() target_drv["volumeID"] = drive["volumeID"] elif "baseGUID" in drive: base_drv = {"GUID": drive["baseGUID"]} target_drv = {"GUID": drive["GUID"]} elif "baseUUID" in drive: base_drv = {"UUID": drive["baseUUID"]} target_drv = {"UUID": drive["UUID"]} else: base_drv, target_drv = (None, None) return base_drv, target_drv def rollback_drives(new_drives): """Rollback the prepared volumes for the snapshot""" for vm_dev_name, drive in new_drives.items(): try: self.vm.cif.teardownVolumePath(drive) except Exception: self.vm.log.exception("Unable to teardown drive: %s", vm_dev_name) def memory_snapshot(memory_volume_path): """Libvirt snapshot XML""" return vmxml.Element('memory', snapshot='external', file=memory_volume_path) def vm_conf_for_memory_snapshot(): """Returns the needed vm configuration with the memory snapshot""" return { 'restoreFromSnapshot': True, '_srcDomXML': self.vm.migratable_domain_xml(), 'elapsedTimeOffset': time.time() - self.vm.start_time } snap = vmxml.Element('domainsnapshot') disks = vmxml.Element('disks') new_drives = {} vm_drives = {} for drive in self.snap_drives: base_drv, tget_drv = norm_snap_drive_params(drive) try: self.vm.findDriveByUUIDs(tget_drv) except LookupError: # The vm is not already using the requested volume for the # snapshot, continuing. pass else: # The snapshot volume is the current one, skipping self.vm.log.debug("The volume is already in use: %s", tget_drv) continue # Next drive try: vm_drive = self.vm.findDriveByUUIDs(base_drv) except LookupError: # The volume we want to snapshot doesn't exist self.vm.log.error("The base volume doesn't exist: %s", base_drv) return response.error('snapshotErr') if vm_drive.hasVolumeLeases: self.vm.log.error('disk %s has volume leases', vm_drive.name) return response.error('noimpl') if vm_drive.transientDisk: self.vm.log.error('disk %s is a transient disk', vm_drive.name) return response.error('transientErr') vm_dev_name = vm_drive.name new_drives[vm_dev_name] = tget_drv.copy() new_drives[vm_dev_name]["type"] = "disk" new_drives[vm_dev_name]["diskType"] = vm_drive.diskType new_drives[vm_dev_name]["poolID"] = vm_drive.poolID new_drives[vm_dev_name]["name"] = vm_dev_name new_drives[vm_dev_name]["format"] = "cow" # We need to keep track of the drive object because # it keeps original data and used to generate snapshot element. # We keep the old volume ID so we can clear the block threshold. vm_drives[vm_dev_name] = (vm_drive, base_drv["volumeID"]) prepared_drives = {} for vm_dev_name, vm_device in new_drives.items(): # Adding the device before requesting to prepare it as we want # to be sure to teardown it down even when prepareVolumePath # failed for some unknown issue that left the volume active. prepared_drives[vm_dev_name] = vm_device try: new_drives[vm_dev_name]["path"] = \ self.vm.cif.prepareVolumePath(new_drives[vm_dev_name]) except Exception: self.vm.log.exception( 'unable to prepare the volume path for ' 'disk %s', vm_dev_name) rollback_drives(prepared_drives) return response.error('snapshotErr') drive, _ = vm_drives[vm_dev_name] snapelem = drive.get_snapshot_xml(vm_device) disks.appendChild(snapelem) snap.appendChild(disks) snap_flags = (libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_REUSE_EXT | libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_NO_METADATA) if self.memory_params: # Save the needed vm configuration # TODO: this, as other places that use pickle.dump # directly to files, should be done with outOfProcess vm_conf_vol = self.memory_params['dstparams'] vm_conf_vol_path = self.vm.cif.prepareVolumePath(vm_conf_vol) try: with open(vm_conf_vol_path, "rb+") as f: vm_conf = vm_conf_for_memory_snapshot() # protocol=2 is needed for clusters < 4.4 # (for Python 2 host compatibility) data = pickle.dumps(vm_conf, protocol=2) # Ensure that the volume is aligned; qemu-img may segfault # when converting unligned images. # https://bugzilla.redhat.com/1649788 aligned_length = utils.round(len(data), 4096) data = data.ljust(aligned_length, b"\0") f.write(data) f.flush() os.fsync(f.fileno()) finally: self.vm.cif.teardownVolumePath(vm_conf_vol) # Adding the memory volume to the snapshot xml memory_vol = self.memory_params['dst'] memory_vol_path = self.vm.cif.prepareVolumePath(memory_vol) snap.appendChild(memory_snapshot(memory_vol_path)) else: memory_vol = memory_vol_path = None snap_flags |= libvirt.VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY snapxml = xmlutils.tostring(snap) # TODO: this is debug information. For 3.6.x we still need to # see the XML even with 'info' as default level. self.vm.log.info("%s", snapxml) self._snapshot_job['memoryVolPath'] = memory_vol_path self._snapshot_job['memoryVol'] = memory_vol self._snapshot_job['newDrives'] = new_drives vm_drives_serialized = {} for k, v in vm_drives.items(): vm_drives_serialized[k] = [xmlutils.tostring(v[0].getXML()), v[1]] self._snapshot_job['vmDrives'] = vm_drives_serialized self.vm.update_snapshot_metadata(self._snapshot_job) # We need to stop the drive monitoring for two reasons, one is to # prevent spurious libvirt errors about missing drive paths (since # we're changing them), and also to prevent to trigger a drive # extension for the new volume with the apparent size of the old one # (the apparentsize is updated as last step in updateDriveParameters) self.vm.drive_monitor.disable() try: if self.should_freeze: self.vm.freeze() try: self.vm.log.info( "Taking a live snapshot (drives=%s," "memory=%s)", ', '.join(drive["name"] for drive in new_drives.values()), self.memory_params is not None) self.vm.run_dom_snapshot(snapxml, snap_flags) self.vm.log.info("Completed live snapshot") except libvirt.libvirtError: self.vm.log.exception("Unable to take snapshot") if self.should_freeze: self.vm.thaw() return response.error('snapshotErr') except: # In case the VM was shutdown in the middle of the snapshot # operation we keep doing the finalizing and reporting the failure. self._finalize_vm(memory_vol) res = False else: res = self.teardown(memory_vol_path, memory_vol, new_drives, vm_drives) if not res: raise RuntimeError("Failed to execute snapshot, " "considering the operation as failure")
def padToBlockSize(path): with open(path, 'a') as f: size = os.fstat(f.fileno()).st_size newSize = utils.round(size, sc.BLOCK_SIZE) log.info("Truncating file %s to %d bytes", path, newSize) os.ftruncate(f.fileno(), newSize)