dom0/core: major rework of QubesVmCollection class

No longer individual QubesVm attributes hardcoded in QubesVmCollection
(qubes.xml load). Now it is integrated to QubesVm attributes machinery. Also
QubesVmCollection have no longer hardcoded supported VM types - this will
greatly improve code extension possibilities.

This commit doesn't cover QubesVmCollection.add_*vm methods (which are broken
for now because of missing "collection" argument to QubesVm constructor). This
will be done in next commit.
This commit is contained in:
Marek Marczykowski 2013-01-17 01:18:42 +01:00
parent bc39e05a6a
commit 84e85c6a9a

View File

@ -204,6 +204,11 @@ default_appvm_label = QubesVmLabels["red"]
default_template_label = QubesVmLabels["gray"]
default_servicevm_label = QubesVmLabels["red"]
QubesVmClasses = {}
def register_qubes_vm_class(class_name, vm_class):
global QubesVmClasses
QubesVmClasses[class_name] = vm_class
class QubesVm(object):
"""
A representation of one Qubes VM
@ -212,6 +217,9 @@ class QubesVm(object):
Note that qid is not the same as Xen's domid!
"""
# In which order load this VM type from qubes.xml
load_order = 100
def _get_attrs_config(self):
""" Object attributes for serialization/deserialization
inner dict keys:
@ -242,7 +250,8 @@ class QubesVm(object):
### order >= 20: have template set
"uses_default_netvm": { "default": True, 'order': 20 },
"netvm": { "default": None, "attr": "_netvm", 'order': 20 },
"label": { "attr": "_label", "default": QubesVmLabels["red"], 'order': 20 },
"label": { "attr": "_label", "default": QubesVmLabels["red"], 'order': 20,
'xml_deserialize': lambda _x: QubesVmLabels[_x] },
"memory": { "default": default_memory, 'order': 20, "eval": "int(value)" },
"maxmem": { "default": None, 'order': 25, "eval": "int(value) if value else None" },
"pcidevs": { "default": '[]', 'order': 25, "eval": \
@ -250,9 +259,10 @@ class QubesVm(object):
# Internal VM (not shown in qubes-manager, doesn't create appmenus entries
"internal": { "default": False },
"vcpus": { "default": None },
"kernel": { "default": None, 'order': 30 },
"uses_default_kernel": { "default": True, 'order': 30 },
"uses_default_kernelopts": { "default": True, 'order': 30 },
"kernel": { "default": None, 'order': 31,
'eval': 'collection.get_default_kernel() if self.uses_default_kernel else value' },
"kernelopts": { "default": "", 'order': 31, "eval": \
'value if not self.uses_default_kernelopts else default_kernelopts_pcidevs if len(self.pcidevs) > 0 else default_kernelopts' },
"mac": { "attr": "_mac", "default": None },
@ -299,8 +309,35 @@ class QubesVm(object):
return attrs
def __basic_parse_xml_attr(self, value):
if value is None:
return None
if value.lower() == "none":
return None
if value.lower() == "true":
return True
if value.lower() == "false":
return False
if value.isdigit():
return int(value)
return value
def __init__(self, **kwargs):
collection = None
if 'collection' in kwargs:
collection = kwargs['collection']
else:
raise ValueError("No collection given to QubesVM constructor")
# Special case for template b/c it is given in "template_qid" property
if "xml_element" in kwargs and kwargs["xml_element"].get("template_qid"):
template_qid = kwargs["xml_element"].get("template_qid")
if template_qid.lower() != "none":
if int(template_qid) in collection:
kwargs["template"] = collection[int(template_qid)]
else:
raise ValueError("Unknown template with QID %s" % template_qid)
attrs = self._get_attrs_config()
for attr_name in sorted(attrs, key=lambda _x: attrs[_x]['order'] if 'order' in attrs[_x] else 1000):
attr_config = attrs[attr_name]
@ -308,11 +345,16 @@ class QubesVm(object):
if 'attr' in attr_config:
attr = attr_config['attr']
value = None
if attr_name not in kwargs:
if attr_name in kwargs:
value = kwargs[attr_name]
elif 'xml_element' in kwargs and kwargs['xml_element'].get(attr_name) is not None:
if 'xml_deserialize' in attr_config and callable(attr_config['xml_deserialize']):
value = attr_config['xml_deserialize'](kwargs['xml_element'].get(attr_name))
else:
value = self.__basic_parse_xml_attr(kwargs['xml_element'].get(attr_name))
else:
if 'default' in attr_config:
value = attr_config['default']
else:
value = kwargs[attr_name]
if 'eval' in attr_config:
setattr(self, attr, eval(attr_config['eval']))
else:
@ -1627,6 +1669,9 @@ class QubesTemplateVm(QubesVm):
A class that represents an TemplateVM. A child of QubesVm.
"""
# In which order load this VM type from qubes.xml
load_order = 50
def _get_attrs_config(self):
attrs_config = super(QubesTemplateVm, self)._get_attrs_config()
attrs_config['dir_path']['eval'] = 'value if value is not None else qubes_templates_dir + "/" + self.name'
@ -1800,6 +1845,10 @@ class QubesNetVm(QubesVm):
"""
A class that represents a NetVM. A child of QubesCowVM.
"""
# In which order load this VM type from qubes.xml
load_order = 70
def _get_attrs_config(self):
attrs_config = super(QubesNetVm, self)._get_attrs_config()
attrs_config['dir_path']['eval'] = 'value if value is not None else qubes_servicevms_dir + "/" + self.name'
@ -1807,7 +1856,8 @@ class QubesNetVm(QubesVm):
attrs_config['memory']['default'] = 200
# New attributes
attrs_config['netid'] = { 'save': 'str(self.netid)', 'order': 30 }
attrs_config['netid'] = { 'save': 'str(self.netid)', 'order': 30,
'eval': 'value if value is not None else collection.get_new_unused_netid()' }
attrs_config['netprefix'] = { 'eval': '"10.137.{0}.".format(self.netid)' }
attrs_config['dispnetprefix'] = { 'eval': '"10.138.{0}.".format(self.netid)' }
@ -1960,7 +2010,6 @@ class QubesNetVm(QubesVm):
self.remove_appmenus()
super(QubesNetVm, self).remove_from_disk()
class QubesProxyVm(QubesNetVm):
"""
A class that represents a ProxyVM, ex FirewallVM. A child of QubesNetVM.
@ -2191,6 +2240,9 @@ class QubesDisposableVm(QubesVm):
A class that represents an DisposableVM. A child of QubesVm.
"""
# In which order load this VM type from qubes.xml
load_order = 120
def _get_attrs_config(self):
attrs_config = super(QubesDisposableVm, self)._get_attrs_config()
@ -2237,7 +2289,6 @@ class QubesDisposableVm(QubesVm):
def verify_files(self):
return True
class QubesAppVm(QubesVm):
"""
A class that represents an AppVM. A child of QubesVm.
@ -2307,7 +2358,8 @@ class QubesHVm(QubesVm):
# Default for meminfo-writer have changed to (correct) False in the
# same version as introduction of guiagent_installed, so for older VMs
# with wrong setting, change it based on 'guiagent_installed' presence
if "guiagent_installed" not in kwargs:
if "guiagent_installed" not in kwargs and \
(not 'xml_element' in kwargs or kwargs['xml_element'].get('guiagent_installed') is None):
self.services['meminfo-writer'] = False
# HVM normally doesn't support dynamic memory management
@ -2531,6 +2583,13 @@ class QubesHVm(QubesVm):
return False
return True
register_qubes_vm_class("QubesTemplateVm", QubesTemplateVm)
register_qubes_vm_class("QubesNetVm", QubesNetVm)
register_qubes_vm_class("QubesProxyVm", QubesProxyVm)
register_qubes_vm_class("QubesDisposableVm", QubesDisposableVm)
register_qubes_vm_class("QubesAppVm", QubesAppVm)
register_qubes_vm_class("QubesHVm", QubesHVm)
class QubesVmCollection(dict):
"""
A collection of Qubes VMs indexed by Qubes id (qid)
@ -2877,87 +2936,6 @@ class QubesVmCollection(dict):
return False
return True
def parse_xml_element(self, element):
kwargs = {}
common_attr_list = ("qid", "name", "dir_path", "conf_file",
"private_img", "root_img", "template_qid",
"installed_by_rpm", "internal",
"uses_default_netvm", "label", "memory", "vcpus", "pcidevs",
"maxmem", "kernel", "uses_default_kernel", "kernelopts", "uses_default_kernelopts",
"mac", "services", "include_in_backups", "debug",
"default_user", "qrexec_timeout", "qrexec_installed", "guiagent_installed", "drive" )
for attribute in common_attr_list:
kwargs[attribute] = element.get(attribute)
if kwargs[attribute] is None:
kwargs.pop(attribute)
kwargs["qid"] = int(kwargs["qid"])
if "include_in_backups" in kwargs:
kwargs["include_in_backups"] = True if kwargs["include_in_backups"] == "True" else False
if "installed_by_rpm" in kwargs:
kwargs["installed_by_rpm"] = True if kwargs["installed_by_rpm"] == "True" else False
if "internal" in kwargs:
kwargs["internal"] = True if kwargs["internal"] == "True" else False
if "template_qid" in kwargs:
if kwargs["template_qid"] == "none" or kwargs["template_qid"] is None:
kwargs.pop("template_qid")
else:
kwargs["template_qid"] = int(kwargs["template_qid"])
template = self[kwargs.pop("template_qid")]
if template is None:
print >> sys.stderr, "ERROR: VM '{0}' uses unkown template qid='{1}'!".\
format(kwargs["name"], kwargs["template_qid"])
else:
kwargs["template"] = template
if "label" in kwargs:
if kwargs["label"] not in QubesVmLabels:
print >> sys.stderr, "ERROR: incorrect label for VM '{0}'".format(kwargs["name"])
kwargs.pop ("label")
else:
kwargs["label"] = QubesVmLabels[kwargs["label"]]
if "kernel" in kwargs and kwargs["kernel"] == "None":
kwargs["kernel"] = None
if "uses_default_kernel" in kwargs:
kwargs["uses_default_kernel"] = True if kwargs["uses_default_kernel"] == "True" else False
else:
# For backward compatibility
kwargs["uses_default_kernel"] = False
if kwargs["uses_default_kernel"]:
kwargs["kernel"] = self.get_default_kernel()
else:
if "kernel" in kwargs and kwargs["kernel"]=="None":
kwargs["kernel"]=None
# for other cases - generic assigment is ok
if "uses_default_kernelopts" in kwargs:
kwargs["uses_default_kernelopts"] = False if kwargs["uses_default_kernelopts"] == "False" else True
if "kernelopts" in kwargs and kwargs["kernelopts"] == "None":
kwargs.pop("kernelopts")
if "kernelopts" not in kwargs:
kwargs["uses_default_kernelopts"] = True
if "debug" in kwargs:
kwargs["debug"] = True if kwargs["debug"] == "True" else False
if "qrexec_installed" in kwargs:
kwargs["qrexec_installed"] = True if kwargs["qrexec_installed"] == "True" else False
if "guiagent_installed" in kwargs:
kwargs["guiagent_installed"] = True if kwargs["guiagent_installed"] == "True" else False
if "drive" in kwargs and kwargs["drive"] == "None":
kwargs["drive"] = None
return kwargs
def set_netvm_dependency(self, element):
kwargs = {}
attr_list = ("qid", "uses_default_netvm", "netvm_qid")
@ -2990,28 +2968,11 @@ class QubesVmCollection(dict):
if netvm:
netvm.connected_vms[vm.qid] = vm
def load(self):
self.clear()
dom0vm = QubesDom0NetVm ()
self[dom0vm.qid] = dom0vm
self.default_netvm_qid = 0
global dom0_vm
dom0_vm = dom0vm
try:
tree = lxml.etree.parse(self.qubes_store_file)
except (EnvironmentError,
xml.parsers.expat.ExpatError) as err:
print("{0}: import error: {1}".format(
os.path.basename(sys.argv[0]), err))
return False
element = tree.getroot()
def load_globals(self, element):
default_template = element.get("default_template")
self.default_template_qid = int(default_template) \
if default_template != "None" else None
if default_template.lower() != "none" else None
default_netvm = element.get("default_netvm")
if default_netvm is not None:
@ -3038,152 +2999,53 @@ class QubesVmCollection(dict):
self.default_kernel = element.get("default_kernel")
# Then, read in the TemplateVMs, because a reference to template VM
# is needed to create each AppVM
for element in tree.findall("QubesTemplateVm"):
try:
kwargs = self.parse_xml_element(element)
def load(self):
self.clear()
vm = QubesTemplateVm(**kwargs)
dom0vm = QubesDom0NetVm (collection=self)
self[dom0vm.qid] = dom0vm
self.default_netvm_qid = 0
self[vm.qid] = vm
except (ValueError, LookupError) as err:
print("{0}: import error (QubesTemplateVm): {1}".format(
os.path.basename(sys.argv[0]), err))
return False
global dom0_vm
dom0_vm = dom0vm
# Read in the NetVMs first, because a reference to NetVM
# is needed to create all other VMs
for element in tree.findall("QubesNetVm"):
try:
kwargs = self.parse_xml_element(element)
# Add NetVM specific fields
attr_list = ("netid",)
try:
tree = lxml.etree.parse(self.qubes_store_file)
except (EnvironmentError,
xml.parsers.expat.ExpatError) as err:
print("{0}: import error: {1}".format(
os.path.basename(sys.argv[0]), err))
return False
for attribute in attr_list:
kwargs[attribute] = element.get(attribute)
self.load_globals(tree.getroot())
kwargs["netid"] = int(kwargs["netid"])
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):
try:
vm = vm_class(xml_element=element, collection=self)
self[vm.qid] = vm
except (ValueError, LookupError) as err:
print("{0}: import error ({1}): {2}".format(
os.path.basename(sys.argv[0]), vm_class_name, err))
raise
return False
vm = QubesNetVm(**kwargs)
self[vm.qid] = vm
except (ValueError, LookupError) as err:
print("{0}: import error (QubesNetVM) {1}".format(
os.path.basename(sys.argv[0]), err))
return False
# Next read in the ProxyVMs, because they may be referenced
# by other VMs
for element in tree.findall("QubesProxyVm"):
try:
kwargs = self.parse_xml_element(element)
# Add ProxyVM specific fields
attr_list = ("netid",)
for attribute in attr_list:
kwargs[attribute] = element.get(attribute)
kwargs["netid"] = int(kwargs["netid"])
vm = QubesProxyVm(**kwargs)
self[vm.qid] = vm
except (ValueError, LookupError) as err:
print("{0}: import error (QubesProxyVM) {1}".format(
os.path.basename(sys.argv[0]), err))
return False
# After importing all NetVMs and ProxyVMs, set netvm references
# 1. For TemplateVMs
for element in tree.findall("QubesTemplateVm"):
try:
self.set_netvm_dependency(element)
except (ValueError, LookupError) as err:
print("{0}: import error (QubesTemplateVm): {1}".format(
os.path.basename(sys.argv[0]), err))
return False
# 2. For PoxyVMs
for element in tree.findall("QubesProxyVm"):
try:
self.set_netvm_dependency(element)
except (ValueError, LookupError) as err:
print("{0}: import error (QubesProxyVM) {1}".format(
os.path.basename(sys.argv[0]), err))
return False
# Finally, read in the AppVMs
for element in tree.findall("QubesAppVm"):
try:
kwargs = self.parse_xml_element(element)
vm = QubesAppVm(**kwargs)
self[vm.qid] = vm
self.set_netvm_dependency(element)
except (ValueError, LookupError) as err:
print("{0}: import error (QubesAppVm): {1}".format(
os.path.basename(sys.argv[0]), err))
return False
# And HVMs
for element in tree.findall("QubesHVm"):
try:
kwargs = self.parse_xml_element(element)
vm = QubesHVm(**kwargs)
self[vm.qid] = vm
self.set_netvm_dependency(element)
except (ValueError, LookupError) as err:
print("{0}: import error (QubesHVm): {1}".format(
os.path.basename(sys.argv[0]), err))
return False
# Really finally, read in the DisposableVMs
for element in tree.findall("QubesDisposableVm"):
try:
kwargs = {}
attr_list = ("qid", "name",
"template_qid",
"label", "dispid", "firewall_conf" )
for attribute in attr_list:
kwargs[attribute] = element.get(attribute)
kwargs["qid"] = int(kwargs["qid"])
kwargs["dispid"] = int(kwargs["dispid"])
kwargs["template_qid"] = int(kwargs["template_qid"])
template = self[kwargs.pop("template_qid")]
if template is None:
print >> sys.stderr, "ERROR: DisposableVM '{0}' uses unkown template qid='{1}'!".\
format(kwargs["name"], kwargs["template_qid"])
else:
kwargs["template"] = template
kwargs["netvm"] = self.get_default_netvm()
if kwargs["label"] is not None:
if kwargs["label"] not in QubesVmLabels:
print >> sys.stderr, "ERROR: incorrect label for VM '{0}'".format(kwargs["name"])
kwargs.pop ("label")
else:
kwargs["label"] = QubesVmLabels[kwargs["label"]]
vm = QubesDisposableVm(**kwargs)
self[vm.qid] = vm
except (ValueError, LookupError) as err:
print("{0}: import error (DisposableAppVm): {1}".format(
os.path.basename(sys.argv[0]), err))
return False
# After importing all VMs, set netvm references, in the same order
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):
try:
self.set_netvm_dependency(element)
except (ValueError, LookupError) as err:
print("{0}: import error2 ({}): {}".format(
os.path.basename(sys.argv[0]), vm_class_name, err))
return False
# if there was no clockvm entry in qubes.xml, try to determine default:
# root of default NetVM chain
if clockvm is None:
if element.get("clockvm") is None:
if self.default_netvm_qid is not None:
clockvm = self[self.default_netvm_qid]
# Find root of netvm chain