From 80d664441dcc9d7a745b204837e7a676a4a57c20 Mon Sep 17 00:00:00 2001 From: Wojtek Porczyk Date: Thu, 17 Sep 2015 12:08:03 +0200 Subject: [PATCH] core3: fixes from Marek This is adapted from commit 90a50dca406e3d40c88ea338566e0460589df7a3. --- qubes/__init__.py | 27 ++++++++---- qubes/events.py | 3 +- qubes/qdb.py | 5 +++ qubes/vm/__init__.py | 4 +- qubes/vm/appvm.py | 9 ++-- qubes/vm/netvm.py | 31 ++++++++++++-- qubes/vm/qubesvm.py | 97 ++++++++++++++++++++++++++++-------------- qubes/vm/templatevm.py | 7 +++ 8 files changed, 134 insertions(+), 49 deletions(-) create mode 100644 qubes/qdb.py diff --git a/qubes/__init__.py b/qubes/__init__.py index 4c4c9923..d883e976 100644 --- a/qubes/__init__.py +++ b/qubes/__init__.py @@ -640,7 +640,7 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name if self._setter is not None: value = self._setter(instance, self, value) - if self._type is not None: + if self._type is not None: # XXX what about QubesVM and other types? value = self._type(value) if has_oldvalue: @@ -705,7 +705,7 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name # class DontSave(Exception): - '''This exception may be raised from saver to sing that property should + '''This exception may be raised from saver to sign that property should not be saved. ''' pass @@ -1023,23 +1023,28 @@ class VMProperty(property): raise TypeError( "'vmclass' should specify a subclass of qubes.vm.BaseVM") - super(VMProperty, self).__init__(name, **kwargs) + super(VMProperty, self).__init__(name, + saver=(lambda self, prop, value: value.name if value else 'None'), + **kwargs) self.vmclass = vmclass self.allow_none = allow_none + def __set__(self, instance, value): if value is None: if self.allow_none: - super(VMProperty, self).__set__(self, instance, value) + super(VMProperty, self).__set__(instance, value) return else: raise ValueError( 'Property {!r} does not allow setting to {!r}'.format( self.__name__, value)) + app = instance if isinstance(instance, Qubes) else instance.app + # XXX this may throw LookupError; that's good until introduction # of QubesNoSuchVMException or whatever - vm = instance.app.domains[value] + vm = app.domains[value] if not isinstance(vm, self.vmclass): raise TypeError('wrong VM class: domains[{!r}] if of type {!s} ' @@ -1047,7 +1052,7 @@ class VMProperty(property): vm.__class__.__name__, self.vmclass.__name__)) - super(VMProperty, self).__set__(self, instance, vm) + super(VMProperty, self).__set__(instance, vm) import qubes.vm.qubesvm @@ -1100,11 +1105,11 @@ class Qubes(PropertyHolder): ''' default_netvm = VMProperty('default_netvm', load_stage=3, - default=None, + default=None, allow_none=True, doc='''Default NetVM for AppVMs. Initial state is `None`, which means that AppVMs are not connected to the Internet.''') default_fw_netvm = VMProperty('default_fw_netvm', load_stage=3, - default=None, + default=None, allow_none=True, doc='''Default NetVM for ProxyVMs. Initial state is `None`, which means that ProxyVMs (including FirewallVM) are not connected to the Internet.''') @@ -1112,9 +1117,11 @@ class Qubes(PropertyHolder): vmclass=qubes.vm.templatevm.TemplateVM, doc='Default template for new AppVMs') updatevm = VMProperty('updatevm', load_stage=3, + allow_none=True, doc='''Which VM to use as `yum` proxy for updating AdminVM and TemplateVMs''') clockvm = VMProperty('clockvm', load_stage=3, + allow_none=True, doc='Which VM to use as NTP proxy for updating AdminVM') default_kernel = property('default_kernel', load_stage=3, doc='Which kernel to use when not overriden in VM') @@ -1209,7 +1216,7 @@ class Qubes(PropertyHolder): # Disable ntpd in ClockVM - to not conflict with ntpdate (both are # using 123/udp port) - if hasattr(self, 'clockvm'): + if hasattr(self, 'clockvm') and self.clockvm is not None: if 'ntpd' in self.clockvm.services: if self.clockvm.services['ntpd']: self.log.warning("VM set as clockvm ({!r}) has enabled " @@ -1367,6 +1374,8 @@ class Qubes(PropertyHolder): @qubes.events.handler('property-pre-set:clockvm') def on_property_pre_set_clockvm(self, event, name, newvalue, oldvalue=None): # pylint: disable=unused-argument,no-self-use + if newvalue is None: + return if 'ntpd' in newvalue.services: if newvalue.services['ntpd']: raise QubesException('Cannot set {!r} as {!r} property since ' diff --git a/qubes/events.py b/qubes/events.py index 8c17867c..334e2d85 100644 --- a/qubes/events.py +++ b/qubes/events.py @@ -102,7 +102,8 @@ class Emitter(object): def __init__(self, *args, **kwargs): super(Emitter, self).__init__(*args, **kwargs) - self.events_enabled = False + if not hasattr(self, 'events_enabled'): + self.events_enabled = False @classmethod diff --git a/qubes/qdb.py b/qubes/qdb.py new file mode 100644 index 00000000..929ca4d1 --- /dev/null +++ b/qubes/qdb.py @@ -0,0 +1,5 @@ +# This is mock file, not installed. It is needed. because pylint needs to +# import all the modules, and qubes.qbd is one of them. + +def QubesDB(dummy): + return None diff --git a/qubes/vm/__init__.py b/qubes/vm/__init__.py index 25614429..acf1cb49 100644 --- a/qubes/vm/__init__.py +++ b/qubes/vm/__init__.py @@ -353,7 +353,7 @@ class BaseVM(qubes.PropertyHolder): args['name'] = self.name if hasattr(self, 'kernels_dir'): args['kerneldir'] = self.kernels_dir - args['uuidnode'] = '{!r}'.format(self.uuid) \ + args['uuidnode'] = '{!s}'.format(self.uuid) \ if hasattr(self, 'uuid') else '' args['vmdir'] = self.dir_path args['pcidevs'] = ''.join(lxml.etree.tostring(self.lvxml_pci_dev(dev)) @@ -398,6 +398,8 @@ class BaseVM(qubes.PropertyHolder): "Debug mode: adding 'earlyprintk=xen' to kernel opts") args['kernelopts'] += ' earlyprintk=xen' + return args + def create_config_file(self, file_path=None, prepare_dvm=False): '''Create libvirt's XML domain config file diff --git a/qubes/vm/appvm.py b/qubes/vm/appvm.py index 012fd3a5..ab36d6e5 100644 --- a/qubes/vm/appvm.py +++ b/qubes/vm/appvm.py @@ -1,6 +1,7 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 +import qubes.events import qubes.vm.qubesvm class AppVM(qubes.vm.qubesvm.QubesVM): @@ -11,9 +12,11 @@ class AppVM(qubes.vm.qubesvm.QubesVM): ls_width=31, doc='Template, on which this AppVM is based.') - def __init__(self, D): - super(AppVM, self).__init__(D) + def __init__(self, *args, **kwargs): + super(AppVM, self).__init__(*args, **kwargs) + @qubes.events.handler('domain-loaded') + def on_domain_loaded(self, event): # Some additional checks for template based VM assert self.template - self.template.appvms.add(self) + #self.template.appvms.add(self) # XXX diff --git a/qubes/vm/netvm.py b/qubes/vm/netvm.py index fddaac50..90ac57e3 100644 --- a/qubes/vm/netvm.py +++ b/qubes/vm/netvm.py @@ -3,7 +3,32 @@ import qubes.vm.qubesvm -class NetVM(qubes.vm.qubesvm.QubesVM): +class NetVM(qubes.vm.appvm.AppVM): '''Network interface VM''' - def __init__(self, D): - super(NetVM, self).__init__(D) + + netvm = qubes.property('netvm', setter=qubes.property.forbidden) + + def __init__(self, *args, **kwargs): + super(NetVM, self).__init__(*args, **kwargs) + + def get_ip_for_vm(self, vm): + return '10.137.{}.{}'.format(self.qid, vm.qid + 2) + + @property + def gateway(self): + return '10.137.{}.1'.format(self.qid) + + @property + def secondary_dns(self): + return '10.137.{}.254'.format(self.qid) + +# @property +# def netmask(self): +# return '255.255.255.0' +# +# @property +# def provides_network(self): +# return True + + netmask = '255.255.255.0' + provides_network = True diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index ba08580c..f201ada2 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -24,6 +24,8 @@ # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # +from __future__ import absolute_import + import datetime import lxml.etree import os @@ -34,11 +36,13 @@ import subprocess import sys import time import uuid +import warnings + import libvirt import qubes import qubes.config -#import qubes.qdb +import qubes.qdb #import qubes.qmemman #import qubes.qmemman_algo import qubes.storage @@ -90,19 +94,29 @@ def _setter_name(self, prop, value): def _setter_kernel(self, prop, value): # pylint: disable=unused-argument - if not os.path.exists(os.path.join( - qubes.config.system_path['qubes_kernels_base_dir'], value)): + dirname = os.path.join( + qubes.config.system_path['qubes_base_dir'], + qubes.config.system_path['qubes_kernels_base_dir'], + value) + if not os.path.exists(dirname): raise qubes.QubesException('Kernel {!r} not installed'.format(value)) for filename in ('vmlinuz', 'modules.img'): - if not os.path.exists(os.path.join( - qubes.config.system_path['qubes_kernels_base_dir'], - value, filename)): + if not os.path.exists(os.path.join(dirname, filename)): raise qubes.QubesException( 'Kernel {!r} not properly installed: missing {!r} file'.format( value, filename)) return value +def _setter_label(self, prop, value): + if isinstance(value, qubes.Label): + return value + if value.startswith('label-'): + return self.app.labels[int(value.split('-', 1)[1])] + + return self.app.get_label(value) + + def _default_conf_file(self, name=None): return (name or self.name) + '.conf' @@ -115,7 +129,8 @@ class QubesVM(qubes.vm.BaseVM): # label = qubes.property('label', - setter=(lambda self, prop, value: self.app.get_label(value)), + setter=_setter_label, + saver=(lambda self, prop, value: 'label-{}'.format(value.index)), ls_width=14, doc='''Colourful label assigned to VM. This is where the colour of the padlock is set.''') @@ -129,9 +144,9 @@ class QubesVM(qubes.vm.BaseVM): `None`, machine is disconnected. When absent, domain uses default NetVM.''') - provides_network = qubes.property('provides_network', - type=bool, setter=qubes.property.bool, - doc='`True` if it is NetVM or ProxyVM, false otherwise.') +# provides_network = qubes.property('provides_network', +# type=bool, setter=qubes.property.bool, +# doc='`True` if it is NetVM or ProxyVM, false otherwise.') qid = qubes.property('qid', type=int, setter=_setter_qid, @@ -180,8 +195,9 @@ class QubesVM(qubes.vm.BaseVM): doc='''Internal VM (not shown in qubes-manager, don't create appmenus entries.''') - # XXX what is that - vcpus = qubes.property('vcpus', default=None, + # FIXME self.app.host could not exist - only self.app.vm required by API + vcpus = qubes.property('vcpus', + default=(lambda self: self.app.host.no_cpus), ls_width=2, doc='FIXME') @@ -215,7 +231,8 @@ class QubesVM(qubes.vm.BaseVM): # XXX shouldn't this go to standalone VM and TemplateVM, and leave here # only plain property? default_user = qubes.property('default_user', type=str, - default=(lambda self: self.template.default_user), + default=(lambda self: self.template.default_user + if hasattr(self, 'template') else 'user'), ls_width=12, doc='FIXME') @@ -353,7 +370,7 @@ class QubesVM(qubes.vm.BaseVM): If :py:attr:`self.kernel` is :py:obj:`None`, the this points inside :py:attr:`self.dir_path` ''' - return os.path.join( + return os.path.join(qubes.config.system_path['qubes_base_dir'], qubes.config.system_path['qubes_kernels_base_dir'], self.kernel) \ if self.kernel is not None \ else os.path.join(self.dir_path, @@ -379,17 +396,25 @@ class QubesVM(qubes.vm.BaseVM): # XXX I don't know what to do with these; probably should be isinstance(...) -# def is_template(self): -# return False -# -# def is_appvm(self): -# return False -# -# def is_proxyvm(self): -# return False -# -# def is_disposablevm(self): -# return False + def is_template(self): + warnings.warn('vm.is_template() is deprecated, use isinstance()', + DeprecationWarning) + return isinstance(self, qubes.vm.templatevm.TemplateVM) + + def is_appvm(self): + warnings.warn('vm.is_appvm() is deprecated, use isinstance()', + DeprecationWarning) + return isinstance(self, qubes.vm.appvm.AppVM) + + def is_proxyvm(self): + warnings.warn('vm.is_proxyvm() is deprecated, use isinstance()', + DeprecationWarning) + return isinstance(self, qubes.vm.proxyvm.ProxyVM) + + def is_disposablevm(self): + warnings.warn('vm.is_disposable() is deprecated, use isinstance()', + DeprecationWarning) + return isinstance(self, qubes.vm.dispvm.DispVM) # network-related @@ -399,7 +424,7 @@ class QubesVM(qubes.vm.BaseVM): def ip(self): '''IP address of this domain.''' if self.netvm is not None: - return self.netvm.get_ip_for_vm(self.qid) + return self.netvm.get_ip_for_vm(self) else: return None @@ -441,6 +466,13 @@ class QubesVM(qubes.vm.BaseVM): return None return "vif{0}.+".format(self.xid) + @property + def provides_network(self): + ''':py:obj:`True` if it is :py:class:`qubes.vm.netvm.NetVM` or + :py:class:`qubes.vm.proxyvm.ProxyVM`, :py:obj:`False` otherwise''' + return isinstance(self, + (qubes.vm.netvm.NetVM, qubes.vm.proxyvm.ProxyVM)) + # # constructor # @@ -472,8 +504,8 @@ class QubesVM(qubes.vm.BaseVM): self.maxmem = self.memory * 10 # By default allow use all VCPUs - if not hasattr(self, 'vcpus') and not self.app.vmm.offline_mode: - self.vcpus = self.app.host.no_cpus +# if not hasattr(self, 'vcpus') and not self.app.vmm.offline_mode: +# self.vcpus = self.app.host.no_cpus if len(self.devices['pci']) > 0: # Force meminfo-writer disabled when VM have PCI devices @@ -486,10 +518,11 @@ class QubesVM(qubes.vm.BaseVM): # Initialize VM image storage class self.storage = qubes.storage.get_storage(self) - if self.kernels_dir is not None: # it is None for AdminVM - self.storage.modules_img = os.path.join(self.kernels_dir, - 'modules.img') - self.storage.modules_img_rw = self.kernel is None + # XXX should be moved to defaults in storage class +# if self.kernels_dir is not None: # it is None for AdminVM +# self.storage.modules_img = os.path.join(self.kernels_dir, +# 'modules.img') +# self.storage.modules_img_rw = self.kernel is None # fire hooks self.fire_event('domain-init') diff --git a/qubes/vm/templatevm.py b/qubes/vm/templatevm.py index 8f4bdce4..be8444dd 100644 --- a/qubes/vm/templatevm.py +++ b/qubes/vm/templatevm.py @@ -1,6 +1,8 @@ #!/usr/bin/python2 -O # vim: fileencoding=utf-8 +import os.path + import qubes import qubes.vm.qubesvm @@ -38,3 +40,8 @@ class TemplateVM(qubes.vm.qubesvm.QubesVM): self.log.info( 'Commiting template update; COW: {}'.format(self.rootcow_img)) self.storage.commit_template_changes() + + + @property + def rootcow_img(self): + return os.path.join(self.dir_path, qubes.config.vm_files['rootcow_img'])