diff --git libcloud/compute/drivers/vcloud.py libcloud/compute/drivers/vcloud.py index 6f5c010..1df498f 100644 --- libcloud/compute/drivers/vcloud.py +++ libcloud/compute/drivers/vcloud.py @@ -15,6 +15,8 @@ """ VMware vCloud driver. """ +import sys +import re import base64 from libcloud.utils.py3 import httplib from libcloud.utils.py3 import urlparse @@ -25,6 +27,7 @@ urlparse = urlparse.urlparse import time from xml.etree import ElementTree as ET +from xml.etree.ElementTree import _ElementInterface from xml.parsers.expat import ExpatError from libcloud.common.base import XmlResponse, ConnectionUserAndKey @@ -38,10 +41,13 @@ from libcloud.compute.base import NodeSize, NodeImage, NodeAuthPassword From vcloud api "The VirtualQuantity element defines the number of MB of memory. This should be either 512 or a multiple of 1024 (1 GB)." """ -VIRTUAL_MEMORY_VALS = [512] + [1024 * i for i in range(1,9)] +VIRTUAL_MEMORY_VALS = [512] + [1024 * i for i in range(1, 9)] DEFAULT_TASK_COMPLETION_TIMEOUT = 600 +DEFAULT_API_VERSION = '0.8' + + def fixxpath(root, xpath): """ElementTree wants namespaces in its xpaths, so here we add them.""" namespace, root_tag = root.tag[1:].split("}", 1) @@ -49,9 +55,19 @@ def fixxpath(root, xpath): for e in xpath.split("/")]) return fixed_xpath + def get_url_path(url): return urlparse(url.strip()).path + +class Vdc: + """Virtual datacenter (vDC) representation""" + def __init__(self, id, name, driver): + self.id = id + self.name = name + self.driver = driver + + class InstantiateVAppXML(object): def __init__(self, name, template, net_href, cpus, memory, @@ -202,12 +218,14 @@ class InstantiateVAppXML(object): {'href': self.net_href} ) + class VCloudResponse(XmlResponse): def success(self): return self.status in (httplib.OK, httplib.CREATED, httplib.NO_CONTENT, httplib.ACCEPTED) + class VCloudConnection(ConnectionUserAndKey): """ Connection class for the vCloud driver @@ -231,7 +249,8 @@ class VCloudConnection(ConnectionUserAndKey): 'Authorization': "Basic %s" % base64.b64encode(b('%s:%s' % (self.user_id, self.key))), - 'Content-Length': 0 + 'Content-Length': 0, + 'Accept': 'application/*+xml' } def _get_auth_token(self): @@ -256,8 +275,10 @@ class VCloudConnection(ConnectionUserAndKey): def add_default_headers(self, headers): headers['Cookie'] = self.token + headers['Accept'] = 'application/*+xml' return headers + class VCloudNodeDriver(NodeDriver): """ vCloud node driver @@ -275,25 +296,41 @@ class VCloudNodeDriver(NodeDriver): '3': NodeState.PENDING, '4': NodeState.RUNNING} + def __new__(cls, key, secret=None, secure=True, host=None, port=None, + api_version=DEFAULT_API_VERSION, **kwargs): + if cls is VCloudNodeDriver: + if api_version == '0.8': + cls = VCloudNodeDriver + elif api_version == '1.5': + cls = VCloud_1_5_NodeDriver + else: + raise NotImplementedError( + "No VCloudNodeDriver found for API version %s" % + (api_version)) + return super(VCloudNodeDriver, cls).__new__(cls) + @property def vdcs(self): + """ + vCloud virtual data centers (vDCs). + @return: C{list} of L{Vdc} objects + """ if not self._vdcs: - self.connection.check_org() # make sure the org is set. + self.connection.check_org() # make sure the org is set. # pylint: disable-msg=E1101 res = self.connection.request(self.org) self._vdcs = [ - get_url_path(i.get('href')) + Vdc(i.get('href'), i.get('name'), self) for i in res.object.findall(fixxpath(res.object, "Link")) if i.get('type') == 'application/vnd.vmware.vcloud.vdc+xml' ] - return self._vdcs @property def networks(self): networks = [] for vdc in self.vdcs: - res = self.connection.request(vdc).object + res = self.connection.request(vdc.id).object networks.extend( [network for network in res.findall( @@ -309,13 +346,21 @@ class VCloudNodeDriver(NodeDriver): driver=self.connection.driver) return image - def _to_node(self, name, elm): + def _to_node(self, elm): state = self.NODE_STATE_MAP[elm.get('status')] + name = elm.get('name') public_ips = [] private_ips = [] # Following code to find private IPs works for Terremark - connections = elm.findall('{http://schemas.dmtf.org/ovf/envelope/1}NetworkConnectionSection/{http://www.vmware.com/vcloud/v0.8}NetworkConnection') + connections = elm.findall('%s/%s' % ( + '{http://schemas.dmtf.org/ovf/envelope/1}NetworkConnectionSection', + fixxpath(elm, 'NetworkConnection') + ) + ) + if not connections: + connections = elm.findall(fixxpath(elm, 'Children/Vm/NetworkConnectionSection/NetworkConnection')) + for connection in connections: ips = [ip.text for ip @@ -351,8 +396,13 @@ class VCloudNodeDriver(NodeDriver): status = res.object.get('status') while status != 'success': if status == 'error': - raise Exception("Error status returned by task %s." - % task_href) + # Get error reason from the response body + error_elem = res.object.find(fixxpath(res.object, 'Error')) + error_msg = "Unknown error" + if error_elem != None: + error_msg = error_elem.get('message') + raise Exception("Error status returned by task %s.: %s" + % (task_href, error_msg)) if status == 'canceled': raise Exception("Canceled status returned by task %s." % task_href) @@ -399,7 +449,7 @@ class VCloudNodeDriver(NodeDriver): def list_nodes(self): nodes = [] for vdc in self.vdcs: - res = self.connection.request(vdc) + res = self.connection.request(vdc.id) elms = res.object.findall(fixxpath( res.object, "ResourceEntities/ResourceEntity") ) @@ -412,14 +462,19 @@ class VCloudNodeDriver(NodeDriver): ] for vapp_name, vapp_href in vapps: - res = self.connection.request( - vapp_href, - headers={ - 'Content-Type': - 'application/vnd.vmware.vcloud.vApp+xml' - } - ) - nodes.append(self._to_node(vapp_name, res.object)) + try: + res = self.connection.request( + vapp_href, + headers={'Content-Type': 'application/vnd.vmware.vcloud.vApp+xml'} + ) + nodes.append(self._to_node(res.object)) + except Exception: + # The vApp was probably removed since the previous vDC query, ignore + e = sys.exc_info()[1] + if not (isinstance(e.args[0], _ElementInterface) and + e.args[0].tag.endswith('Error') and + e.args[0].get('minorErrorCode') == 'ACCESS_TO_RESOURCE_IS_FORBIDDEN'): + raise e return nodes @@ -472,7 +527,7 @@ class VCloudNodeDriver(NodeDriver): def list_images(self, location=None): images = [] for vdc in self.vdcs: - res = self.connection.request(vdc).object + res = self.connection.request(vdc.id).object res_ents = res.findall(fixxpath( res, "ResourceEntities/ResourceEntity") ) @@ -494,7 +549,23 @@ class VCloudNodeDriver(NodeDriver): 'application/vnd.vmware.vcloud.vAppTemplate+xml' ] - return images + def idfun(image): + return image.id + return self._uniquer(images, idfun) + + def _uniquer(self, seq, idfun=None): + if idfun is None: + def idfun(x): + return x + seen = {} + result = [] + for item in seq: + marker = idfun(item) + if marker in seen: + continue + seen[marker] = 1 + result.append(item) + return result def create_node(self, **kwargs): """Creates and returns node. @@ -550,7 +621,7 @@ class VCloudNodeDriver(NodeDriver): # Instantiate VM and get identifier. res = self.connection.request( '%s/action/instantiateVAppTemplate' - % kwargs.get('vdc', self.vdcs[0]), + % kwargs.get('vdc', self.vdcs[0].id), data=instantiate_xml.tostring(), method='POST', headers={ @@ -558,7 +629,6 @@ class VCloudNodeDriver(NodeDriver): 'application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml' } ) - vapp_name = res.object.get('name') vapp_href = get_url_path(res.object.get('href')) # Deploy the VM from the identifier. @@ -572,12 +642,13 @@ class VCloudNodeDriver(NodeDriver): method='POST') res = self.connection.request(vapp_href) - node = self._to_node(vapp_name, res.object) + node = self._to_node(res.object) return node features = {"create_node": ["password"]} + class HostingComConnection(VCloudConnection): """ vCloud connection subclass for Hosting.com @@ -593,12 +664,14 @@ class HostingComConnection(VCloudConnection): 'Content-Length': 0 } + class HostingComDriver(VCloudNodeDriver): """ vCloud node driver for Hosting.com """ connectionCls = HostingComConnection + class TerremarkConnection(VCloudConnection): """ vCloud connection subclass for Terremark @@ -606,6 +679,7 @@ class TerremarkConnection(VCloudConnection): host = "services.vcloudexpress.terremark.com" + class TerremarkDriver(VCloudNodeDriver): """ vCloud node driver for Terremark @@ -615,3 +689,422 @@ class TerremarkDriver(VCloudNodeDriver): def list_locations(self): return [NodeLocation(0, "Terremark Texas", 'US', self)] + + +class VCloud_1_5_Connection(VCloudConnection): + + def _get_auth_token(self): + if not self.token: + # Log In + conn = self.conn_classes[self.secure](self.host, + self.port) + conn.request(method='POST', url='/api/sessions', + headers=self._get_auth_headers()) + + resp = conn.getresponse() + headers = dict(resp.getheaders()) + + # Set authorization token + try: + self.token = headers['x-vcloud-authorization'] + except KeyError: + raise InvalidCredsError() + + # Get the URL of the Organization + body = ET.XML(resp.read()) + org_name = body.get('org') + org_list_url = get_url_path( + next((link for link in body.findall(fixxpath(body, 'Link')) + if link.get('type') == 'application/vnd.vmware.vcloud.orgList+xml')).get('href') + ) + + conn.request(method='GET', url=org_list_url, + headers=self.add_default_headers({})) + body = ET.XML(conn.getresponse().read()) + self.driver.org = get_url_path( + next((org for org in body.findall(fixxpath(body, 'Org')) + if org.get('name') == org_name)).get('href') + ) + + def add_default_headers(self, headers): + headers['Accept'] = 'application/*+xml;version=1.5' + headers['x-vcloud-authorization'] = self.token + return headers + + +class Instantiate_1_5_VAppXML(object): + def __init__(self, name, template, network): + self.name = name + self.template = template + self.network = network + self._build_xmltree() + + def tostring(self): + return ET.tostring(self.root) + + def _build_xmltree(self): + self.root = self._make_instantiation_root() + + if self.network: + instantionation_params = ET.SubElement(self.root, "InstantiationParams") + network_config_section = ET.SubElement(instantionation_params, "NetworkConfigSection") + ET.SubElement(network_config_section, "Info", {'xmlns': "http://schemas.dmtf.org/ovf/envelope/1"}) + network_config = ET.SubElement(network_config_section, "NetworkConfig") + self._add_network_association(network_config) + + self._add_vapp_template(self.root) + + def _make_instantiation_root(self): + return ET.Element( + "InstantiateVAppTemplateParams", + {'name': self.name, + 'deploy': 'false', + 'powerOn': 'false', + 'xml:lang': 'en', + 'xmlns': "http://www.vmware.com/vcloud/v1.5", + 'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance"} + ) + + def _add_vapp_template(self, parent): + return ET.SubElement( + parent, + "Source", + {'href': self.template} + ) + + def _add_network_association(self, parent): + parent.set('networkName', self.network.get('name')) + configuration = ET.SubElement(parent, 'Configuration') + ET.SubElement(configuration, 'ParentNetwork', {'href': self.network.get('href')}) + configuration.append(self.network.find(fixxpath(self.network, 'Configuration/FenceMode'))) + + +class VCloud_1_5_NodeDriver(VCloudNodeDriver): + + connectionCls = VCloud_1_5_Connection + + # Based on http://pubs.vmware.com/vcloud-api-1-5/api_prog/GUID-843BE3AD-5EF6-4442-B864-BCAE44A51867.html + NODE_STATE_MAP = {'-1': NodeState.UNKNOWN, + '0': NodeState.PENDING, + '1': NodeState.PENDING, + '2': NodeState.PENDING, + '3': NodeState.PENDING, + '4': NodeState.RUNNING, + '5': NodeState.RUNNING, + '6': NodeState.UNKNOWN, + '7': NodeState.UNKNOWN, + '8': NodeState.TERMINATED, + '9': NodeState.UNKNOWN, + '10': NodeState.UNKNOWN} + + def list_locations(self): + return [NodeLocation(id=self.connection.host, name=self.connection.host, country="N/A", driver=self)] + + def ex_find_node(self, node_name, vdcs=None): + """ + Searches for node across specified vDCs. This is more effective than querying all nodes to get a single + instance. + + @param node_name: The name of the node to search for + @type node_name: C{string} + + @param vdcs: None, vDC or a list of vDCs to search in. If None all vDCs will be searched. + @type node_name: L{Vdc} + + @return: C{Node} node instance or None if not found + """ + if not vdcs: + vdcs = self.vdcs + if not getattr(vdcs, '__iter__', False): + vdcs = [vdcs] + for vdc in vdcs: + res = self.connection.request(vdc.id) + entity_elems = res.object.findall(fixxpath(res.object, "ResourceEntities/ResourceEntity")) + for entity_elem in entity_elems: + if entity_elem.get('type') == 'application/vnd.vmware.vcloud.vApp+xml' and entity_elem.get('name') == node_name: + res = self.connection.request(entity_elem.get('href'), + headers={'Content-Type': 'application/vnd.vmware.vcloud.vApp+xml'}) + return self._to_node(res.object) + return None + + def destroy_node(self, node): + try: + self.ex_undeploy_node(node) + except Exception: + # Some vendors don't implement undeploy at all yet, + # so catch this and move on. + pass + + res = self.connection.request(get_url_path(node.id), method='DELETE') + return res.status == 202 + + def reboot_node(self, node): + res = self.connection.request('%s/power/action/reset' + % get_url_path(node.id), + method='POST') + if res.status == 202 or res.status == 204: + self._wait_for_task_completion(res.object.get('href')) + return True + else: + return False + + def ex_deploy_node(self, node): + """ + Deploys existing node. Equal to vApp "start" operation. + + @param node: The node to be deployed + @type node: L{Node} + + @return: C{Node} deployed node + """ + deploy_xml = ET.Element('DeployVAppParams', {'powerOn': 'true', + 'xmlns': 'http://www.vmware.com/vcloud/v1.5'}) + res = self.connection.request('%s/action/deploy' % get_url_path(node.id), + data=ET.tostring(deploy_xml), + method='POST', + headers={ + 'Content-Type': 'application/vnd.vmware.vcloud.deployVAppParams+xml' + }) + self._wait_for_task_completion(res.object.get('href')) + res = self.connection.request(get_url_path(node.id)) + return self._to_node(res.object) + + def ex_undeploy_node(self, node): + """ + Undeploys existing node. Equal to vApp "stop" operation. + + @param node: The node to be deployed + @type node: L{Node} + + @return: C{Node} undeployed node instance + """ + undeploy_xml = ET.Element('UndeployVAppParams', {'xmlns': 'http://www.vmware.com/vcloud/v1.5'}) + undeploy_power_action_xml = ET.SubElement(undeploy_xml, 'UndeployPowerAction') + undeploy_power_action_xml.text = 'shutdown' + + try: + res = self.connection.request('%s/action/undeploy' % get_url_path(node.id), + data=ET.tostring(undeploy_xml), + method='POST', + headers={ + 'Content-Type': 'application/vnd.vmware.vcloud.undeployVAppParams+xml' + }) + self._wait_for_task_completion(res.object.get('href')) + except Exception: + undeploy_power_action_xml.text = 'powerOff' + res = self.connection.request('%s/action/undeploy' % get_url_path(node.id), + data=ET.tostring(undeploy_xml), + method='POST', + headers={ + 'Content-Type': 'application/vnd.vmware.vcloud.undeployVAppParams+xml' + }) + self._wait_for_task_completion(res.object.get('href')) + + res = self.connection.request(get_url_path(node.id)) + return self._to_node(res.object) + + def create_node(self, **kwargs): + """Creates and returns node. If the source image is: + - vApp template - a new vApp is instantiated from template + - existing vApp - a new vApp is cloned from the source vApp. Can not clone more vApps is parallel otherwise + resource busy error is raised. + + + See L{NodeDriver.create_node} for more keyword args. + + @keyword image: OS Image to boot on node. (required). Can be a NodeImage or existing Node that will be + cloned. + @type image: L{NodeImage} or L{Node} + + Non-standard optional keyword arguments: + @keyword ex_network: link to a "Network" e.g., "https://services.vcloudexpress.terremark.com/api/network/7" + @type ex_network: C{string} + + @keyword ex_vdc: link to a "VDC" e.g., "https://services.vcloudexpress.terremark.com/api/vdc/1" + @type ex_vdc: C{string} + + @keyword ex_vm_names: list of names to be used as a VM and computer name. The name must be max. 15 characters + long and follow the host name requirements + @type ex_vm_names: C{list} of L{string} + + """ + name = kwargs['name'] + image = kwargs['image'] + size = kwargs.get('size', None) + ex_vm_names = kwargs.get('ex_vm_names') + + self._validate_vm_names(ex_vm_names) + + # Some providers don't require a network link + network_href = kwargs.get('ex_network', None) + if network_href: + network_elem = self.connection.request(network_href).object + else: + network_elem = None + + vdc = kwargs.get('ex_vdc', self.vdcs[0]) + if not vdc: + vdc = self.vdcs[0] + + if self._is_node(image): + vapp_name, vapp_href = self._clone_node(name, image, vdc) + else: + vapp_name, vapp_href = self._instantiate_node(name, image, network_elem, vdc) + + self._change_vm_names(vapp_href, ex_vm_names) + + # Power on the VM. + # Retry 3 times: when instantiating large number of VMs at the same time some may fail on resource allocation + retry = 3 + while True: + try: + res = self.connection.request('%s/power/action/powerOn' % vapp_href, + method='POST') + self._wait_for_task_completion(res.object.get('href')) + break + except Exception: + if retry <= 0: + raise + retry -= 1 + time.sleep(10) + + res = self.connection.request(vapp_href) + node = self._to_node(res.object) + + return node + + def _instantiate_node(self, name, image, network_elem, vdc): + instantiate_xml = Instantiate_1_5_VAppXML( + name=name, + template=image.id, + network=network_elem + ) + + # Instantiate VM and get identifier. + res = self.connection.request( + '%s/action/instantiateVAppTemplate' % vdc.id, + data=instantiate_xml.tostring(), + method='POST', + headers={ + 'Content-Type': + 'application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml' + } + ) + vapp_name = res.object.get('name') + vapp_href = get_url_path(res.object.get('href')) + + task_href = res.object.find(fixxpath(res.object, "Tasks/Task")).get('href') + self._wait_for_task_completion(task_href) + return vapp_name, vapp_href + + def _clone_node(self, name, sourceNode, vdc): + + clone_xml = ET.Element("CloneVAppParams", + {'name': name, 'deploy': 'false', 'powerOn': 'false', + 'xmlns': "http://www.vmware.com/vcloud/v1.5", + 'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance"} + ) + ET.SubElement(clone_xml, 'Description').text = 'Clone of ' + sourceNode.name + ET.SubElement(clone_xml, 'Source', {'href': sourceNode.id}) + + res = self.connection.request( + '%s/action/cloneVApp' % vdc.id, + data=ET.tostring(clone_xml), + method='POST', + headers={ + 'Content-Type': + 'application/vnd.vmware.vcloud.cloneVAppParams+xml' + } + ) + vapp_name = res.object.get('name') + vapp_href = get_url_path(res.object.get('href')) + + task_href = res.object.find(fixxpath(res.object, "Tasks/Task")).get('href') + self._wait_for_task_completion(task_href) + + res = self.connection.request(vapp_href) + + vms = res.object.findall(fixxpath(res.object, "Children/Vm")) + + # Fix the networking for VMs + for i, vm in enumerate(vms): + # Remove network + network_xml = ET.Element("NetworkConnectionSection", { + 'ovf:required': 'false', + 'xmlns': "http://www.vmware.com/vcloud/v1.5", + 'xmlns:ovf': 'http://schemas.dmtf.org/ovf/envelope/1'}) + ET.SubElement(network_xml, "ovf:Info").text = 'Specifies the available VM network connections' + res = self.connection.request('%s/networkConnectionSection' % get_url_path(vm.get('href')), + data=ET.tostring(network_xml), + method='PUT', + headers={'Content-Type': 'application/vnd.vmware.vcloud.networkConnectionSection+xml'} + ) + self._wait_for_task_completion(res.object.get('href')) + + # Re-add network + network_xml = vm.find(fixxpath(vm, 'NetworkConnectionSection')) + network_conn_xml = network_xml.find(fixxpath(network_xml, 'NetworkConnection')) + network_conn_xml.set('needsCustomization', 'true') + network_conn_xml.remove(network_conn_xml.find(fixxpath(network_xml, 'IpAddress'))) + network_conn_xml.remove(network_conn_xml.find(fixxpath(network_xml, 'MACAddress'))) + + res = self.connection.request('%s/networkConnectionSection' % get_url_path(vm.get('href')), + data=ET.tostring(network_xml), + method='PUT', + headers={'Content-Type': 'application/vnd.vmware.vcloud.networkConnectionSection+xml'} + ) + self._wait_for_task_completion(res.object.get('href')) + + return vapp_name, vapp_href + + @staticmethod + def _validate_vm_names(names): + if names is None: + return + hname_re = re.compile('^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9]*)[\-])*([A-Za-z]|[A-Za-z][A-Za-z0-9]*[A-Za-z0-9])$') + for name in names: + if len(name) > 15: + raise ValueError('The VM name "' + name + '" is too long for the computer name (max 15 chars allowed).') + if not hname_re.match(name): + raise ValueError('The VM name "' + name + '" can not be used. "' + name + '" is not a valid computer name for the VM.') + + def _change_vm_names(self, vapp_href, vm_names): + if vm_names is None: + return + + res = self.connection.request(vapp_href) + vms = res.object.findall(fixxpath(res.object, "Children/Vm")) + + for i, vm in enumerate(vms): + if len(vm_names) <= i: + return + + # Get GuestCustomizationSection + res = self.connection.request('%s/guestCustomizationSection' % get_url_path(vm.get('href'))) + + # Update GuestCustomizationSection + res.object.find(fixxpath(res.object, 'ComputerName')).text = vm_names[i] + # Remove AdminPassword from customization section + admin_pass = res.object.find(fixxpath(res.object, 'AdminPassword')) + if admin_pass is not None: + res.object.remove(admin_pass) + res = self.connection.request('%s/guestCustomizationSection' % get_url_path(vm.get('href')), + data=ET.tostring(res.object), + method='PUT', + headers={'Content-Type': 'application/vnd.vmware.vcloud.guestCustomizationSection+xml'} + ) + self._wait_for_task_completion(res.object.get('href')) + + # Update Vm name + req_xml = ET.Element("Vm", { + 'name': vm_names[i], + 'xmlns': "http://www.vmware.com/vcloud/v1.5"}) + res = self.connection.request(get_url_path(vm.get('href')), + data=ET.tostring(req_xml), + method='PUT', + headers={'Content-Type': 'application/vnd.vmware.vcloud.vm+xml'} + ) + self._wait_for_task_completion(res.object.get('href')) + + def _is_node(self, node_or_image): + return isinstance(node_or_image, Node) diff --git libcloud/compute/providers.py libcloud/compute/providers.py index d4b838a..f16c37e 100644 --- libcloud/compute/providers.py +++ libcloud/compute/providers.py @@ -101,6 +101,8 @@ DRIVERS = { ('libcloud.compute.drivers.openstack', 'OpenStackNodeDriver'), Provider.NINEFOLD: ('libcloud.compute.drivers.ninefold', 'NinefoldNodeDriver'), + Provider.VCLOUD: + ('libcloud.compute.drivers.vcloud', 'VCloudNodeDriver'), Provider.TERREMARK: ('libcloud.compute.drivers.vcloud', 'TerremarkDriver'), Provider.CLOUDSTACK: diff --git test/compute/fixtures/vcloud_1_5/api_catalogItem_3132e037_759b_4627_9056_ca66466fa607.xml test/compute/fixtures/vcloud_1_5/api_catalogItem_3132e037_759b_4627_9056_ca66466fa607.xml new file mode 100644 index 0000000..d48b6f8 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_catalogItem_3132e037_759b_4627_9056_ca66466fa607.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_catalog_cddb3cb2_3394_4b14_b831_11fbc4028da4.xml test/compute/fixtures/vcloud_1_5/api_catalog_cddb3cb2_3394_4b14_b831_11fbc4028da4.xml new file mode 100644 index 0000000..9cca0b7 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_catalog_cddb3cb2_3394_4b14_b831_11fbc4028da4.xml @@ -0,0 +1,9 @@ + + + + + + + + false + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_network_dca8b667_6c8f_4c3e_be57_7a9425dba4f4.xml test/compute/fixtures/vcloud_1_5/api_network_dca8b667_6c8f_4c3e_be57_7a9425dba4f4.xml new file mode 100644 index 0000000..3385375 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_network_dca8b667_6c8f_4c3e_be57_7a9425dba4f4.xml @@ -0,0 +1,49 @@ + + + + + + + true + 65.41.64.1 + 255.255.252.0 + 65.41.42.113 + 65.41.42.114 + vm.netsuite.com + + + 65.41.65.1 + 65.41.65.64 + + + 65.41.65.70 + 65.41.65.88 + + + 65.41.65.90 + 65.41.66.6 + + + 65.41.66.8 + 65.41.66.67 + + + 65.41.66.69 + 65.41.66.108 + + + 65.41.66.110 + 65.41.66.227 + + + 65.41.66.229 + 65.41.67.254 + + + + bridged + false + + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_org.xml test/compute/fixtures/vcloud_1_5/api_org.xml new file mode 100644 index 0000000..6cf4081 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_org.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_org_96726c78_4ae3_402f_b08b_7a78c6903d2a.xml test/compute/fixtures/vcloud_1_5/api_org_96726c78_4ae3_402f_b08b_7a78c6903d2a.xml new file mode 100644 index 0000000..7060d31 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_org_96726c78_4ae3_402f_b08b_7a78c6903d2a.xml @@ -0,0 +1,11 @@ + + + + + + + + + + MyOrg Product Development + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_sessions.xml test/compute/fixtures/vcloud_1_5/api_sessions.xml new file mode 100644 index 0000000..bccdc58 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_sessions.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_task_b034df55_fe81_4798_bc81_1f0fd0ead450.xml test/compute/fixtures/vcloud_1_5/api_task_b034df55_fe81_4798_bc81_1f0fd0ead450.xml new file mode 100644 index 0000000..1f55575 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_task_b034df55_fe81_4798_bc81_1f0fd0ead450.xml @@ -0,0 +1,9 @@ + + + + + 100 + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_task_undeploy.xml test/compute/fixtures/vcloud_1_5/api_task_undeploy.xml new file mode 100644 index 0000000..05f0953 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_task_undeploy.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_task_undeploy_error.xml test/compute/fixtures/vcloud_1_5/api_task_undeploy_error.xml new file mode 100644 index 0000000..59effee --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_task_undeploy_error.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_vApp_undeployTest.xml test/compute/fixtures/vcloud_1_5/api_vApp_undeployTest.xml new file mode 100644 index 0000000..6b05191 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_vApp_undeployTest.xml @@ -0,0 +1,271 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Lease settings section + + 0 + 0 + + + VApp startup section + + + + + The list of logical networks + + + + + + The configuration parameters for logical networks + + + + + + + true + 65.41.64.1 + 255.255.252.0 + 65.41.42.113 + 65.41.42.114 + vm.myorg.com + + + 65.41.67.1 + 65.41.67.254 + + + + + bridged + false + + + false + 3600 + 7200 + + 65.41.64.2 + 65.41.67.0 + + + + true + drop + false + + true + Allow all outgoing traffic + allow + + true + + -1 + Any + -1 + Any + out + false + + + + true + ipTranslation + allowTraffic + + + automatic + ScrumVM_Master + 0 + + + + + false + + + + + false + + + + + + false + + + + + + + + + + + + + + + Virtual hardware requirements + + Virtual Hardware Family + 0 + mgalet-test2 + vmx-07 + + + 00:50:56:01:00:99 + 0 + true + vCloud - Default + PCNet32 ethernet adapter + Network adapter 0 + 1 + PCNet32 + 10 + + + 0 + SCSI Controller + SCSI Controller 0 + 2 + lsilogic + 6 + + + 0 + Hard disk + Hard disk 1 + + 2000 + 2 + 17 + + + 0 + IDE Controller + IDE Controller 0 + 3 + 5 + + + 0 + false + CD/DVD Drive + CD/DVD Drive 1 + + 3002 + 3 + 15 + + + 0 + false + Floppy Drive + Floppy Drive 1 + + 8000 + 14 + + + hertz * 10^6 + Number of Virtual CPUs + 2 virtual CPU(s) + 4 + 0 + 3 + 2 + 0 + + + + byte * 2^20 + Memory Size + 4096 MB of memory + 5 + 0 + 4 + 4096 + 0 + + + + + + + + + + + + + + + + + Specifies the operating system installed + Red Hat Enterprise Linux 5 (64-bit) + + + + Specifies the available VM network connections + 0 + + 0 + 65.41.67.2 + true + 00:50:56:01:00:99 + POOL + + + + + Specifies Guest OS Customization Settings + true + false + dd75d1d3-5b7b-48f0-aff3-69622ab7e045 + false + false + true + true + sN#9QH9# + false + mgalet-test2 + + + + Specifies Runtime info + + + ScrumVM_Master + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a.xml test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a.xml new file mode 100644 index 0000000..0a6512e --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a.xml @@ -0,0 +1,295 @@ + + + + + + + + + + + + + + + + + + Lease settings section + + 0 + 0 + + + VApp startup section + + + + + The list of logical networks + + + + + + The configuration parameters for logical networks + + + + + + + true + 65.41.64.1 + 255.255.252.0 + 65.41.42.113 + 65.41.42.114 + vm.myorg.com + + + 65.41.67.1 + 65.41.67.254 + + + + + bridged + false + + + false + 3600 + 7200 + + 65.41.64.2 + 65.41.67.0 + + + + true + drop + false + + true + Allow all outgoing traffic + allow + + true + + -1 + Any + -1 + Any + out + false + + + + true + ipTranslation + allowTraffic + + + automatic + ScrumVM_Master + 0 + + + + + false + + + + + true + + + + + + false + + + + + + + + + + + + + + + + + + + Virtual hardware requirements + + Virtual Hardware Family + 0 + mgalet-test2 + vmx-07 + + + 00:50:56:01:00:99 + 0 + true + vCloud - Default + PCNet32 ethernet adapter + Network adapter 0 + 1 + PCNet32 + 10 + + + 0 + SCSI Controller + SCSI Controller 0 + 2 + lsilogic + 6 + + + 0 + Hard disk + Hard disk 1 + + 2000 + 2 + 17 + + + 0 + IDE Controller + IDE Controller 0 + 3 + 5 + + + 0 + false + CD/DVD Drive + CD/DVD Drive 1 + + 3002 + 3 + 15 + + + 0 + false + Floppy Drive + Floppy Drive 1 + + 8000 + 14 + + + hertz * 10^6 + Number of Virtual CPUs + 2 virtual CPU(s) + 4 + 0 + 3 + 2 + 0 + + + + byte * 2^20 + Memory Size + 4096 MB of memory + 5 + 0 + 4 + 4096 + 0 + + + + + + + + + + + + + + + + + Specifies the operating system installed + Red Hat Enterprise Linux 5 (64-bit) + + + + Specifies the available VM network connections + 0 + + 0 + 65.41.67.2 + true + 00:50:56:01:00:99 + POOL + + + + + Specifies Guest OS Customization Settings + true + false + dd75d1d3-5b7b-48f0-aff3-69622ab7e045 + false + false + true + true + sN#9QH9# + false + mgalet-test2 + + + + Specifies Runtime info + + + ScrumVM_Master + + + VMware ESXi + 5.0.0 + VMware, Inc. + en + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn.xml test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn.xml new file mode 100644 index 0000000..5472234 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset.xml test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset.xml new file mode 100644 index 0000000..568b8d5 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_vApp_vapp_access_to_resource_forbidden.xml test/compute/fixtures/vcloud_1_5/api_vApp_vapp_access_to_resource_forbidden.xml new file mode 100644 index 0000000..830c059 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_vApp_vapp_access_to_resource_forbidden.xml @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml new file mode 100644 index 0000000..1542ad5 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + AllocationVApp + + MB + 0 + 0 + 126976 + 0 + + + + MHz + 0 + 0 + 6000 + 0 + + + MB + 0 + 0 + 4255 + 0 + + + + + + + + + + + + + vmx-04 + vmx-07 + vmx-08 + + + 0 + 1024 + 150 + true + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_cloneVApp.xml test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_cloneVApp.xml new file mode 100644 index 0000000..c162a36 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_cloneVApp.xml @@ -0,0 +1,20 @@ + + + + testing instance + + + + + + + 1 + + + + + + false + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_instantiateVAppTemplate.xml test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_instantiateVAppTemplate.xml new file mode 100644 index 0000000..7ba1576 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_instantiateVAppTemplate.xml @@ -0,0 +1,23 @@ + + + + + + + testing instance + + + + + + + 1 + + + + + + false + \ No newline at end of file diff --git test/compute/fixtures/vcloud_1_5/get_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml test/compute/fixtures/vcloud_1_5/get_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml new file mode 100644 index 0000000..49da789 --- /dev/null +++ test/compute/fixtures/vcloud_1_5/get_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml @@ -0,0 +1,17 @@ + + Specifies Guest OS Customization Settings + true + false + 9e8837e6-5c4c-4112-bf01-5498616d865f + false + false + true + true + aabbccddee + false + VMMast-001 + + diff --git test/compute/fixtures/vcloud_1_5/put_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml test/compute/fixtures/vcloud_1_5/put_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml new file mode 100644 index 0000000..31cd08f --- /dev/null +++ test/compute/fixtures/vcloud_1_5/put_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git test/compute/test_vcloud.py test/compute/test_vcloud.py index 08ad9a0..0128eeb 100644 --- test/compute/test_vcloud.py +++ test/compute/test_vcloud.py @@ -14,11 +14,11 @@ # limitations under the License. import sys import unittest +from xml.etree import ElementTree as ET from libcloud.utils.py3 import httplib -from libcloud.compute.drivers.vcloud import TerremarkDriver -from libcloud.compute.drivers.vcloud import VCloudNodeDriver -from libcloud.compute.base import Node +from libcloud.compute.drivers.vcloud import TerremarkDriver, VCloudNodeDriver, VCloud_1_5_NodeDriver, Vdc +from libcloud.compute.base import Node, NodeImage from libcloud.compute.types import NodeState from test import MockHttp @@ -77,6 +77,101 @@ class TerremarkTests(unittest.TestCase, TestCaseMixin): ret = self.driver.destroy_node(node) self.assertTrue(ret) +class VCloud_1_5_Tests(unittest.TestCase, TestCaseMixin): + + def setUp(self): + VCloudNodeDriver.connectionCls.host = 'test' + VCloudNodeDriver.connectionCls.conn_classes = (None, VCloud_1_5_MockHttp) + VCloud_1_5_MockHttp.type = None + self.driver = VCloud_1_5_NodeDriver(*VCLOUD_PARAMS) + + def test_list_images(self): + ret = self.driver.list_images() + self.assertEqual('https://vm-vcloud/api/vAppTemplate/vappTemplate-ac1bc027-bf8c-4050-8643-4971f691c158', ret[0].id) + + def test_list_sizes(self): + ret = self.driver.list_sizes() + self.assertEqual(ret[0].ram, 512) + + def test_networks(self): + ret = self.driver.networks + self.assertEqual(ret[0].get('href'), 'https://vm-vcloud/api/network/dca8b667-6c8f-4c3e-be57-7a9425dba4f4') + + def test_create_node(self): + image = self.driver.list_images()[0] + size = self.driver.list_sizes()[0] + node = self.driver.create_node( + name='testNode', + image=image, + size=size, + ex_vdc=Vdc('https://vm-vcloud/api/vdc/3d9ae28c-1de9-4307-8107-9356ff8ba6d0', 'MyVdc', self.driver), + ex_network='https://vm-vcloud/api/network/dca8b667-6c8f-4c3e-be57-7a9425dba4f4', + cpus=2, + ) + self.assertTrue(isinstance(node, Node)) + self.assertEqual('https://vm-vcloud/api/vApp/vapp-8c57a5b6-e61b-48ca-8a78-3b70ee65ef6a', node.id) + self.assertEqual('testNode', node.name) + + def test_create_node_clone(self): + image = self.driver.list_nodes()[0] + node = self.driver.create_node(name='testNode', image=image) + self.assertTrue(isinstance(node, Node)) + self.assertEqual('https://vm-vcloud/api/vApp/vapp-8c57a5b6-e61b-48ca-8a78-3b70ee65ef6a', node.id) + self.assertEqual('testNode', node.name) + + def test_list_nodes(self): + ret = self.driver.list_nodes() + node = ret[0] + self.assertEqual(node.id, 'https://vm-vcloud/api/vApp/vapp-8c57a5b6-e61b-48ca-8a78-3b70ee65ef6a') + self.assertEqual(node.name, 'testNode') + self.assertEqual(node.state, NodeState.RUNNING) + self.assertEqual(node.public_ips, ['65.41.67.2']) + self.assertEqual(node.private_ips, []) + + def test_reboot_node(self): + node = self.driver.list_nodes()[0] + ret = self.driver.reboot_node(node) + self.assertTrue(ret) + + def test_destroy_node(self): + node = self.driver.list_nodes()[0] + ret = self.driver.destroy_node(node) + self.assertTrue(ret) + + def test_validate_vm_names(self): + # valid inputs + self.driver._validate_vm_names(['host-n-ame-name']) + self.driver._validate_vm_names(['tc-mybuild-b1']) + self.driver._validate_vm_names(None) + # invalid inputs + self.assertRaises(ValueError, self.driver._validate_vm_names, ['invalid.host']) + self.assertRaises(ValueError, self.driver._validate_vm_names, ['inv-alid.host']) + self.assertRaises(ValueError, self.driver._validate_vm_names, ['hostnametoooolong']) + self.assertRaises(ValueError, self.driver._validate_vm_names, ['host$name']) + + def test_change_vm_names(self): + self.driver._change_vm_names('/api/vApp/vapp-8c57a5b6-e61b-48ca-8a78-3b70ee65ef6a', ['changed1', 'changed2']) + + def test_is_node(self): + self.assertTrue(self.driver._is_node(Node('testId', 'testNode', state=0, public_ips=[], private_ips=[], driver=self.driver))) + self.assertFalse(self.driver._is_node(NodeImage('testId', 'testNode', driver=self.driver))) + + def test_ex_undeploy(self): + node = self.driver.ex_undeploy_node(Node('https://test/api/vApp/undeployTest', 'testNode', state=0, public_ips=[], private_ips=[], driver=self.driver)) + self.assertEqual(node.state, NodeState.TERMINATED) + + def test_ex_undeploy_with_error(self): + node = self.driver.ex_undeploy_node(Node('https://test/api/vApp/undeployErrorTest', 'testNode', state=0, public_ips=[], private_ips=[], driver=self.driver)) + self.assertEqual(node.state, NodeState.TERMINATED) + + def test_ex_find_node(self): + node = self.driver.ex_find_node('testNode') + self.assertEqual(node.name, "testNode") + node = self.driver.ex_find_node('testNode', self.driver.vdcs[0]) + self.assertEqual(node.name, "testNode") + node = self.driver.ex_find_node('testNonExisting', self.driver.vdcs[0]) + self.assertEqual(node, None) + class TerremarkMockHttp(MockHttp): @@ -138,5 +233,117 @@ class TerremarkMockHttp(MockHttp): body = self.fixtures.load('api_v0_8_task_11001.xml') return (httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED]) + +class VCloud_1_5_MockHttp(MockHttp): + + fixtures = ComputeFileFixtures('vcloud_1_5') + + def _api_sessions(self, method, url, body, headers): + headers['set-cookie'] = 'vcloud-token=testtoken' + body = self.fixtures.load('api_sessions.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_org(self, method, url, body, headers): + body = self.fixtures.load('api_org.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_org_96726c78_4ae3_402f_b08b_7a78c6903d2a(self, method, url, body, headers): + body = self.fixtures.load('api_org_96726c78_4ae3_402f_b08b_7a78c6903d2a.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_network_dca8b667_6c8f_4c3e_be57_7a9425dba4f4(self, method, url, body, headers): + body = self.fixtures.load('api_network_dca8b667_6c8f_4c3e_be57_7a9425dba4f4.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0(self, method, url, body, headers): + body = self.fixtures.load('api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_instantiateVAppTemplate(self, method, url, body, headers): + body = self.fixtures.load('api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_instantiateVAppTemplate.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + + def _api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn(self, method, url, body, headers): + body = self.fixtures.load('api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_powerOn.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + + # Clone + def _api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_cloneVApp(self, method, url, body, headers): + body = self.fixtures.load('api_vdc_3d9ae28c_1de9_4307_8107_9356ff8ba6d0_action_cloneVApp.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + + def _api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_networkConnectionSection(self, method, url, body, headers): + body = self.fixtures.load('api_task_b034df55_fe81_4798_bc81_1f0fd0ead450.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + + def _api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a(self, method, url, body, headers): + status = httplib.OK + if method == 'GET': + body = self.fixtures.load('api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a.xml') + status = httplib.OK + elif method == 'DELETE': + body = self.fixtures.load('api_task_b034df55_fe81_4798_bc81_1f0fd0ead450.xml') + status = httplib.ACCEPTED + return status, body, headers, httplib.responses[status] + + def _api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045(self, method, url, body, headers): + body = self.fixtures.load('put_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + + def _api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection(self, method, url, body, headers): + if method == 'GET': + body = self.fixtures.load('get_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml') + status = httplib.OK + else: + body = self.fixtures.load('put_api_vApp_vm_dd75d1d3_5b7b_48f0_aff3_69622ab7e045_guestCustomizationSection.xml') + status = httplib.ACCEPTED + return status, body, headers, httplib.responses[status] + + def _api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset(self, method, url, body, headers): + body = self.fixtures.load('api_vApp_vapp_8c57a5b6_e61b_48ca_8a78_3b70ee65ef6a_power_action_reset.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + + def _api_task_b034df55_fe81_4798_bc81_1f0fd0ead450(self, method, url, body, headers): + body = self.fixtures.load('api_task_b034df55_fe81_4798_bc81_1f0fd0ead450.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_catalog_cddb3cb2_3394_4b14_b831_11fbc4028da4(self, method, url, body, headers): + body = self.fixtures.load('api_catalog_cddb3cb2_3394_4b14_b831_11fbc4028da4.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_catalogItem_3132e037_759b_4627_9056_ca66466fa607(self, method, url, body, headers): + body = self.fixtures.load('api_catalogItem_3132e037_759b_4627_9056_ca66466fa607.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_vApp_undeployTest(self, method, url, body, headers): + body = self.fixtures.load('api_vApp_undeployTest.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_vApp_undeployTest_action_undeploy(self, method, url, body, headers): + body = self.fixtures.load('api_task_undeploy.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + + def _api_task_undeploy(self, method, url, body, headers): + body = self.fixtures.load('api_task_undeploy.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_vApp_undeployErrorTest(self, method, url, body, headers): + body = self.fixtures.load('api_vApp_undeployTest.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_vApp_undeployErrorTest_action_undeploy(self, method, url, body, headers): + if 'shutdown' in body: + body = self.fixtures.load('api_task_undeploy_error.xml') + else: + body = self.fixtures.load('api_task_undeploy.xml') + return httplib.ACCEPTED, body, headers, httplib.responses[httplib.ACCEPTED] + + def _api_task_undeployError(self, method, url, body, headers): + body = self.fixtures.load('api_task_undeploy_error.xml') + return httplib.OK, body, headers, httplib.responses[httplib.OK] + + def _api_vApp_vapp_access_to_resource_forbidden(self, method, url, body, headers): + raise Exception(ET.fromstring(self.fixtures.load('api_vApp_vapp_access_to_resource_forbidden.xml'))) + if __name__ == '__main__': sys.exit(unittest.main())