def test_two_vols_different_domains(self): log = [] locks = [ FakeGuardedLock('01_dom', 'dom1', 'mode', log), FakeGuardedLock('02_img', 'img1', 'mode', log), FakeGuardedLock('03_vol', 'vol1', 'mode', log), FakeGuardedLock('01_dom', 'dom2', 'mode', log), FakeGuardedLock('02_img', 'img2', 'mode', log), FakeGuardedLock('03_vol', 'vol2', 'mode', log)] expected = [ ('acquire', '01_dom', 'dom1', 'mode'), ('acquire', '01_dom', 'dom2', 'mode'), ('acquire', '02_img', 'img1', 'mode'), ('acquire', '02_img', 'img2', 'mode'), ('acquire', '03_vol', 'vol1', 'mode'), ('acquire', '03_vol', 'vol2', 'mode'), ('release', '03_vol', 'vol2', 'mode'), ('release', '03_vol', 'vol1', 'mode'), ('release', '02_img', 'img2', 'mode'), ('release', '02_img', 'img1', 'mode'), ('release', '01_dom', 'dom2', 'mode'), ('release', '01_dom', 'dom1', 'mode')] with guarded.context(locks): self.assertEqual(expected[:6], log) self.assertEqual(expected, log)
def _run(self): with guarded.context(self._vol_info.locks): self._validate() with self._vol_info.prepare(): with self._vol_info.volume_operation(): qemuimg.amend(self._vol_info.path, self._vol_attr.compat)
def _run(self): with guarded.context(self._source.locks + self._dest.locks): with self._source.prepare(), self._dest.prepare(): # Do not start copying if we have already been aborted if self._status == jobs.STATUS.ABORTED: return # Workaround for volumes containing VM configuration info that # were created with invalid vdsm metadata. if self._source.is_invalid_vm_conf_disk(): src_format = dst_format = qemuimg.FORMAT.RAW else: src_format = self._source.qemu_format dst_format = self._dest.qemu_format with self._dest.volume_operation(): self._operation = qemuimg.convert( self._source.path, self._dest.path, srcFormat=src_format, dstFormat=dst_format, dstQcow2Compat=self._dest.qcow2_compat, backing=self._dest.backing_path, backingFormat=self._dest.backing_qemu_format) with utils.closing(self._operation): self._operation.wait_for_completion()
def prepare(subchain): log.info("Preparing subchain %s for merge", subchain) with guarded.context(subchain.locks): with subchain.prepare(): _update_base_capacity(subchain.base_vol, subchain.top_vol) _extend_base_allocation(subchain.base_vol, subchain.top_vol)
def _run(self): self.log.info("Merging subchain %s", self.subchain) with guarded.context(self.subchain.locks): self.subchain.validate() with self.subchain.prepare(), self.subchain.volume_operation(): self.operation = qemuimg.commit( self.subchain.top_vol.getVolumePath(), topFormat=sc.fmt2str(self.subchain.top_vol.getFormat()), base=self.subchain.base_vol.getVolumePath()) self.operation.run()
def finalize(subchain): """ During finalize we distunguish between leaf merge and internal merge. In case of leaf merge, we only upate vdsm metadata, i.e. we call syncVolumeChain that marks the top volume as ILLEGAL. If the operation succeeds, the top volume is marked as ILLEGAL and will be removed by the engine. In case of failure, if the top volume is LEGAL, the user can recover by retrying cold merge. If the top volume is ILLEGAL, and the engine fails to delete the volume, a manual recovery is required. In case of internal merge, we need to update qcow metadata and vdsm metadata. For qcow metadata, we rebase top's child on base, and for vdsm metadata, we invoke syncVolumeChain that changes the child of the top to point to the base as its parent. As we would like to minimize the window where the top volume is ILLEGAL, we set it to ILLEGAL just before calling qemuimg rebase. After finalize internal merge, there are three possible states: 1. top volume illegal, qemu and vdsm chains updated. The operation will be finished by the engine deleting the top volume. 2. top volume is ILLEGAL but not rebased, both qemu chain and vdsm chain are synchronized. Manual recovery is possible by inspecting the chains and setting the top volume to legal. 3. top volume is ILLEGAL, qemu chain rebased, but vdsm chain wasn't modified or partly modified. Manual recovery is possible by updating vdsm chain. """ log.info("Finalizing subchain after merge: %s", subchain) with guarded.context(subchain.locks): # TODO: As each cold merge step - prepare, merge and finalize - # requires different volumes to be prepared, we will add a prepare # helper for each step. with subchain.prepare(): subchain.validate() dom = sdCache.produce_manifest(subchain.sd_id) if subchain.top_vol.isLeaf(): _finalize_leaf_merge(dom, subchain) else: _finalize_internal_merge(dom, subchain) if subchain.base_vol.can_reduce(): # If the top volume is leaf, the base volume will become a leaf # after the top volume is deleted. optimal_size = subchain.base_vol.optimal_size( as_leaf=subchain.top_vol.isLeaf()) actual_size = subchain.base_vol.getVolumeSize() # Optimal size must be computed while the image is prepared, but # reducing with the volume still active will issue a warning from LVM. # Thus, reduce after having teardown the volume. if subchain.base_vol.can_reduce() and optimal_size < actual_size: _shrink_base_volume(subchain, optimal_size)
def test_deadlock(self): log = [] locks = [ FakeGuardedLock('00_storage', 'dom', 'shared', log), # Attemting to lock next locks will deadlock in resourceManager. FakeGuardedLock('02_img.dom', 'img', 'exclusive', log), FakeGuardedLock('02_img.dom', 'img', 'shared', log), FakeGuardedLock('03_vol.dom', 'vol', 'exclusive', log), ] # Creating a context should raise with self.assertRaises(guarded.Deadlock): with guarded.context(locks): pass # Without locking any of the locks self.assertEqual([], log)
def test_one_vol(self): log = [] locks = [ FakeGuardedLock('01_dom', 'dom', 'mode', log), FakeGuardedLock('02_img', 'img', 'mode', log), FakeGuardedLock('03_vol', 'vol', 'mode', log)] expected = [ ('acquire', '01_dom', 'dom', 'mode'), ('acquire', '02_img', 'img', 'mode'), ('acquire', '03_vol', 'vol', 'mode'), ('release', '03_vol', 'vol', 'mode'), ('release', '02_img', 'img', 'mode'), ('release', '01_dom', 'dom', 'mode')] with guarded.context(locks): self.assertEqual(expected[:3], log) self.assertEqual(expected, log)
def test_aquire_failure_then_release_failure(self): log = [] locks = [ FakeGuardedLock('01_dom', 'dom1', 'mode', log), FakeGuardedLock('02_img', 'img1', 'mode', log, release=InjectedFailure), FakeGuardedLock('03_vol', 'vol1', 'mode', log, acquire=InjectedFailure)] expected = [ ('acquire', '01_dom', 'dom1', 'mode'), ('acquire', '02_img', 'img1', 'mode'), ('release', '01_dom', 'dom1', 'mode')] with self.assertRaises(InjectedFailure): with guarded.context(locks): pass self.assertEqual(expected, log)
def test_one_vol(self): log = [] locks = [ FakeGuardedLock('01_dom', 'dom', 'mode', log), FakeGuardedLock('02_img', 'img', 'mode', log), FakeGuardedLock('03_vol', 'vol', 'mode', log) ] expected = [('acquire', '01_dom', 'dom', 'mode'), ('acquire', '02_img', 'img', 'mode'), ('acquire', '03_vol', 'vol', 'mode'), ('release', '03_vol', 'vol', 'mode'), ('release', '02_img', 'img', 'mode'), ('release', '01_dom', 'dom', 'mode')] with guarded.context(locks): self.assertEqual(expected[:3], log) self.assertEqual(expected, log)
def finalize(subchain): """ During finalize we distunguish between leaf merge and internal merge. In case of leaf merge, we only upate vdsm metadata, i.e. we call syncVolumeChain that marks the top volume as ILLEGAL. If the operation succeeds, the top volume is marked as ILLEGAL and will be removed by the engine. In case of failure, if the top volume is LEGAL, the user can recover by retrying cold merge. If the top volume is ILLEGAL, and the engine fails to delete the volume, a manual recovery is required. In case of internal merge, we need to update qcow metadata and vdsm metadata. For qcow metadata, we rebase top's child on base, and for vdsm metadata, we invoke syncVolumeChain that changes the child of the top to point to the base as its parent. As we would like to minimize the window where the top volume is ILLEGAL, we set it to ILLEGAL just before calling qemuimg rebase. After finalize internal merge, there are three possible states: 1. top volume illegal, qemu and vdsm chains updated. The operation will be finished by the engine deleting the top volume. 2. top volume is ILLEGAL but not rebased, both qemu chain and vdsm chain are synchronized. Manual recovery is possible by inspecting the chains and setting the top volume to legal. 3. top volume is ILLEGAL, qemu chain rebased, but vdsm chain wasn't modified or partly modified. Manual recovery is possible by updating vdsm chain. """ log.info("Finalizing subchain after merge: %s", subchain) with guarded.context(subchain.locks): # TODO: As each cold merge step - prepare, merge and finalize - # requires different volumes to be prepared, we will add a prepare # helper for each step. with subchain.prepare(): subchain.validate() dom = sdCache.produce_manifest(subchain.sd_id) if subchain.top_vol.isLeaf(): _finalize_leaf_merge(dom, subchain) else: _finalize_internal_merge(dom, subchain) if subchain.base_vol.chunked(): # optimal_size must be called when the volume is prepared optimal_size = subchain.base_vol.optimal_size() if subchain.base_vol.chunked(): _shrink_base_volume(subchain, optimal_size)
def test_fail_inside_context(self): log = [] locks = [ FakeGuardedLock('01_dom', 'dom1', 'mode', log), FakeGuardedLock('02_img', 'img1', 'mode', log), FakeGuardedLock('03_vol', 'vol1', 'mode', log) ] expected = [('acquire', '01_dom', 'dom1', 'mode'), ('acquire', '02_img', 'img1', 'mode'), ('acquire', '03_vol', 'vol1', 'mode'), ('release', '03_vol', 'vol1', 'mode'), ('release', '02_img', 'img1', 'mode'), ('release', '01_dom', 'dom1', 'mode')] with self.assertRaises(InjectedFailure): with guarded.context(locks): raise InjectedFailure() self.assertEqual(expected, log)
def test_fail_inside_context_with_release_failure(self): log = [] locks = [ FakeGuardedLock('01_dom', 'dom1', 'mode', log), FakeGuardedLock('02_img', 'img1', 'mode', log), FakeGuardedLock('03_vol', 'vol1', 'mode', log, release=InjectedFailure)] expected = [ ('acquire', '01_dom', 'dom1', 'mode'), ('acquire', '02_img', 'img1', 'mode'), ('acquire', '03_vol', 'vol1', 'mode'), ('release', '02_img', 'img1', 'mode'), ('release', '01_dom', 'dom1', 'mode')] with self.assertRaises(RuntimeError): with guarded.context(locks): raise RuntimeError() self.assertEqual(expected, log)
def finalize(subchain): log.info("Finalizing subchain after merge: %s", subchain) with guarded.context(subchain.locks): with subchain.prepare(): subchain.validate() # Base volume must be ILLEGAL. Otherwise, VM could be run while # performing cold merge. base_legality = subchain.base_vol.getLegality() if base_legality == sc.LEGAL_VOL: raise se.UnexpectedVolumeState( subchain.base_id, sc.ILLEGAL_VOL, base_legality) dom = sdCache.produce_manifest(subchain.sd_id) _update_qemu_metadata(dom, subchain) _update_vdsm_metadata(dom, subchain) subchain.base_vol.setLegality(sc.LEGAL_VOL)
def _run(self): self.log.info("Merging subchain %s", self.subchain) with guarded.context(self.subchain.locks): self.subchain.validate() # Base volume must be ILLEGAL. Otherwise, VM could be run while # performing cold merge. base_legality = self.subchain.base_vol.getLegality() if base_legality == sc.LEGAL_VOL: raise se.UnexpectedVolumeState(self.subchain.base_id, sc.ILLEGAL_VOL, base_legality) with self.subchain.prepare(), self.subchain.volume_operation(): self.operation = qemuimg.commit( self.subchain.top_vol.getVolumePath(), topFormat=sc.fmt2str(self.subchain.top_vol.getFormat()), base=self.subchain.base_vol.getVolumePath()) with utils.closing(self.operation): self.operation.wait_for_completion()
def test_release_failure(self): log = [] locks = [ FakeGuardedLock('01_dom', 'dom1', 'mode', log), FakeGuardedLock('02_img', 'img1', 'mode', log), FakeGuardedLock('03_vol', 'vol1', 'mode', log, release=InjectedFailure) ] expected = [('acquire', '01_dom', 'dom1', 'mode'), ('acquire', '02_img', 'img1', 'mode'), ('acquire', '03_vol', 'vol1', 'mode'), ('release', '02_img', 'img1', 'mode'), ('release', '01_dom', 'dom1', 'mode')] with self.assertRaises(guarded.ReleaseError): with guarded.context(locks): pass self.assertEqual(expected, log)
def test_two_vols_same_image(self): log = [] locks = [ FakeGuardedLock('01_dom', 'dom1', 'mode', log), FakeGuardedLock('02_img', 'img1', 'mode', log), FakeGuardedLock('03_vol', 'vol1', 'mode', log), FakeGuardedLock('01_dom', 'dom1', 'mode', log), FakeGuardedLock('02_img', 'img1', 'mode', log), FakeGuardedLock('03_vol', 'vol2', 'mode', log) ] expected = [('acquire', '01_dom', 'dom1', 'mode'), ('acquire', '02_img', 'img1', 'mode'), ('acquire', '03_vol', 'vol1', 'mode'), ('acquire', '03_vol', 'vol2', 'mode'), ('release', '03_vol', 'vol2', 'mode'), ('release', '03_vol', 'vol1', 'mode'), ('release', '02_img', 'img1', 'mode'), ('release', '01_dom', 'dom1', 'mode')] with guarded.context(locks): self.assertEqual(expected[:4], log) self.assertEqual(expected, log)
def _run(self): with guarded.context(self._source.locks + self._dest.locks): with self._source.prepare(), self._dest.prepare(): # Do not start copying if we have already been aborted if self._status == jobs.STATUS.ABORTED: return # Workaround for volumes containing VM configuration info that # were created with invalid vdsm metadata. if self._source.is_invalid_vm_conf_disk(): src_format = dst_format = qemuimg.FORMAT.RAW else: src_format = self._source.qemu_format dst_format = self._dest.qemu_format self._validate_copy_bitmaps(src_format, dst_format) with self._dest.volume_operation(): self._operation = qemuimg.convert( self._source.path, self._dest.path, srcFormat=src_format, dstFormat=dst_format, dstQcow2Compat=self._dest.qcow2_compat, backing=self._dest.backing_path, backingFormat=self._dest.backing_qemu_format, unordered_writes=self._dest. recommends_unordered_writes, create=self._dest.requires_create, bitmaps=self._copy_bitmaps, target_is_zero=self._dest.zero_initialized, ) with utils.stopwatch("Copy volume {}".format( self._source.path), level=logging.INFO, log=log): self._operation.run()
def _run(self): with guarded.context(self._source.locks + self._dest.locks): with self._source.prepare(), self._dest.prepare(): # Do not start copying if we have already been aborted if self._status == jobs.STATUS.ABORTED: return # Workaround for volumes containing VM configuration info that # were created with invalid vdsm metadata. if self._source.is_invalid_vm_conf_disk(): src_format = dst_format = qemuimg.FORMAT.RAW else: src_format = self._source.qemu_format dst_format = self._dest.qemu_format with self._dest.volume_operation(): self._operation = qemuimg.convert( self._source.path, self._dest.path, srcFormat=src_format, dstFormat=dst_format, backing=self._dest.backing_path, backingFormat=self._dest.backing_qemu_format) self._operation.wait_for_completion()
def _run(self): with guarded.context(self._vol_info.locks): self._validate() with self._vol_info.prepare(): with self._vol_info.volume_operation(): virtsparsify.sparsify_inplace(self._vol_info.path)
def _run(self): with guarded.context(self._vol_info.locks): self._validate() with self._vol_info.prepare(): with self._vol_info.volume_operation(): bitmaps.clear_bitmaps(self._vol_info.path)
def test_empty(self): with guarded.context([]): pass
def _run(self): with guarded.context(self._endpoint.locks): self._endpoint.volume.set_generation(self._endpoint.generation, self._new_gen)
def _run(self): with guarded.context(self._endpoint.locks): self._endpoint.volume.update_attributes(self._endpoint.generation, self._vol_attr)
def _run(self): with guarded.context(self._vol_info.locks): self._validate() with self._vol_info.prepare(): with self._vol_info.volume_operation(): qemuimg.amend(self._vol_info.path, self._qcow2_attr.compat)