Source code for COT.ovf.name_helper

#!/usr/bin/env python
#
# name_helper.py - Handling the many XML names in an OVF descriptor
#
# June 2016, Glenn F. Matthews
# Copyright (c) 2013-2016 the COT project developers.
# See the COPYRIGHT.txt file at the top-level directory of this distribution
# and at https://github.com/glennmatthews/cot/blob/master/COPYRIGHT.txt.
#
# This file is part of the Common OVF Tool (COT) project.
# It is subject to the license terms in the LICENSE.txt file found in the
# top-level directory of this distribution and at
# https://github.com/glennmatthews/cot/blob/master/LICENSE.txt. No part
# of COT, including this file, may be copied, modified, propagated, or
# distributed except according to the terms contained in the LICENSE.txt file.
"""Module for handling the differences in XML between OVF spec versions.

**Functions**

.. autosummary::
  :nosignatures:

  name_helper

**Classes and Exceptions**

.. autosummary::
  :nosignatures:

  OVFNameHelper1
  OVFNameHelper0
  OVFNameHelper2
"""

from COT.data_validation import ValueUnsupportedError


[docs]def name_helper(version): """Generate an instance of the correct OVFNameHelper variant class. Args: version (float): OVF specification version to use, such as 0.9, 1.0, or 2.0 Returns: Instance of OVFNameHelper[012] as appropriate. """ if version < 1.0: return OVFNameHelper0() elif version < 2.0: return OVFNameHelper1() else: return OVFNameHelper2()
class _Tag(object): """Helper class representing a named XML namespace and associated tag.""" def __init__(self, namespace_name, tag): """Store namespace name and tag. Args: namespace_name (str): XML namespace name tag (str): XML tag """ self.namespace_name = namespace_name.upper() self.tag = tag CIM_URI = "http://schemas.dmtf.org/wbem/wscim/1"
[docs]class OVFNameHelper1(object): """Helper class for :class:`OVF` version 1.x. Provides string constants for easier lookup of various OVF XML elements and attributes. Version-specific subclasses below provide variant properties. """ # For the standard namespace URIs in an OVF descriptor, let's define # shorthand identifiers to be used when writing back out to XML: NSM = dict( xsi="http://www.w3.org/2001/XMLSchema-instance", cim=CIM_URI + "/common", rasd=CIM_URI + "/cim-schema/2/CIM_ResourceAllocationSettingData", vssd=CIM_URI + "/cim-schema/2/CIM_VirtualSystemSettingData", # The OVF namespace varies by version ovf="http://schemas.dmtf.org/ovf/envelope/1", # Older OVF versions have ethernet and storage items # in the same RASD namespace as other hardware, but 2.x has separate ) """Shorthand for XML namespace URIs usually seen in a version 1.x OVF.""" # Non-standard namespaces (such as VMWare's # 'http://www.vmware.com/schema/ovf') should not be added to the NSM # dictionary, but may be registered manually by calling # register_namespace() as needed - see OVF.write() for examples. # List of ResourceType string values we know about # http://schemas.dmtf.org/wbem/cim-html/2/ # CIM_ResourceAllocationSettingData.html RES_MAP = { 'cpu': '3', 'memory': '4', 'ide': '5', 'scsi': '6', 'fc': '7', 'iscsi': '8', 'ib': '9', 'ethernet': '10', 'floppy': '14', 'cdrom': '15', 'dvd': '16', 'harddisk': '17', 'sata': '20', # 'Other Storage' but VBox uses for SATA 'serial': '21', 'parallel': '22', 'usb': '23', } """Mapping of human-readable strings to ResourceType values. See http://schemas.dmtf.org/wbem/cim-html/2/CIM_ResourceAllocationSettingData.html for more details. """ # noqa: E501 # Cached strings, built on the fly _cache = {} # XML elements we care about in the OVF descriptor # TagPlusNamespace objects _raw = dict( # Top-level element is Envelope ENVELOPE=_Tag('ovf', 'Envelope'), # All Section elements have an Info element as child INFO=_Tag('ovf', 'Info'), # Envelope -> NetworkSection -> Network NETWORK_SECTION=_Tag('ovf', 'NetworkSection'), NETWORK=_Tag('ovf', 'Network'), # Attributes of a Network element NETWORK_NAME=_Tag('ovf', 'name'), # Network sub-elements NWK_DESC=_Tag('ovf', 'Description'), # Envelope -> DeploymentOptionSection -> Configuration DEPLOY_OPT_SECTION=_Tag('ovf', 'DeploymentOptionSection'), CONFIG=_Tag('ovf', 'Configuration'), # Attributes of a Configuration element CONFIG_ID=_Tag('ovf', 'id'), CONFIG_DEFAULT=_Tag('ovf', 'default'), # Configuration sub-elements CFG_LABEL=_Tag('ovf', 'Label'), CFG_DESC=_Tag('ovf', 'Description'), # Envelope -> References -> File REFERENCES=_Tag('ovf', 'References'), FILE=_Tag('ovf', 'File'), # Attributes of a File element FILE_ID=_Tag('ovf', 'id'), FILE_HREF=_Tag('ovf', 'href'), FILE_SIZE=_Tag('ovf', 'size'), # Envelope -> DiskSection -> Disk DISK_SECTION=_Tag('ovf', 'DiskSection'), DISK=_Tag('ovf', 'Disk'), # Attributes of a Disk element DISK_ID=_Tag('ovf', 'diskId'), DISK_FILE_REF=_Tag('ovf', 'fileRef'), DISK_CAPACITY=_Tag('ovf', 'capacity'), DISK_CAP_UNITS=_Tag('ovf', 'capacityAllocationUnits'), DISK_FORMAT=_Tag('ovf', 'format'), # Envelope -> VirtualSystem -> AnnotationSection -> Annotation ANNOTATION_SECTION=_Tag('ovf', 'AnnotationSection'), ANNOTATION=_Tag('ovf', 'Annotation'), # Envelope -> VirtualSystem -> ProductSection VIRTUAL_SYSTEM=_Tag('ovf', 'VirtualSystem'), PRODUCT_SECTION=_Tag('ovf', 'ProductSection'), # ProductSection attributes PRODUCT_CLASS=_Tag('ovf', 'class'), # ProductSection sub-elements PRODUCT=_Tag('ovf', 'Product'), VENDOR=_Tag('ovf', 'Vendor'), VERSION=_Tag('ovf', 'Version'), FULL_VERSION=_Tag('ovf', 'FullVersion'), PRODUCT_URL=_Tag('ovf', 'ProductUrl'), VENDOR_URL=_Tag('ovf', 'VendorUrl'), APPLICATION_URL=_Tag('ovf', 'AppUrl'), PROPERTY=_Tag('ovf', 'Property'), # Attributes of a Property element PROP_KEY=_Tag('ovf', 'key'), PROP_VALUE=_Tag('ovf', 'value'), PROP_QUAL=_Tag('ovf', 'qualifiers'), PROP_TYPE=_Tag('ovf', 'type'), PROP_USER_CONFIGABLE=_Tag('ovf', 'userConfigurable'), # Property sub-elements PROPERTY_LABEL=_Tag('ovf', 'Label'), PROPERTY_DESC=_Tag('ovf', 'Description'), ENVIRONMENT_TRANSPORT=_Tag('ovf', 'transport'), # Envelope -> VirtualSystem -> EulaSection -> License EULA_SECTION=_Tag('ovf', 'EulaSection'), EULA_LICENSE=_Tag('ovf', 'License'), # Envelope -> VirtualSystem -> VirtualHardwareSection -> Item(s) # In version 2.x, there can also be StorageItem and EthernetPortItem VIRTUAL_HW_SECTION=_Tag('ovf', 'VirtualHardwareSection'), ITEM=_Tag('ovf', 'Item'), # These are just regular Items in older OVF versions STORAGE_ITEM=_Tag('ovf', 'Item'), ETHERNET_PORT_ITEM=_Tag('ovf', 'Item'), # Item attributes ITEM_CONFIG=_Tag('ovf', 'configuration'), # ... VirtualHardwareSection -> System -> VirtualSystemType SYSTEM=_Tag('ovf', 'System'), VIRTUAL_SYSTEM_TYPE=_Tag('vssd', 'VirtualSystemType'), ) # Item sub-elements # As these are shared across the RASD, SASD, and EPASD namespaces # in OVF 2.0, we don't hard-code a namespace any more. _item_children = dict( ADDRESS='Address', ADDRESS_ON_PARENT='AddressOnParent', ALLOCATION_UNITS='AllocationUnits', AUTOMATIC_ALLOCATION='AutomaticAllocation', AUTOMATIC_DEALLOCATION='AutomaticDeallocation', CAPTION='Caption', CONNECTION='Connection', CONSUMER_VISIBILITY='ConsumerVisibility', ITEM_DESCRIPTION='Description', ELEMENT_NAME='ElementName', HOST_RESOURCE='HostResource', OLD_HOST_RSRC_FILE_REF="/file/", OLD_HOST_RSRC_DISK_REF="/disk/", HOST_RSRC_FILE_REF="ovf:/file/", HOST_RSRC_DISK_REF="ovf:/disk/", INSTANCE_ID='InstanceID', LIMIT='Limit', MAPPING_BEHAVIOR='MappingBehavior', OTHER_RESOURCE_TYPE='OtherResourceType', PARENT='Parent', POOL_ID='PoolID', RESERVATION='Reservation', RESOURCE_SUB_TYPE='ResourceSubType', RESOURCE_TYPE='ResourceType', VIRTUAL_QUANTITY='VirtualQuantity', WEIGHT='Weight', ) def __getattr__(self, name): """Transparently pass attribute lookups to _raw and _cache. Args: name (str): Attribute name to look up. Returns: Value looked up from :attr:`_raw` and/or :attr:`_cache`. Raises: AttributeError: if the given ``name`` is not found. """ if name in self._item_children: return self._item_children[name] if name not in self._cache: if name.lower() in self.NSM: self._cache[name] = "{%s}" % self.NSM[name.lower()] elif name == "EPASD" or name == "SASD": self._cache[name] = self.RASD elif name not in self._raw: raise AttributeError else: ns = getattr(self, self._raw[name].namespace_name) tag = self._raw[name].tag self._cache[name] = ns + tag return self._cache[name]
[docs] def __init__(self): """Create a name helper for OVF version 1.x.""" # 1.0 is nice in that they're all in alphabetical order self.ITEM_CHILDREN = ( self.ADDRESS, self.ADDRESS_ON_PARENT, self.ALLOCATION_UNITS, self.AUTOMATIC_ALLOCATION, self.AUTOMATIC_DEALLOCATION, self.CAPTION, self.CONNECTION, self.CONSUMER_VISIBILITY, self.ITEM_DESCRIPTION, self.ELEMENT_NAME, self.HOST_RESOURCE, self.INSTANCE_ID, self.LIMIT, self.MAPPING_BEHAVIOR, self.OTHER_RESOURCE_TYPE, self.PARENT, self.POOL_ID, self.RESERVATION, self.RESOURCE_SUB_TYPE, self.RESOURCE_TYPE, self.VIRTUAL_QUANTITY, self.WEIGHT, ) # all of these are 0.9 exclusive self.NETWORK_SECTION_ATTRIB = {} self.DISK_SECTION_ATTRIB = {} self.ANNOTATION_SECTION_ATTRIB = {} self.VIRTUAL_SYSTEM_ATTRIB = {} self.PRODUCT_SECTION_ATTRIB = {} self.EULA_SECTION_ATTRIB = {} self.VIRTUAL_HW_SECTION_ATTRIB = {}
[docs] def namespace_for_item_tag(self, tag): """Get the XML namespace for the given item tag. Args: tag (str): Un-namespaced XML tag. Returns: str: XML namespace string, or None. """ if tag == self.ITEM: return self.RASD elif tag == self.STORAGE_ITEM: return self.SASD elif tag == self.ETHERNET_PORT_ITEM: return self.EPASD return None
[docs] def namespace_for_resource_type(self, resource_type): """Get the XML namespace for the given ResourceType. Args: resource_type (str): ResourceType value string. Returns: str: XML namespace string, or None. """ if resource_type == self.RES_MAP['ethernet']: return self.EPASD elif (resource_type == self.RES_MAP['harddisk'] or resource_type == self.RES_MAP['cdrom']): return self.SASD else: return self.RASD
[docs] def item_tag_for_namespace(self, ns): """Get the Item tag for the given XML namespace. Args: ns (str): XML namespace Returns: str: 'Item', 'StorageItem', or 'EthernetPortItem' as appropriate. Raises: ValueUnsupportedError: if the namespace is unrecognized """ if ns == self.RASD: return self.ITEM elif ns == self.SASD: return self.STORAGE_ITEM elif ns == self.EPASD: return self.ETHERNET_PORT_ITEM else: raise ValueUnsupportedError("namespace", ns, [self.RASD, self.SASD, self.EPASD])
[docs]class OVFNameHelper0(OVFNameHelper1): """Helper class for :class:`OVF` of versions prior to 1.0. Provides string constants for easier lookup of various OVF XML elements and attributes. """ NSM = dict( OVFNameHelper1.NSM, ovf="http://www.vmware.com/schema/ovf/1/envelope", ) """Shorthand for XML namespace URIs usually seen in a version 0.x OVF.""" _cache = dict(OVFNameHelper1._cache) _raw = dict( OVFNameHelper1._raw, NETWORK_SECTION=_Tag('ovf', 'Section'), DISK_SECTION=_Tag('ovf', 'Section'), ANNOTATION_SECTION=_Tag('ovf', 'Section'), VIRTUAL_SYSTEM=_Tag('ovf', 'Content'), PRODUCT_SECTION=_Tag('ovf', 'Section'), PROP_VALUE=_Tag('ovf', 'defaultValue'), PROP_USER_CONFIGABLE=_Tag('ovf', 'configurableByUser'), EULA_SECTION=_Tag('ovf', 'Section'), VIRTUAL_HW_SECTION=_Tag('ovf', 'Section'), ) _item_children = dict( OVFNameHelper1._item_children, BUS_NUMBER='BusNumber', # No ElementName in 0.9, but Caption serves a similar purpose ELEMENT_NAME='Caption', HOST_RSRC_FILE_REF="/file/", HOST_RSRC_DISK_REF="/disk/", INSTANCE_ID='InstanceId', )
[docs] def __init__(self): """Create a name helper for OVF version 0.x.""" super(OVFNameHelper0, self).__init__() self.ITEM_CHILDREN = ( self.CAPTION, self.ITEM_DESCRIPTION, self.INSTANCE_ID, self.RESOURCE_TYPE, self.OTHER_RESOURCE_TYPE, self.RESOURCE_SUB_TYPE, self.POOL_ID, self.CONSUMER_VISIBILITY, self.HOST_RESOURCE, self.ALLOCATION_UNITS, self.VIRTUAL_QUANTITY, self.RESERVATION, self.LIMIT, self.WEIGHT, self.AUTOMATIC_ALLOCATION, self.AUTOMATIC_DEALLOCATION, self.PARENT, self.CONNECTION, self.ADDRESS, self.MAPPING_BEHAVIOR, self.ADDRESS_ON_PARENT, self.BUS_NUMBER, ) xsi_type = "{" + self.NSM['xsi'] + "}type" self.NETWORK_SECTION_ATTRIB = { xsi_type: "ovf:NetworkSection_Type" } self.DISK_SECTION_ATTRIB = { xsi_type: "ovf:DiskSection_Type" } self.ANNOTATION_SECTION_ATTRIB = { xsi_type: "ovf:AnnotationSection_Type" } self.VIRTUAL_SYSTEM_ATTRIB = { xsi_type: "ovf:VirtualSystem_Type" } self.PRODUCT_SECTION_ATTRIB = { xsi_type: "ovf:ProductSection_Type" } self.EULA_SECTION_ATTRIB = { xsi_type: "ovf:EulaSection_Type" } self.VIRTUAL_HW_SECTION_ATTRIB = { xsi_type: "ovf:VirtualHardwareSection_Type" }
[docs]class OVFNameHelper2(OVFNameHelper1): """Helper class for :class:`OVF` of version 2.x. TODO. Provides string constants for easier lookup of various OVF XML elements and attributes. """ NSM = dict( OVFNameHelper1.NSM, ovf="http://schemas.dmtf.org/ovf/envelope/2", # OVF 2.0 adds new namespaces for ethernet ports & storage devices epasd=(CIM_URI + "/cim-schema/2/CIM_EthernetPortAllocationSettingData.xsd"), sasd=(CIM_URI + "/cim-schema/2/CIM_StorageAllocationSettingData.xsd"), ) """Shorthand for XML namespace URIs usually seen in a version 2.x OVF.""" _cache = dict(OVFNameHelper1._cache) _raw = dict( OVFNameHelper1._raw, STORAGE_ITEM=_Tag('ovf', 'StorageItem'), ETHERNET_PORT_ITEM=_Tag('ovf', 'EthernetPortItem'), )
[docs] def __init__(self): """Create a name helper for OVF version 2.x.""" super(OVFNameHelper2, self).__init__()