Merge branch 'bug3164'
* bug3164: tests: add regression test for #3164 storage/lvm: make sure volume cache is refreshed after changes storage/lvm: fix Volume.verify() storage/lvm: remove old volume only after successfully cloning new one
This commit is contained in:
		
						commit
						836d9f902a
					
				| @ -275,6 +275,9 @@ class Volume(object): | ||||
|     def verify(self): | ||||
|         ''' Verifies the volume. | ||||
| 
 | ||||
|         This function is supposed to either return :py:obj:`True`, or raise | ||||
|         an exception. | ||||
| 
 | ||||
|         This can be implemented as a coroutine.''' | ||||
|         raise self._not_implemented("verify") | ||||
| 
 | ||||
|  | ||||
| @ -270,10 +270,22 @@ class ThinVolume(qubes.storage.Volume): | ||||
|             qubes_lvm(cmd, self.log) | ||||
|             self._remove_revisions() | ||||
| 
 | ||||
|         cmd = ['remove', self.vid] | ||||
|         qubes_lvm(cmd, self.log) | ||||
|         cmd = ['clone', self._vid_snap, self.vid] | ||||
|         # TODO: when converting this function to coroutine, this _must_ be | ||||
|         # under a lock | ||||
|         # remove old volume only after _successful_ clone of the new one | ||||
|         cmd = ['rename', self.vid, self.vid + '-tmp'] | ||||
|         qubes_lvm(cmd, self.log) | ||||
|         try: | ||||
|             cmd = ['clone', self._vid_snap, self.vid] | ||||
|             qubes_lvm(cmd, self.log) | ||||
|         except: | ||||
|             # restore original volume | ||||
|             cmd = ['rename', self.vid + '-tmp', self.vid] | ||||
|             qubes_lvm(cmd, self.log) | ||||
|             raise | ||||
|         else: | ||||
|             cmd = ['remove', self.vid + '-tmp'] | ||||
|             qubes_lvm(cmd, self.log) | ||||
| 
 | ||||
| 
 | ||||
|     def create(self): | ||||
| @ -419,34 +431,47 @@ class ThinVolume(qubes.storage.Volume): | ||||
| 
 | ||||
| 
 | ||||
|     def start(self): | ||||
|         if self.snap_on_start or self.save_on_stop: | ||||
|             if not self.save_on_stop or not self.is_dirty(): | ||||
|                 self._snapshot() | ||||
|         else: | ||||
|             self._reset() | ||||
| 
 | ||||
|         reset_cache() | ||||
|         try: | ||||
|             if self.snap_on_start or self.save_on_stop: | ||||
|                 if not self.save_on_stop or not self.is_dirty(): | ||||
|                     self._snapshot() | ||||
|             else: | ||||
|                 self._reset() | ||||
|         finally: | ||||
|             reset_cache() | ||||
|         return self | ||||
| 
 | ||||
|     def stop(self): | ||||
|         if self.save_on_stop: | ||||
|             self._commit() | ||||
|         if self.snap_on_start or self.save_on_stop: | ||||
|             cmd = ['remove', self._vid_snap] | ||||
|             qubes_lvm(cmd, self.log) | ||||
|         else: | ||||
|             cmd = ['remove', self.vid] | ||||
|             qubes_lvm(cmd, self.log) | ||||
|         reset_cache() | ||||
|         try: | ||||
|             if self.save_on_stop: | ||||
|                 self._commit() | ||||
|             if self.snap_on_start or self.save_on_stop: | ||||
|                 cmd = ['remove', self._vid_snap] | ||||
|                 qubes_lvm(cmd, self.log) | ||||
|             else: | ||||
|                 cmd = ['remove', self.vid] | ||||
|                 qubes_lvm(cmd, self.log) | ||||
|         finally: | ||||
|             reset_cache() | ||||
|         return self | ||||
| 
 | ||||
|     def verify(self): | ||||
|         ''' Verifies the volume. ''' | ||||
|         if not self.save_on_stop and not self.snap_on_start: | ||||
|             # volatile volumes don't need any files | ||||
|             return True | ||||
|         if self.source is not None: | ||||
|             vid = str(self.source) | ||||
|         else: | ||||
|             vid = self.vid | ||||
|         try: | ||||
|             vol_info = size_cache[self.vid] | ||||
|             return vol_info['attr'][4] == 'a' | ||||
|             vol_info = size_cache[vid] | ||||
|             if vol_info['attr'][4] != 'a': | ||||
|                 raise qubes.storage.StoragePoolException( | ||||
|                     'volume {} not active'.format(vid)) | ||||
|         except KeyError: | ||||
|             return False | ||||
|             raise qubes.storage.StoragePoolException( | ||||
|                 'volume {} missing'.format(vid)) | ||||
| 
 | ||||
| 
 | ||||
|     def block_device(self): | ||||
| @ -493,6 +518,8 @@ def qubes_lvm(cmd, log=logging.getLogger('qubes.storage.lvm')): | ||||
|         lvm_cmd = ["lvextend", "-L%s" % size, cmd[1]] | ||||
|     elif action == 'activate': | ||||
|         lvm_cmd = ['lvchange', '-ay', cmd[1]] | ||||
|     elif action == 'rename': | ||||
|         lvm_cmd = ['lvrename', cmd[1], cmd[2]] | ||||
|     else: | ||||
|         raise NotImplementedError('unsupported action: ' + action) | ||||
|     if lvm_is_very_old: | ||||
|  | ||||
| @ -30,9 +30,12 @@ import tempfile | ||||
| import time | ||||
| import unittest | ||||
| 
 | ||||
| import collections | ||||
| 
 | ||||
| import qubes | ||||
| import qubes.firewall | ||||
| import qubes.tests | ||||
| import qubes.storage | ||||
| import qubes.vm.appvm | ||||
| import qubes.vm.qubesvm | ||||
| import qubes.vm.standalonevm | ||||
| @ -79,6 +82,49 @@ class TC_00_Basic(qubes.tests.SystemTestCase): | ||||
|         self.loop.run_until_complete(asyncio.sleep(0.1)) | ||||
|         self.assertTrue(flag) | ||||
| 
 | ||||
|     def _test_200_on_domain_start(self, vm, event, **_kwargs): | ||||
|         '''Simulate domain crash just after startup''' | ||||
|         vm.libvirt_domain.destroy() | ||||
| 
 | ||||
|     def test_200_shutdown_event_race(self): | ||||
|         '''Regression test for 3164''' | ||||
|         vmname = self.make_vm_name('appvm') | ||||
| 
 | ||||
|         self.vm = self.app.add_new_vm(qubes.vm.appvm.AppVM, | ||||
|             name=vmname, template=self.app.default_template, | ||||
|             label='red') | ||||
|         # help the luck a little - don't wait for qrexec to easier win the race | ||||
|         self.vm.features['qrexec'] = False | ||||
|         self.loop.run_until_complete(self.vm.create_on_disk()) | ||||
|         # another way to help the luck a little - make sure the private | ||||
|         # volume is first in (normally unordered) dict - this way if any | ||||
|         # volume action fails, it will be at or after private volume - not | ||||
|         # before (preventing private volume action) | ||||
|         old_volumes = self.vm.volumes | ||||
|         self.vm.volumes = collections.OrderedDict() | ||||
|         self.vm.volumes['private'] = old_volumes.pop('private') | ||||
|         self.vm.volumes.update(old_volumes.items()) | ||||
|         del old_volumes | ||||
| 
 | ||||
|         self.loop.run_until_complete(self.vm.start()) | ||||
| 
 | ||||
|         # kill it the way it does not give a chance for domain-shutdown it | ||||
|         # execute | ||||
|         self.vm.libvirt_domain.destroy() | ||||
| 
 | ||||
|         # now, lets try to start the VM again, before domain-shutdown event | ||||
|         # got handled (#3164), and immediately trigger second domain-shutdown | ||||
|         self.vm.add_handler('domain-start', self._test_200_on_domain_start) | ||||
|         self.loop.run_until_complete(self.vm.start()) | ||||
| 
 | ||||
|         # and give a chance for both domain-shutdown handlers to execute | ||||
|         self.loop.run_until_complete(asyncio.sleep(1)) | ||||
|         with self.assertNotRaises(qubes.exc.QubesException): | ||||
|             # if the above caused two domain-shutdown handlers being called | ||||
|             # one after another, private volume is gone | ||||
|             self.loop.run_until_complete(self.vm.storage.verify()) | ||||
| 
 | ||||
| 
 | ||||
| class TC_01_Properties(qubes.tests.SystemTestCase): | ||||
|     # pylint: disable=attribute-defined-outside-init | ||||
|     def setUp(self): | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Marek Marczykowski-Górecki
						Marek Marczykowski-Górecki