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:
parent
218d43a2e0
commit
02cfab8257
@ -142,6 +142,9 @@ class PropertyHolder(object):
|
|||||||
'''
|
'''
|
||||||
if item.startswith('_'):
|
if item.startswith('_'):
|
||||||
raise AttributeError(item)
|
raise AttributeError(item)
|
||||||
|
# pre-fill cache if enabled
|
||||||
|
if self.app.cache_enabled and not self._properties_cache:
|
||||||
|
self._fetch_all_properties()
|
||||||
# cached value
|
# cached value
|
||||||
if item in self._properties_cache:
|
if item in self._properties_cache:
|
||||||
return self._properties_cache[item][0]
|
return self._properties_cache[item][0]
|
||||||
@ -197,6 +200,9 @@ class PropertyHolder(object):
|
|||||||
def __getattr__(self, item):
|
def __getattr__(self, item):
|
||||||
if item.startswith('_'):
|
if item.startswith('_'):
|
||||||
raise AttributeError(item)
|
raise AttributeError(item)
|
||||||
|
# pre-fill cache if enabled
|
||||||
|
if self.app.cache_enabled and not self._properties_cache:
|
||||||
|
self._fetch_all_properties()
|
||||||
# cached value
|
# cached value
|
||||||
if item in self._properties_cache:
|
if item in self._properties_cache:
|
||||||
value = self._properties_cache[item][1]
|
value = self._properties_cache[item][1]
|
||||||
@ -272,6 +278,50 @@ class PropertyHolder(object):
|
|||||||
raise qubesadmin.exc.QubesDaemonCommunicationError(
|
raise qubesadmin.exc.QubesDaemonCommunicationError(
|
||||||
'Received invalid value type: {}'.format(prop_type))
|
'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
|
@classmethod
|
||||||
def _local_properties(cls):
|
def _local_properties(cls):
|
||||||
'''
|
'''
|
||||||
|
@ -188,6 +188,37 @@ class TC_00_Properties(qubesadmin.tests.vm.VMTestCase):
|
|||||||
self.vm.property_get_default('prop1')
|
self.vm.property_get_default('prop1')
|
||||||
self.assertAllCalled()
|
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):
|
class TC_01_SpecialCases(qubesadmin.tests.vm.VMTestCase):
|
||||||
def test_000_get_name(self):
|
def test_000_get_name(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user