From 2005207462f57b28d6959dcfd18f36209183821a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Tue, 19 Nov 2013 18:42:59 +0100 Subject: [PATCH] Template support for HVM (#719) Any HVM (which isn't already template-based) can be a template for another HVM. For now do not allow simultaneous run of template and its VM (this assumption simplify the implementation, as no root-cow.img is needed). --- core-modules/000QubesVm.py | 13 ++++++++ core-modules/01QubesHVm.py | 66 +++++++++++++++++++++++++++++++++----- core/qubes.py | 6 +++- qvm-tools/qvm-create | 4 --- 4 files changed, 76 insertions(+), 13 deletions(-) diff --git a/core-modules/000QubesVm.py b/core-modules/000QubesVm.py index 4c8fdf8f..0e6fa908 100644 --- a/core-modules/000QubesVm.py +++ b/core-modules/000QubesVm.py @@ -444,6 +444,19 @@ class QubesVm(object): for hook in self.hooks_post_rename: hook(self, old_name) + @property + def template(self): + return self._template + + @template.setter + def template(self, value): + # FIXME: check if the value is instance of QubesTemplateVM, not the VM + # type. The problem is while this file is loaded, QubesTemplateVM is + # not defined yet. + if value and (not value.is_template() or value.type != "TemplateVM"): + raise QubesException("Only PV template can be a template for the PV VM") + self._template = value + def is_template(self): return False diff --git a/core-modules/01QubesHVm.py b/core-modules/01QubesHVm.py index 8ac2c0e4..0e657834 100644 --- a/core-modules/01QubesHVm.py +++ b/core-modules/01QubesHVm.py @@ -29,7 +29,7 @@ import sys import re from qubes.qubes import QubesVm,register_qubes_vm_class,xs,xc,dry_run -from qubes.qubes import QubesException +from qubes.qubes import QubesException,QubesVmCollection from qubes.qubes import system_path,defaults system_path["config_template_hvm"] = '/usr/share/qubes/vm-template-hvm.conf' @@ -55,7 +55,6 @@ class QubesHVm(QubesVm): attrs.pop('uses_default_kernel') attrs.pop('uses_default_kernelopts') attrs['dir_path']['eval'] = 'value if value is not None else os.path.join(system_path["qubes_appvms_dir"], self.name)' - attrs['volatile_img']['eval'] = 'None' attrs['config_file_template']['eval'] = 'system_path["config_template_hvm"]' attrs['drive'] = { 'save': 'str(self.drive)' } attrs['maxmem'].pop('save') @@ -65,8 +64,6 @@ class QubesHVm(QubesVm): attrs['_start_guid_first']['eval'] = 'True' attrs['services']['default'] = "{'meminfo-writer': False}" - # only standalone HVM supported for now - attrs['template']['eval'] = 'None' attrs['memory']['default'] = defaults["hvm_memory"] return attrs @@ -90,6 +87,9 @@ class QubesHVm(QubesVm): if self.guiagent_installed: self._start_guid_first = False + # The QubesHVM can be a template itself, so collect appvms based on it + self.appvms = QubesVmCollection() + @property def type(self): return "HVM" @@ -97,6 +97,20 @@ class QubesHVm(QubesVm): def is_appvm(self): return True + def is_template(self): + # Any non-template based HVM can be a template itself + return self.template is None + + @property + def template(self): + return self._template + + @template.setter + def template(self, value): + if value and (not value.is_template() or value.type != "HVM"): + raise QubesException("Only HVM can be a template for the HVM") + self._template = value + def get_clone_attrs(self): attrs = super(QubesHVm, self).get_clone_attrs() attrs.remove('kernel') @@ -123,11 +137,18 @@ class QubesHVm(QubesVm): self.create_config_file() # create empty disk - f_root = open(self.root_img, "w") - f_root.truncate(defaults["hvm_disk_size"]) - f_root.close() + if self.template is None: + if verbose: + print >> sys.stderr, "--> Creating root image: {0}".\ + format(self.root_img) + f_root = open(self.root_img, "w") + f_root.truncate(defaults["hvm_disk_size"]) + f_root.close() # create empty private.img + if verbose: + print >> sys.stderr, "--> Creating private image: {0}".\ + format(self.private_img) f_private = open(self.private_img, "w") f_private.truncate(defaults["hvm_private_img_size"]) f_private.close() @@ -155,6 +176,14 @@ class QubesHVm(QubesVm): f_private.truncate (size) f_private.close () + def get_rootdev(self, source_template=None): + if self.template: + return "'script:snapshot:{template_root}:{volatile},xvda,w',".format( + template_root=self.template.root_img, + volatile=self.volatile_img) + else: + return "'script:file:{root_img},xvda,w',".format(root_img=self.root_img) + def get_config_params(self, source_template=None): params = super(QubesHVm, self).get_config_params(source_template=source_template) @@ -229,7 +258,23 @@ class QubesHVm(QubesVm): return True def reset_volatile_storage(self, **kwargs): - pass + assert not self.is_running(), "Attempt to clean volatile image of running VM!" + + source_template = kwargs.get("source_template", self.template) + + if source_template is None: + # Nothing to do on non-template based VM + return + + if os.path.exists (self.volatile_img): + os.remove (self.volatile_img) + + f_volatile = open (self.volatile_img, "w") + f_root = open (self.template.root_img, "r") + f_root.seek(0, os.SEEK_END) + f_volatile.truncate (f_root.tell()) # make empty sparse file of the same size as root.img + f_volatile.close () + f_root.close() @property def vif(self): @@ -259,6 +304,11 @@ class QubesHVm(QubesVm): return -1 def start(self, *args, **kwargs): + for vm in self.appvms.values(): + if vm.is_running(): + raise QubesException("Cannot start HVM template while VMs based on it are running") + if self.template and self.template.is_running(): + raise QubesException("Cannot start the HVM while its template is running") try: super(QubesHVm, self).start(*args, **kwargs) except QubesException as e: diff --git a/core/qubes.py b/core/qubes.py index 2fcbb8d7..dea1fb35 100755 --- a/core/qubes.py +++ b/core/qubes.py @@ -625,7 +625,11 @@ class QubesVmCollection(dict): for (vm_class_name, vm_class) in sorted(QubesVmClasses.items(), key=lambda _x: _x[1].load_order): - for element in tree.findall(vm_class_name): + vms_of_class = tree.findall(vm_class_name) + # first non-template based, then template based + sorted_vms_of_class = sorted(vms_of_class, key= \ + lambda x: str(x.get('template_qid')).lower() != "none") + for element in sorted_vms_of_class: try: vm = vm_class(xml_element=element, collection=self) self[vm.qid] = vm diff --git a/qvm-tools/qvm-create b/qvm-tools/qvm-create index a4ff0891..3d7e9176 100755 --- a/qvm-tools/qvm-create +++ b/qvm-tools/qvm-create @@ -87,10 +87,6 @@ def main(): exit (1) label = QubesVmLabels[options.label] - if options.hvm: - # Only standalone HVMs are supported for now - options.standalone = True - if not options.standalone and options.root is not None: print >> sys.stderr, "root.img can be specified only for standalone VMs" exit (1)