Enable caching in qvm-ls and qvm-prefs

Both tools issue a large number of Admin API calls and greatly benefit
from a cache filled with a single per-vm Admin API call
(admin.vm.property.GetAll). In case of qvm-ls, this also saves multiple
admin.vm.CurrentState calls (power state is given in the admin.vm.List
response too).

QubesOS/qubes-issues#3293
This commit is contained in:
Marek Marczykowski-Górecki 2020-04-20 03:08:24 +02:00
parent 79c7392424
commit c081ed8c82
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
5 changed files with 50 additions and 53 deletions

View File

@ -29,11 +29,9 @@ class TC_00_qubes_prefs(qubesadmin.tests.QubesTestCase):
('dom0', 'admin.property.List', None, None)] = \ ('dom0', 'admin.property.List', None, None)] = \
b'0\x00prop1\nprop2\n' b'0\x00prop1\nprop2\n'
self.app.expected_calls[ self.app.expected_calls[
('dom0', 'admin.property.Get', 'prop1', None)] = \ ('dom0', 'admin.property.GetAll', None, None)] = \
b'0\x00default=True type=str value1' b'0\x00prop1 default=True type=str value1\n' \
self.app.expected_calls[ b'prop2 default=False type=str value2\n'
('dom0', 'admin.property.Get', 'prop2', None)] = \
b'0\x00default=False type=str value2'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with qubesadmin.tests.tools.StdoutBuffer() as stdout:
self.assertEqual(0, qubesadmin.tools.qubes_prefs.main([], app=self.app)) self.assertEqual(0, qubesadmin.tools.qubes_prefs.main([], app=self.app))
self.assertEqual(stdout.getvalue(), self.assertEqual(stdout.getvalue(),

View File

@ -278,42 +278,34 @@ class TC_90_List_with_qubesd_calls(qubesadmin.tests.QubesTestCase):
b'0\x00vm1 class=AppVM state=Running\n' \ b'0\x00vm1 class=AppVM state=Running\n' \
b'template1 class=TemplateVM state=Halted\n' \ b'template1 class=TemplateVM state=Halted\n' \
b'sys-net class=AppVM state=Running\n' b'sys-net class=AppVM state=Running\n'
self.app.expected_calls[
('vm1', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Running'
self.app.expected_calls[
('sys-net', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Running'
self.app.expected_calls[
('template1', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Halted'
props = { props = {
'label': b'type=label green', 'label': 'type=label green',
'template': b'type=vm template1', 'template': 'type=vm template1',
'netvm': b'type=vm sys-net', 'netvm': 'type=vm sys-net',
# 'virt_mode': b'type=str pv', # 'virt_mode': b'type=str pv',
} }
for key, value in props.items():
self.app.expected_calls[
('vm1', 'admin.vm.property.Get', key, None)] = \
b'0\x00default=True ' + value
# setup template1
props['label'] = b'type=label black'
for key, value in props.items():
self.app.expected_calls[
('template1', 'admin.vm.property.Get', key, None)] = \
b'0\x00default=True ' + value
self.app.expected_calls[ self.app.expected_calls[
('template1', 'admin.vm.property.Get', 'template', None)] = \ ('vm1', 'admin.vm.property.GetAll', None, None)] = \
b'' # request refused - no such property b'0\x00' + ''.join(
'{} default=True {}\n'.format(key, value)
for key, value in props.items()).encode()
# setup sys-net # setup sys-net
props['label'] = b'type=label red' props['label'] = 'type=label red'
for key, value in props.items(): self.app.expected_calls[
self.app.expected_calls[ ('sys-net', 'admin.vm.property.GetAll', None, None)] = \
('sys-net', 'admin.vm.property.Get', key, None)] = \ b'0\x00' + ''.join(
b'0\x00default=True ' + value '{} default=True {}\n'.format(key, value)
for key, value in props.items()).encode()
# setup template1
props['label'] = 'type=label black'
del props['template']
self.app.expected_calls[
('template1', 'admin.vm.property.GetAll', None, None)] = \
b'0\x00' + ''.join(
'{} default=True {}\n'.format(key, value)
for key, value in props.items()).encode()
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with qubesadmin.tests.tools.StdoutBuffer() as stdout:
qubesadmin.tools.qvm_ls.main([], app=self.app) qubesadmin.tools.qvm_ls.main([], app=self.app)
@ -337,22 +329,24 @@ class TC_90_List_with_qubesd_calls(qubesadmin.tests.QubesTestCase):
('sys-net', 'admin.vm.CurrentState', None, None)] = \ ('sys-net', 'admin.vm.CurrentState', None, None)] = \
b'0\x00power_state=Running' b'0\x00power_state=Running'
props = { props = {
'label': b'type=label green', 'label': 'type=label green',
'template': b'type=vm template1', 'template': 'type=vm template1',
'netvm': b'type=vm sys-net', 'netvm': 'type=vm sys-net',
# 'virt_mode': b'type=str pv', # 'virt_mode': b'type=str pv',
} }
for key, value in props.items(): self.app.expected_calls[
self.app.expected_calls[ ('vm1', 'admin.vm.property.GetAll', None, None)] = \
('vm1', 'admin.vm.property.Get', key, None)] = \ b'0\x00' + ''.join(
b'0\x00default=True ' + value '{} default=True {}\n'.format(key, value)
for key, value in props.items()).encode()
# setup sys-net # setup sys-net
props['label'] = b'type=label red' props['label'] = 'type=label red'
for key, value in props.items(): self.app.expected_calls[
self.app.expected_calls[ ('sys-net', 'admin.vm.property.GetAll', None, None)] = \
('sys-net', 'admin.vm.property.Get', key, None)] = \ b'0\x00' + ''.join(
b'0\x00default=True ' + value '{} default=True {}\n'.format(key, value)
for key, value in props.items()).encode()
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with qubesadmin.tests.tools.StdoutBuffer() as stdout:
qubesadmin.tools.qvm_ls.main(['vm1', 'sys-net'], app=self.app) qubesadmin.tools.qvm_ls.main(['vm1', 'sys-net'], app=self.app)

View File

@ -33,11 +33,9 @@ class TC_00_qvm_prefs(qubesadmin.tests.QubesTestCase):
('dom0', 'admin.vm.property.List', None, None)] = \ ('dom0', 'admin.vm.property.List', None, None)] = \
b'0\x00prop1\nprop2\n' b'0\x00prop1\nprop2\n'
self.app.expected_calls[ self.app.expected_calls[
('dom0', 'admin.vm.property.Get', 'prop1', None)] = \ ('dom0', 'admin.vm.property.GetAll', None, None)] = \
b'0\x00default=True type=str value1' b'0\x00prop1 default=True type=str value1\n' \
self.app.expected_calls[ b'prop2 default=False type=str value2\n'
('dom0', 'admin.vm.property.Get', 'prop2', None)] = \
b'0\x00default=False type=str value2'
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with qubesadmin.tests.tools.StdoutBuffer() as stdout:
self.assertEqual(0, qubesadmin.tools.qvm_prefs.main([ self.assertEqual(0, qubesadmin.tools.qvm_prefs.main([
'dom0'], app=self.app)) 'dom0'], app=self.app))
@ -46,7 +44,7 @@ class TC_00_qvm_prefs(qubesadmin.tests.QubesTestCase):
'prop2 - value2\n') 'prop2 - value2\n')
with qubesadmin.tests.tools.StdoutBuffer() as stdout: with qubesadmin.tests.tools.StdoutBuffer() as stdout:
self.assertEqual(0, qubesadmin.tools.qvm_prefs.main([ self.assertEqual(0, qubesadmin.tools.qvm_prefs.main([
'dom0','--hide-default'], app=self.app)) 'dom0', '--hide-default'], app=self.app))
self.assertEqual(stdout.getvalue(), self.assertEqual(stdout.getvalue(),
'prop2 - value2\n') 'prop2 - value2\n')
self.assertAllCalled() self.assertAllCalled()

View File

@ -664,6 +664,10 @@ def main(args=None, app=None):
parser.print_error(str(e)) parser.print_error(str(e))
return 1 return 1
# fetch all the properties with one Admin API call, instead of issuing
# one call per property
args.app.cache_enabled = True
if args.raw_list: if args.raw_list:
args.raw_data = True args.raw_data = True
args.fields = 'name' args.fields = 'name'

View File

@ -93,6 +93,9 @@ def process_actions(parser, args, target):
return 0 return 0
if args.property is None: if args.property is None:
# fetch all the properties with one Admin API call, instead of issuing
# one call per property
args.app.cache_enabled = True
properties = target.property_list() properties = target.property_list()
width = max(len(prop) for prop in properties) width = max(len(prop) for prop in properties)