Try to use new property.GetAll method to pre-fill the cache

When caching is enabled, reduce number of calls by getting all the
properties at once. If the call is not available (for example because of
the policy), fallback to getting individual values.

QubesOS/qubes-issues#5415
This commit is contained in:
Marek Marczykowski-Górecki 2019-12-03 06:16:10 +01:00
parent 218d43a2e0
commit 02cfab8257
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 81 additions and 0 deletions

View File

@ -142,6 +142,9 @@ class PropertyHolder(object):
'''
if item.startswith('_'):
raise AttributeError(item)
# pre-fill cache if enabled
if self.app.cache_enabled and not self._properties_cache:
self._fetch_all_properties()
# cached value
if item in self._properties_cache:
return self._properties_cache[item][0]
@ -197,6 +200,9 @@ class PropertyHolder(object):
def __getattr__(self, item):
if item.startswith('_'):
raise AttributeError(item)
# pre-fill cache if enabled
if self.app.cache_enabled and not self._properties_cache:
self._fetch_all_properties()
# cached value
if item in self._properties_cache:
value = self._properties_cache[item][1]
@ -272,6 +278,50 @@ class PropertyHolder(object):
raise qubesadmin.exc.QubesDaemonCommunicationError(
'Received invalid value type: {}'.format(prop_type))
def _fetch_all_properties(self):
"""
Retrieve all properties values at once using (prefix).property.GetAll
method. If it succeed, save retrieved values in the properties cache.
If the request fails (for example because of qrexec policy), do nothing.
Exceptions when parsing received value are not handled.
:return: None
"""
def unescape(line):
"""Handle \\-escaped values, generates a list of character codes"""
escaped = False
for char in line:
if escaped:
assert char in (ord('n'), ord('\\'))
if char == ord('n'):
yield ord('\n')
elif char == ord('\\'):
yield char
escaped = False
elif char == ord('\\'):
escaped = True
else:
yield char
assert not escaped
try:
properties_str = self.qubesd_call(
self._method_dest,
self._method_prefix + 'GetAll',
None,
None)
except qubesadmin.exc.QubesDaemonNoResponseError:
return
for line in properties_str.splitlines():
# decode newlines
line = bytes(unescape(line))
name, property_str = line.split(b' ', 1)
name = name.decode()
is_default, value = self._deserialize_property(property_str)
self._properties_cache[name] = (is_default, value)
self._properties = list(self._properties_cache.keys())
@classmethod
def _local_properties(cls):
'''

View File

@ -188,6 +188,37 @@ class TC_00_Properties(qubesadmin.tests.vm.VMTestCase):
self.vm.property_get_default('prop1')
self.assertAllCalled()
def test_050_get_all(self):
self.app.expected_calls[
('test-vm', 'admin.vm.property.GetAll', None, None)] = [
b'0\x00name default=False type=str test-vm\n'
b'debug default=True type=bool False\n'
b'backup_timestamp default=True type=int \n'
b'kernel default=True type=str 1.0\n'
b'qid default=True type=int 3\n'
b'kernelopts default=False type=str opt1\\nopt2\\nopt3\\\\opt4\n'
b'klass default=True type=str AppVM\n', ]
self.app.cache_enabled = True
self.assertEqual(self.vm.name, 'test-vm')
with self.assertRaises(AttributeError):
self.vm.backup_timestamp
self.assertEqual(self.vm.debug, False)
self.assertEqual(self.vm.qid, 3)
self.assertEqual(self.vm.kernelopts, 'opt1\nopt2\nopt3\\opt4')
self.assertTrue(self.vm.property_is_default('kernel'))
def test_051_get_all_fallback(self):
self.app.expected_calls[
('test-vm', 'admin.vm.property.GetAll', None, None)] = [b'', ]
self.app.expected_calls[
('test-vm', 'admin.vm.property.Get', 'qid', None)] = \
b'0\x00default=True type=int 3'
self.app.cache_enabled = True
self.assertEqual(self.vm.qid, 3)
# check if cached
self.assertEqual(self.vm.qid, 3)
self.assertAllCalled()
class TC_01_SpecialCases(qubesadmin.tests.vm.VMTestCase):
def test_000_get_name(self):