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 @@
+
+
+
+
+
+
\ 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
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+ 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..4909913 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['x-vcloud-authorization'] = '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())