Always use QubesVM objects, instead of AppVM/TemplateVM etc

Very few calls at client side really needs VM class name. So, even in
non-blind mode use just QubesVM class, to avoid strange cases depending
on blind mode being enabled or not. Then, have VM class name in 'klass'
property. If known at object creation time, cache it, otherwise query
qubesd at first access.
This commit is contained in:
Marek Marczykowski-Górecki 2017-10-02 20:43:01 +02:00
parent ab9a57a544
commit edcaed537a
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
10 changed files with 57 additions and 38 deletions

View File

@ -86,12 +86,14 @@ class VMCollection(object):
if not self.app.blind_mode and item not in self: if not self.app.blind_mode and item not in self:
raise KeyError(item) raise KeyError(item)
if item not in self._vm_objects: if item not in self._vm_objects:
if self.app.blind_mode: cls = qubesadmin.vm.QubesVM
cls = qubesadmin.vm.QubesVM # provide class name to constructor, if already cached (which can be
else: # done by 'item not in self' check above, unless blind_mode is
cls = qubesadmin.utils.get_entry_point_one(VM_ENTRY_POINT, # enabled
self._vm_list[item]['class']) klass = None
self._vm_objects[item] = cls(self.app, item) 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)
return self._vm_objects[item] return self._vm_objects[item]
def __contains__(self, item): def __contains__(self, item):
@ -312,7 +314,7 @@ class QubesBase(qubesadmin.base.PropertyHolder):
src_vm = self.domains[src_vm] src_vm = self.domains[src_vm]
if new_cls is None: if new_cls is None:
new_cls = src_vm.__class__.__name__ new_cls = src_vm.klass
template = getattr(src_vm, 'template', None) template = getattr(src_vm, 'template', None)
if template is not None: if template is not None:

View File

@ -1456,7 +1456,7 @@ class BackupRestore(object):
except KeyError: except KeyError:
host_template = None host_template = None
present_on_host = (host_template and present_on_host = (host_template and
isinstance(host_template, qubesadmin.vm.TemplateVM)) host_template.klass == 'TemplateVM')
present_in_backup = (template_name in restore_info.keys() and present_in_backup = (template_name in restore_info.keys() and
restore_info[template_name].good_to_go and restore_info[template_name].good_to_go and
restore_info[template_name].vm.klass == restore_info[template_name].vm.klass ==

View File

@ -29,6 +29,7 @@ import qubesadmin.app
class TestVM(object): class TestVM(object):
def __init__(self, name, **kwargs): def __init__(self, name, **kwargs):
self.name = name self.name = name
self.klass = 'TestVM'
for key, value in kwargs.items(): for key, value in kwargs.items():
setattr(self, key, value) setattr(self, key, value)

View File

@ -94,7 +94,7 @@ class TC_00_VMCollection(qubesadmin.tests.QubesTestCase):
b'test-vm2 class=AppVM state=Running\n' b'test-vm2 class=AppVM state=Running\n'
values = self.app.domains.values() values = self.app.domains.values()
for obj in values: for obj in values:
self.assertIsInstance(obj, qubesadmin.vm.AppVM) self.assertIsInstance(obj, qubesadmin.vm.QubesVM)
self.assertEqual(set([vm.name for vm in values]), self.assertEqual(set([vm.name for vm in values]),
set(['test-vm', 'test-vm2'])) set(['test-vm', 'test-vm2']))
self.assertAllCalled() self.assertAllCalled()
@ -122,6 +122,17 @@ class TC_00_VMCollection(qubesadmin.tests.QubesTestCase):
self.assertNotIn('test-non-existent', self.app.domains) self.assertNotIn('test-non-existent', self.app.domains)
self.assertAllCalled() self.assertAllCalled()
def test_009_getitem_cache_class(self):
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')
except KeyError:
self.fail('VM not found in collection')
self.assertAllCalled()
class TC_10_QubesBase(qubesadmin.tests.QubesTestCase): class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
def test_010_new_simple(self): def test_010_new_simple(self):
@ -131,7 +142,7 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
b'0\x00new-vm class=AppVM state=Running\n' b'0\x00new-vm class=AppVM state=Running\n'
vm = self.app.add_new_vm('AppVM', 'new-vm', 'red') vm = self.app.add_new_vm('AppVM', 'new-vm', 'red')
self.assertEqual(vm.name, 'new-vm') self.assertEqual(vm.name, 'new-vm')
self.assertEqual(vm.__class__.__name__, 'AppVM') self.assertEqual(vm.klass, 'AppVM')
self.assertAllCalled() self.assertAllCalled()
def test_011_new_template(self): def test_011_new_template(self):
@ -141,7 +152,7 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
b'0\x00new-template class=TemplateVM state=Running\n' b'0\x00new-template class=TemplateVM state=Running\n'
vm = self.app.add_new_vm('TemplateVM', 'new-template', 'red') vm = self.app.add_new_vm('TemplateVM', 'new-template', 'red')
self.assertEqual(vm.name, 'new-template') self.assertEqual(vm.name, 'new-template')
self.assertEqual(vm.__class__.__name__, 'TemplateVM') self.assertEqual(vm.klass, 'TemplateVM')
self.assertAllCalled() self.assertAllCalled()
def test_012_new_template_based(self): def test_012_new_template_based(self):
@ -151,7 +162,7 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
b'0\x00new-vm class=AppVM state=Running\n' b'0\x00new-vm class=AppVM state=Running\n'
vm = self.app.add_new_vm('AppVM', 'new-vm', 'red', 'some-template') vm = self.app.add_new_vm('AppVM', 'new-vm', 'red', 'some-template')
self.assertEqual(vm.name, 'new-vm') self.assertEqual(vm.name, 'new-vm')
self.assertEqual(vm.__class__.__name__, 'AppVM') self.assertEqual(vm.klass, 'AppVM')
self.assertAllCalled() self.assertAllCalled()
def test_013_new_objects_params(self): def test_013_new_objects_params(self):
@ -165,7 +176,7 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
vm = self.app.add_new_vm(self.app.get_vm_class('AppVM'), 'new-vm', vm = self.app.add_new_vm(self.app.get_vm_class('AppVM'), 'new-vm',
self.app.get_label('red'), self.app.domains['some-template']) self.app.get_label('red'), self.app.domains['some-template'])
self.assertEqual(vm.name, 'new-vm') self.assertEqual(vm.name, 'new-vm')
self.assertEqual(vm.__class__.__name__, 'AppVM') self.assertEqual(vm.klass, 'AppVM')
self.assertAllCalled() self.assertAllCalled()
def test_014_new_pool(self): def test_014_new_pool(self):
@ -175,7 +186,7 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
b'0\x00new-vm class=AppVM state=Running\n' b'0\x00new-vm class=AppVM state=Running\n'
vm = self.app.add_new_vm('AppVM', 'new-vm', 'red', pool='some-pool') vm = self.app.add_new_vm('AppVM', 'new-vm', 'red', pool='some-pool')
self.assertEqual(vm.name, 'new-vm') self.assertEqual(vm.name, 'new-vm')
self.assertEqual(vm.__class__.__name__, 'AppVM') self.assertEqual(vm.klass, 'AppVM')
self.assertAllCalled() self.assertAllCalled()
def test_015_new_pools(self): def test_015_new_pools(self):
@ -187,7 +198,7 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
vm = self.app.add_new_vm('AppVM', 'new-vm', 'red', vm = self.app.add_new_vm('AppVM', 'new-vm', 'red',
pools={'private': 'some-pool', 'volatile': 'other-pool'}) pools={'private': 'some-pool', 'volatile': 'other-pool'})
self.assertEqual(vm.name, 'new-vm') self.assertEqual(vm.name, 'new-vm')
self.assertEqual(vm.__class__.__name__, 'AppVM') self.assertEqual(vm.klass, 'AppVM')
self.assertAllCalled() self.assertAllCalled()
def test_016_new_template_based_default(self): def test_016_new_template_based_default(self):
@ -198,7 +209,7 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
vm = self.app.add_new_vm('AppVM', 'new-vm', 'red', vm = self.app.add_new_vm('AppVM', 'new-vm', 'red',
template=qubesadmin.DEFAULT) template=qubesadmin.DEFAULT)
self.assertEqual(vm.name, 'new-vm') self.assertEqual(vm.name, 'new-vm')
self.assertEqual(vm.__class__.__name__, 'AppVM') self.assertEqual(vm.klass, 'AppVM')
self.assertAllCalled() self.assertAllCalled()
def test_020_get_label(self): def test_020_get_label(self):
@ -422,7 +433,7 @@ class TC_10_QubesBase(qubesadmin.tests.QubesTestCase):
new_vm = self.app.clone_vm('test-vm', 'new-name', new_vm = self.app.clone_vm('test-vm', 'new-name',
new_cls='StandaloneVM') new_cls='StandaloneVM')
self.assertEqual(new_vm.name, 'new-name') self.assertEqual(new_vm.name, 'new-name')
self.assertEqual(new_vm.__class__.__name__, 'StandaloneVM') self.assertEqual(new_vm.klass, 'StandaloneVM')
self.assertAllCalled() self.assertAllCalled()
def test_035_clone_fail(self): def test_035_clone_fail(self):

View File

@ -156,7 +156,7 @@ class VmNameAction(QubesAction):
namespace.domains = [ namespace.domains = [
vm vm
for vm in app.domains for vm in app.domains
if not isinstance(vm, qubesadmin.vm.AdminVM) and if not vm.klass == 'AdminVM' and
vm.name not in namespace.exclude vm.name not in namespace.exclude
] ]
else: else:

View File

@ -64,8 +64,7 @@ def main(args=None, app=None):
print_msg(paused, "is paused", "are paused") print_msg(paused, "is paused", "are paused")
return 0 if paused else 1 return 0 if paused else 1
elif args.template: elif args.template:
template = [vm for vm in domains if isinstance(vm, template = [vm for vm in domains if vm.klass == 'TemplateVM']
qubesadmin.vm.TemplateVM)]
if args.verbose: if args.verbose:
print_msg(template, "is a template", "are templates") print_msg(template, "is a template", "are templates")
return 0 if template else 1 return 0 if template else 1

View File

@ -214,19 +214,16 @@ class FlagsColumn(Column):
When it is HVM (optimised VM), the letter is capital. When it is HVM (optimised VM), the letter is capital.
''' '''
if isinstance(vm, qubesadmin.vm.AdminVM): type_codes = {
return '0' 'AdminVM': '0',
'TemplateVM': 't',
ret = None 'AppVM': 'a',
# TODO right order, depending on inheritance 'StandaloneVM': 's',
if isinstance(vm, qubesadmin.vm.TemplateVM): 'DispVM': 'd',
ret = 't' }
if isinstance(vm, qubesadmin.vm.AppVM): ret = type_codes.get(vm.klass, None)
ret = 'a' if ret == '0':
if isinstance(vm, qubesadmin.vm.StandaloneVM): return ret
ret = 's'
if isinstance(vm, qubesadmin.vm.DispVM):
ret = 'd'
if ret is not None: if ret is not None:
if getattr(vm, 'virt_mode', 'pv') == 'hvm': if getattr(vm, 'virt_mode', 'pv') == 'hvm':
@ -338,7 +335,7 @@ Column('STATE',
doc='Current power state.') doc='Current power state.')
Column('CLASS', Column('CLASS',
attr=(lambda vm: type(vm).__name__), attr=(lambda vm: vm.klass),
doc='Class of the qube.') doc='Class of the qube.')

View File

@ -109,7 +109,7 @@ def get_drive_assignment(app, drive_str):
'Existing block device identifier needed when running from ' 'Existing block device identifier needed when running from '
'outside of dom0 (see qvm-block)') 'outside of dom0 (see qvm-block)')
try: try:
if isinstance(backend_domain, qubesadmin.vm.AdminVM): if backend_domain.klass == 'AdminVM':
loop_name = subprocess.check_output( loop_name = subprocess.check_output(
['sudo', 'losetup', '-f', '--show', ident]) ['sudo', 'losetup', '-f', '--show', ident])
else: else:

View File

@ -296,7 +296,7 @@ class GUILauncher(object):
'''Send monitor layout to all (running) VMs''' '''Send monitor layout to all (running) VMs'''
monitor_layout = get_monitor_layout() monitor_layout = get_monitor_layout()
for vm in self.app.domains: for vm in self.app.domains:
if isinstance(vm, qubesadmin.vm.AdminVM): if vm.klass == 'AdminVM':
continue continue
if vm.is_running(): if vm.is_running():
if not vm.features.check_with_template('gui', True): if not vm.features.check_with_template('gui', True):
@ -325,7 +325,7 @@ class GUILauncher(object):
monitor_layout = get_monitor_layout() monitor_layout = get_monitor_layout()
self.app.domains.clear_cache() self.app.domains.clear_cache()
for vm in self.app.domains: for vm in self.app.domains:
if isinstance(vm, qubesadmin.vm.AdminVM): if vm.klass == 'AdminVM':
continue continue
if not vm.features.check_with_template('gui', True): if not vm.features.check_with_template('gui', True):
continue continue

View File

@ -46,9 +46,10 @@ class QubesVM(qubesadmin.base.PropertyHolder):
firewall = None firewall = None
def __init__(self, app, name): def __init__(self, app, name, klass=None):
super(QubesVM, self).__init__(app, 'admin.vm.property.', name) super(QubesVM, self).__init__(app, 'admin.vm.property.', name)
self._volumes = None self._volumes = None
self._klass = klass
self.log = logging.getLogger(name) self.log = logging.getLogger(name)
self.tags = qubesadmin.tags.Tags(self) self.tags = qubesadmin.tags.Tags(self)
self.features = qubesadmin.features.Features(self) self.features = qubesadmin.features.Features(self)
@ -314,6 +315,14 @@ class QubesVM(qubesadmin.base.PropertyHolder):
e.cmd = command e.cmd = command
raise e raise e
@property
def klass(self):
''' Qube class '''
# use cached value if available
if self._klass is None:
# pylint: disable=no-member
self._klass = super(QubesVM, self).klass
return self._klass
# pylint: disable=abstract-method # pylint: disable=abstract-method
class AdminVM(QubesVM): class AdminVM(QubesVM):