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_template_label = QubesVmLabels["gray"]
default_servicevm_label = QubesVmLabels["red"] 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): class QubesVm(object):
""" """
A representation of one Qubes VM A representation of one Qubes VM
@ -212,6 +217,9 @@ class QubesVm(object):
Note that qid is not the same as Xen's domid! 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): def _get_attrs_config(self):
""" Object attributes for serialization/deserialization """ Object attributes for serialization/deserialization
inner dict keys: inner dict keys:
@ -242,7 +250,8 @@ class QubesVm(object):
### order >= 20: have template set ### order >= 20: have template set
"uses_default_netvm": { "default": True, 'order': 20 }, "uses_default_netvm": { "default": True, 'order': 20 },
"netvm": { "default": None, "attr": "_netvm", '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)" }, "memory": { "default": default_memory, 'order': 20, "eval": "int(value)" },
"maxmem": { "default": None, 'order': 25, "eval": "int(value) if value else None" }, "maxmem": { "default": None, 'order': 25, "eval": "int(value) if value else None" },
"pcidevs": { "default": '[]', 'order': 25, "eval": \ "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 VM (not shown in qubes-manager, doesn't create appmenus entries
"internal": { "default": False }, "internal": { "default": False },
"vcpus": { "default": None }, "vcpus": { "default": None },
"kernel": { "default": None, 'order': 30 },
"uses_default_kernel": { "default": True, 'order': 30 }, "uses_default_kernel": { "default": True, 'order': 30 },
"uses_default_kernelopts": { "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": \ "kernelopts": { "default": "", 'order': 31, "eval": \
'value if not self.uses_default_kernelopts else default_kernelopts_pcidevs if len(self.pcidevs) > 0 else default_kernelopts' }, 'value if not self.uses_default_kernelopts else default_kernelopts_pcidevs if len(self.pcidevs) > 0 else default_kernelopts' },
"mac": { "attr": "_mac", "default": None }, "mac": { "attr": "_mac", "default": None },
@ -299,8 +309,35 @@ class QubesVm(object):
return attrs 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): 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() attrs = self._get_attrs_config()
for attr_name in sorted(attrs, key=lambda _x: attrs[_x]['order'] if 'order' in attrs[_x] else 1000): for attr_name in sorted(attrs, key=lambda _x: attrs[_x]['order'] if 'order' in attrs[_x] else 1000):
attr_config = attrs[attr_name] attr_config = attrs[attr_name]
@ -308,11 +345,16 @@ class QubesVm(object):
if 'attr' in attr_config: if 'attr' in attr_config:
attr = attr_config['attr'] attr = attr_config['attr']
value = None 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: if 'default' in attr_config:
value = attr_config['default'] value = attr_config['default']
else:
value = kwargs[attr_name]
if 'eval' in attr_config: if 'eval' in attr_config:
setattr(self, attr, eval(attr_config['eval'])) setattr(self, attr, eval(attr_config['eval']))
else: else:
@ -1627,6 +1669,9 @@ class QubesTemplateVm(QubesVm):
A class that represents an TemplateVM. A child of 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): def _get_attrs_config(self):
attrs_config = super(QubesTemplateVm, self)._get_attrs_config() 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' 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. 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): def _get_attrs_config(self):
attrs_config = super(QubesNetVm, self)._get_attrs_config() 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' 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 attrs_config['memory']['default'] = 200
# New attributes # 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['netprefix'] = { 'eval': '"10.137.{0}.".format(self.netid)' }
attrs_config['dispnetprefix'] = { 'eval': '"10.138.{0}.".format(self.netid)' } attrs_config['dispnetprefix'] = { 'eval': '"10.138.{0}.".format(self.netid)' }
@ -1960,7 +2010,6 @@ class QubesNetVm(QubesVm):
self.remove_appmenus() self.remove_appmenus()
super(QubesNetVm, self).remove_from_disk() super(QubesNetVm, self).remove_from_disk()
class QubesProxyVm(QubesNetVm): class QubesProxyVm(QubesNetVm):
""" """
A class that represents a ProxyVM, ex FirewallVM. A child of 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. 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): def _get_attrs_config(self):
attrs_config = super(QubesDisposableVm, self)._get_attrs_config() attrs_config = super(QubesDisposableVm, self)._get_attrs_config()
@ -2237,7 +2289,6 @@ class QubesDisposableVm(QubesVm):
def verify_files(self): def verify_files(self):
return True return True
class QubesAppVm(QubesVm): class QubesAppVm(QubesVm):
""" """
A class that represents an AppVM. A child of 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 # Default for meminfo-writer have changed to (correct) False in the
# same version as introduction of guiagent_installed, so for older VMs # same version as introduction of guiagent_installed, so for older VMs
# with wrong setting, change it based on 'guiagent_installed' presence # 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 self.services['meminfo-writer'] = False
# HVM normally doesn't support dynamic memory management # HVM normally doesn't support dynamic memory management
@ -2531,6 +2583,13 @@ class QubesHVm(QubesVm):
return False return False
return True 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): class QubesVmCollection(dict):
""" """
A collection of Qubes VMs indexed by Qubes id (qid) A collection of Qubes VMs indexed by Qubes id (qid)
@ -2877,87 +2936,6 @@ class QubesVmCollection(dict):
return False return False
return True 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): def set_netvm_dependency(self, element):
kwargs = {} kwargs = {}
attr_list = ("qid", "uses_default_netvm", "netvm_qid") attr_list = ("qid", "uses_default_netvm", "netvm_qid")
@ -2990,28 +2968,11 @@ class QubesVmCollection(dict):
if netvm: if netvm:
netvm.connected_vms[vm.qid] = vm netvm.connected_vms[vm.qid] = vm
def load(self):
self.clear()
dom0vm = QubesDom0NetVm () def load_globals(self, element):
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()
default_template = element.get("default_template") default_template = element.get("default_template")
self.default_template_qid = int(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") default_netvm = element.get("default_netvm")
if default_netvm is not None: if default_netvm is not None:
@ -3038,152 +2999,53 @@ class QubesVmCollection(dict):
self.default_kernel = element.get("default_kernel") 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 global dom0_vm
except (ValueError, LookupError) as err: dom0_vm = dom0vm
print("{0}: import error (QubesTemplateVm): {1}".format(
os.path.basename(sys.argv[0]), err))
return False
# Read in the NetVMs first, because a reference to NetVM try:
# is needed to create all other VMs tree = lxml.etree.parse(self.qubes_store_file)
for element in tree.findall("QubesNetVm"): except (EnvironmentError,
try: xml.parsers.expat.ExpatError) as err:
kwargs = self.parse_xml_element(element) print("{0}: import error: {1}".format(
# Add NetVM specific fields os.path.basename(sys.argv[0]), err))
attr_list = ("netid",) return False
for attribute in attr_list: self.load_globals(tree.getroot())
kwargs[attribute] = element.get(attribute)
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) # After importing all VMs, set netvm references, in the same order
self[vm.qid] = vm for (vm_class_name, vm_class) in sorted(QubesVmClasses.items(),
key=lambda _x: _x[1].load_order):
except (ValueError, LookupError) as err: for element in tree.findall(vm_class_name):
print("{0}: import error (QubesNetVM) {1}".format( try:
os.path.basename(sys.argv[0]), err)) self.set_netvm_dependency(element)
return False except (ValueError, LookupError) as err:
print("{0}: import error2 ({}): {}".format(
# Next read in the ProxyVMs, because they may be referenced os.path.basename(sys.argv[0]), vm_class_name, err))
# by other VMs return False
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
# if there was no clockvm entry in qubes.xml, try to determine default: # if there was no clockvm entry in qubes.xml, try to determine default:
# root of default NetVM chain # root of default NetVM chain
if clockvm is None: if element.get("clockvm") is None:
if self.default_netvm_qid is not None: if self.default_netvm_qid is not None:
clockvm = self[self.default_netvm_qid] clockvm = self[self.default_netvm_qid]
# Find root of netvm chain # Find root of netvm chain