Skip to content

Commit

Permalink
Cache power state when caching is enabled
Browse files Browse the repository at this point in the history
Power state changes are signaled with events too, so it is possible to
cache it and update/invalidate cache with events.
Additionally, admin.vm.List returns a power state, so the cache can be
populated early. This in particular greatly improves qvm-ls performance -
eliminate admin.vm.CurrentState call at all.

QubesOS/qubes-issues#3293
  • Loading branch information
marmarek committed Apr 20, 2020
1 parent 06b491f commit c946b80
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 3 deletions.
51 changes: 50 additions & 1 deletion qubesadmin/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ def refresh_cache(self, force=False):
props = props.split(' ')
new_vm_list[vm_name] = dict(
[vm_prop.split('=', 1) for vm_prop in props])
# if cache not enabled, drop power state
if not self.app.cache_enabled:
try:
del new_vm_list[vm_name]['state']
except KeyError:
pass

self._vm_list = new_vm_list
for name, vm in list(self._vm_objects.items()):
Expand Down Expand Up @@ -103,9 +109,12 @@ def get_blind(self, item):
# done by 'item not in self' check above, unless blind_mode is
# enabled
klass = None
power_state = None
if self._vm_list and item in self._vm_list:
klass = self._vm_list[item]['class']
self._vm_objects[item] = cls(self.app, item, klass=klass)
power_state = self._vm_list[item].get('state')
self._vm_objects[item] = cls(self.app, item, klass=klass,
power_state=power_state)
return self._vm_objects[item]

def __contains__(self, item):
Expand Down Expand Up @@ -605,6 +614,36 @@ def _invalidate_cache(self, subject, event, name, **kwargs):
except KeyError:
pass

def _invalidate_power_state_cache(self, subject, event, **kwargs):
""" Invalidate cached VM power state.
This method is designed to be hooed as an event handler for:
- domain-pre-start
- domain-start
- domain-shutdown
- domain-paused
- domain-unpaused
:param subject: a VM object
:param event: name of the event
:param kwargs: other arguments
:return:
"""

if event == 'domain-pre-start':
power_state = 'Transient'
elif event == 'domain-start':
power_state = 'Running'
elif event == 'domain-shutdown':
power_state = 'Halted'
elif event == 'domain-paused':
power_state = 'Paused'
elif event == 'domain-unpaused':
power_state = 'Running'

# pylint: disable=protected-access
subject._power_state_cache = power_state

def register_cache_handlers(self, dispatcher):
"""Register cache-related event handlers
Expand All @@ -613,6 +652,16 @@ def register_cache_handlers(self, dispatcher):

dispatcher.add_handler('property-set:*', self._invalidate_cache)
dispatcher.add_handler('property-del:*', self._invalidate_cache)
dispatcher.add_handler('domain-pre-start',
self._invalidate_power_state_cache)
dispatcher.add_handler('domain-start',
self._invalidate_power_state_cache)
dispatcher.add_handler('domain-shutdown',
self._invalidate_power_state_cache)
dispatcher.add_handler('domain-paused',
self._invalidate_power_state_cache)
dispatcher.add_handler('domain-unpaused',
self._invalidate_power_state_cache)


class QubesLocal(QubesBase):
Expand Down
27 changes: 27 additions & 0 deletions qubesadmin/tests/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,33 @@ def test_009_getitem_cache_class(self):
self.fail('VM not found in collection')
self.assertAllCalled()

def test_010_getitem_cache_power_state(self):
self.app.cache_enabled = True
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\x00test-vm class=AppVM state=Running\n'
try:
vm = self.app.domains['test-vm']
self.assertEqual(vm.name, 'test-vm')
self.assertEqual(vm.klass, 'AppVM')
self.assertEqual(vm.get_power_state(), 'Running')
except KeyError:
self.fail('VM not found in collection')
self.assertAllCalled()

def test_011_getitem_non_cache_power_state(self):
self.app.expected_calls[('dom0', 'admin.vm.List', None, None)] = \
b'0\x00test-vm class=AppVM state=Running\n'
self.app.expected_calls[('test-vm', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Running mem=1024'
try:
vm = self.app.domains['test-vm']
self.assertEqual(vm.name, 'test-vm')
self.assertEqual(vm.klass, 'AppVM')
self.assertEqual(vm.get_power_state(), 'Running')
except KeyError:
self.fail('VM not found in collection')
self.assertAllCalled()


class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
def setUp(self):
Expand Down
10 changes: 8 additions & 2 deletions qubesadmin/vm/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,11 @@ class QubesVM(qubesadmin.base.PropertyHolder):

firewall = None

def __init__(self, app, name, klass=None):
def __init__(self, app, name, klass=None, power_state=None):
super(QubesVM, self).__init__(app, 'admin.vm.property.', name)
self._volumes = None
self._klass = klass
self._power_state_cache = power_state
self.log = logging.getLogger(name)
self.tags = qubesadmin.tags.Tags(self)
self.features = qubesadmin.features.Features(self)
Expand Down Expand Up @@ -181,8 +182,13 @@ def get_power_state(self):
'''

if self._power_state_cache is not None:
return self._power_state_cache
try:
return self._get_current_state()['power_state']
power_state = self._get_current_state()['power_state']
if self.app.cache_enabled:
self._power_state_cache = power_state
return power_state
except qubesadmin.exc.QubesDaemonNoResponseError:
return 'NA'

Expand Down

0 comments on commit c946b80

Please sign in to comment.