text: # Licensed to the Apache Software Foundation (ASF) under one or more
  # contributor license agreements.  See the NOTICE file distributed with
  # this work for additional information regarding copyright ownership.
  # The ASF licenses this file to You under the Apache License, Version 2.0
  # (the "License"); you may not use this file except in compliance with
  # the License.  You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  """
  VMware vCloud driver.
  """
  import copy
  import sys
  import re
  import base64
  import os
  from libcloud.utils.py3 import httplib
  from libcloud.utils.py3 import urlencode
  from libcloud.utils.py3 import urlparse
  from libcloud.utils.py3 import b
  from libcloud.utils.py3 import next
  from libcloud.utils.py3 import ET
  
  urlparse = urlparse.urlparse
  
  import time
  
  from xml.parsers.expat import ExpatError
  
  from libcloud.common.base import XmlResponse, ConnectionUserAndKey
  from libcloud.common.types import InvalidCredsError, LibcloudError
  from libcloud.compute.providers import Provider
  from libcloud.compute.types import NodeState
  from libcloud.compute.base import Node, NodeDriver, NodeLocation
  from libcloud.compute.base import NodeSize, NodeImage
  
  """
  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)]
  
  # Default timeout (in seconds) for long running tasks
  DEFAULT_TASK_COMPLETION_TIMEOUT = 600
  
  DEFAULT_API_VERSION = '0.8'
  
  """
  Valid vCloud API v1.5 input values.
  """
  VIRTUAL_CPU_VALS_1_5 = [i for i in range(1, 9)]
  FENCE_MODE_VALS_1_5 = ['bridged', 'isolated', 'natRouted']
  IP_MODE_VALS_1_5 = ['POOL', 'DHCP', 'MANUAL', 'NONE']
  
  
  def fixxpath(root, xpath):
      """ElementTree wants namespaces in its xpaths, so here we add them."""
      namespace, root_tag = root.tag[1:].split("}", 1)
      fixed_xpath = "/".join(["{%s}%s" % (namespace, e)
                              for e in xpath.split("/")])
      return fixed_xpath
  
  
  def get_url_path(url):
      return urlparse(url.strip()).path
  
  
  class Vdc(object):
      """
      Virtual datacenter (vDC) representation
      """
      def __init__(self, id, name, driver, allocation_model=None, cpu=None,
                   memory=None, storage=None):
          self.id = id
          self.name = name
          self.driver = driver
          self.allocation_model = allocation_model
          self.cpu = cpu
          self.memory = memory
          self.storage = storage
  
      def __repr__(self):
          return ('<Vdc: id=%s, name=%s, driver=%s  ...>'
                  % (self.id, self.name, self.driver.name))
  
  
  class Capacity(object):
      """
      Represents CPU, Memory or Storage capacity of vDC.
      """
      def __init__(self, limit, used, units):
          self.limit = limit
          self.used = used
          self.units = units
  
      def __repr__(self):
          return ('<Capacity: limit=%s, used=%s, units=%s>'
                  % (self.limit, self.used, self.units))
  
  
  class ControlAccess(object):
      """
      Represents control access settings of a node
      """
      class AccessLevel(object):
          READ_ONLY = 'ReadOnly'
          CHANGE = 'Change'
          FULL_CONTROL = 'FullControl'
  
      def __init__(self, node, everyone_access_level, subjects=None):
          self.node = node
          self.everyone_access_level = everyone_access_level
          if not subjects:
              subjects = []
          self.subjects = subjects
  
      def __repr__(self):
          return ('<ControlAccess: node=%s, everyone_access_level=%s, '
                  'subjects=%s>'
                  % (self.node, self.everyone_access_level, self.subjects))
  
  
  class Subject(object):
      """
      User or group subject
      """
      def __init__(self, type, name, access_level, id=None):
          self.type = type
          self.name = name
          self.access_level = access_level
          self.id = id
  
      def __repr__(self):
          return ('<Subject: type=%s, name=%s, access_level=%s>'
                  % (self.type, self.name, self.access_level))
  
  
  class InstantiateVAppXML(object):
  
      def __init__(self, name, template, net_href, cpus, memory,
                   password=None, row=None, group=None):
          self.name = name
          self.template = template
          self.net_href = net_href
          self.cpus = cpus
          self.memory = memory
          self.password = password
          self.row = row
          self.group = group
  
          self._build_xmltree()
  
      def tostring(self):
          return ET.tostring(self.root)
  
      def _build_xmltree(self):
          self.root = self._make_instantiation_root()
  
          self._add_vapp_template(self.root)
          instantiation_params = ET.SubElement(self.root,
                                               "InstantiationParams")
  
          # product and virtual hardware
          self._make_product_section(instantiation_params)
          self._make_virtual_hardware(instantiation_params)
  
          network_config_section = ET.SubElement(instantiation_params,
                                                 "NetworkConfigSection")
  
          network_config = ET.SubElement(network_config_section,
                                         "NetworkConfig")
          self._add_network_association(network_config)
  
      def _make_instantiation_root(self):
          return ET.Element(
              "InstantiateVAppTemplateParams",
              {'name': self.name,
               'xml:lang': 'en',
               'xmlns': "http://www.vmware.com/vcloud/v0.8",
               'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance"}
          )
  
      def _add_vapp_template(self, parent):
          return ET.SubElement(
              parent,
              "VAppTemplate",
              {'href': self.template}
          )
  
      def _make_product_section(self, parent):
          prod_section = ET.SubElement(
              parent,
              "ProductSection",
              {'xmlns:q1': "http://www.vmware.com/vcloud/v0.8",
               'xmlns:ovf': "http://schemas.dmtf.org/ovf/envelope/1"}
          )
  
          if self.password:
              self._add_property(prod_section, 'password', self.password)
  
          if self.row:
              self._add_property(prod_section, 'row', self.row)
  
          if self.group:
              self._add_property(prod_section, 'group', self.group)
  
          return prod_section
  
      def _add_property(self, parent, ovfkey, ovfvalue):
          return ET.SubElement(
              parent,
              "Property",
              {'xmlns': 'http://schemas.dmtf.org/ovf/envelope/1',
               'ovf:key': ovfkey,
               'ovf:value': ovfvalue}
          )
  
      def _make_virtual_hardware(self, parent):
          vh = ET.SubElement(
              parent,
              "VirtualHardwareSection",
              {'xmlns:q1': "http://www.vmware.com/vcloud/v0.8"}
          )
  
          self._add_cpu(vh)
          self._add_memory(vh)
  
          return vh
  
      def _add_cpu(self, parent):
          cpu_item = ET.SubElement(
              parent,
              "Item",
              {'xmlns': "http://schemas.dmtf.org/ovf/envelope/1"}
          )
          self._add_instance_id(cpu_item, '1')
          self._add_resource_type(cpu_item, '3')
          self._add_virtual_quantity(cpu_item, self.cpus)
  
          return cpu_item
  
      def _add_memory(self, parent):
          mem_item = ET.SubElement(
              parent,
              'Item',
              {'xmlns': "http://schemas.dmtf.org/ovf/envelope/1"}
          )
          self._add_instance_id(mem_item, '2')
          self._add_resource_type(mem_item, '4')
          self._add_virtual_quantity(mem_item, self.memory)
  
          return mem_item
  
      def _add_instance_id(self, parent, id):
          elm = ET.SubElement(
              parent,
              'InstanceID',
              {'xmlns': 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/'
                        'CIM_ResourceAllocationSettingData'}
          )
          elm.text = id
          return elm
  
      def _add_resource_type(self, parent, type):
          elm = ET.SubElement(
              parent,
              'ResourceType',
              {'xmlns': 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/'
                        'CIM_ResourceAllocationSettingData'}
          )
          elm.text = type
          return elm
  
      def _add_virtual_quantity(self, parent, amount):
          elm = ET.SubElement(
              parent,
              'VirtualQuantity',
              {'xmlns': 'http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/'
                        'CIM_ResourceAllocationSettingData'}
          )
          elm.text = amount
          return elm
  
      def _add_network_association(self, parent):
          return ET.SubElement(
              parent,
              'NetworkAssociation',
              {'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
      """
  
      responseCls = VCloudResponse
      token = None
      host = None
  
      def request(self, *args, **kwargs):
          self._get_auth_token()
          return super(VCloudConnection, self).request(*args, **kwargs)
  
      def check_org(self):
          # the only way to get our org is by logging in.
          self._get_auth_token()
  
      def _get_auth_headers(self):
          """Some providers need different headers than others"""
          return {
              'Authorization': "Basic %s" % base64.b64encode(
                  b('%s:%s' % (self.user_id, self.key))).decode('utf-8'),
              'Content-Length': '0',
              'Accept': 'application/*+xml'
          }
  
      def _get_auth_token(self):
          if not self.token:
              self.connection.request(method='POST', url='/api/v0.8/login',
                                      headers=self._get_auth_headers())
  
              resp = self.connection.getresponse()
              headers = resp.headers
              body = ET.XML(resp.text)
  
              try:
                  self.token = headers['set-cookie']
              except KeyError:
                  raise InvalidCredsError()
  
              self.driver.org = get_url_path(
                  body.find(fixxpath(body, 'Org')).get('href')
              )
  
      def add_default_headers(self, headers):
          headers['Cookie'] = self.token
          headers['Accept'] = 'application/*+xml'
          return headers
  
  
  class VCloudNodeDriver(NodeDriver):
  
      """
      vCloud node driver
      """
  
      type = Provider.VCLOUD
      name = 'vCloud'
      website = 'http://www.vmware.com/products/vcloud/'
      connectionCls = VCloudConnection
      org = None
      _vdcs = None
  
      NODE_STATE_MAP = {'0': NodeState.PENDING,
                        '1': NodeState.PENDING,
                        '2': NodeState.PENDING,
                        '3': NodeState.PENDING,
                        '4': NodeState.RUNNING}
  
      features = {'create_node': ['password']}
  
      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
              elif api_version == '5.1':
                  cls = VCloud_5_1_NodeDriver
              elif api_version == '5.5':
                  cls = VCloud_5_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: list of vDC objects
          :rtype: ``list`` of :class:`Vdc`
          """
          if not self._vdcs:
              self.connection.check_org()  # make sure the org is set.
              res = self.connection.request(self.org)
              self._vdcs = [
                  self._to_vdc(
                      self.connection.request(get_url_path(i.get('href'))).object
                  )
                  for i in res.object.findall(fixxpath(res.object, "Link"))
                  if i.get('type') == 'application/vnd.vmware.vcloud.vdc+xml'
              ]
          return self._vdcs
  
      def _to_vdc(self, vdc_elm):
          return Vdc(vdc_elm.get('href'), vdc_elm.get('name'), self)
  
      def _get_vdc(self, vdc_name):
          vdc = None
          if not vdc_name:
              # Return the first organisation VDC found
              vdc = self.vdcs[0]
          else:
              for v in self.vdcs:
                  if v.name == vdc_name:
                      vdc = v
              if vdc is None:
                  raise ValueError('%s virtual data centre could not be found',
                                   vdc_name)
          return vdc
  
      @property
      def networks(self):
          networks = []
          for vdc in self.vdcs:
              res = self.connection.request(get_url_path(vdc.id)).object
              networks.extend(
                  [network
                   for network in res.findall(
                       fixxpath(res, 'AvailableNetworks/Network')
  
                   )]
              )
  
          return networks
  
      def _to_image(self, image):
          image = NodeImage(id=image.get('href'),
                            name=image.get('name'),
                            driver=self.connection.driver)
          return image
  
      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('%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
                     in connection.findall(fixxpath(elm, "IpAddress"))]
              if connection.get('Network') == 'Internal':
                  private_ips.extend(ips)
              else:
                  public_ips.extend(ips)
  
          node = Node(id=elm.get('href'),
                      name=name,
                      state=state,
                      public_ips=public_ips,
                      private_ips=private_ips,
                      driver=self.connection.driver)
  
          return node
  
      def _get_catalog_hrefs(self):
          res = self.connection.request(self.org)
          catalogs = [
              i.get('href')
              for i in res.object.findall(fixxpath(res.object, "Link"))
              if i.get('type') == 'application/vnd.vmware.vcloud.catalog+xml'
          ]
  
          return catalogs
  
      def _wait_for_task_completion(self, task_href,
                                    timeout=DEFAULT_TASK_COMPLETION_TIMEOUT):
          start_time = time.time()
          res = self.connection.request(get_url_path(task_href))
          status = res.object.get('status')
          while status != 'success':
              if status == 'error':
                  # Get error reason from the response body
                  error_elem = res.object.find(fixxpath(res.object, 'Error'))
                  error_msg = "Unknown error"
                  if error_elem is not 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)
              if (time.time() - start_time >= timeout):
                  raise Exception("Timeout (%s sec) while waiting for task %s."
                                  % (timeout, task_href))
              time.sleep(5)
              res = self.connection.request(get_url_path(task_href))
              status = res.object.get('status')
  
      def destroy_node(self, node):
          node_path = get_url_path(node.id)
          # blindly poweroff node, it will throw an exception if already off
          try:
              res = self.connection.request('%s/power/action/poweroff'
                                            % node_path,
                                            method='POST')
              self._wait_for_task_completion(res.object.get('href'))
          except Exception:
              pass
  
          try:
              res = self.connection.request('%s/action/undeploy' % node_path,
                                            method='POST')
              self._wait_for_task_completion(res.object.get('href'))
          except ExpatError:
              # The undeploy response is malformed XML atm.
              # We can remove this whent he providers fix the problem.
              pass
          except Exception:
              # Some vendors don't implement undeploy at all yet,
              # so catch this and move on.
              pass
  
          res = self.connection.request(node_path, method='DELETE')
          return res.status == httplib.ACCEPTED
  
      def reboot_node(self, node):
          res = self.connection.request('%s/power/action/reset'
                                        % get_url_path(node.id),
                                        method='POST')
          return res.status in [httplib.ACCEPTED, httplib.NO_CONTENT]
  
      def list_nodes(self):
          return self.ex_list_nodes()
  
      def ex_list_nodes(self, vdcs=None):
          """
          List all nodes across all vDCs. Using 'vdcs' you can specify which vDCs
          should be queried.
  
          :param vdcs: None, vDC or a list of vDCs to query. If None all vDCs
                       will be queried.
          :type vdcs: :class:`Vdc`
  
          :rtype: ``list`` of :class:`Node`
          """
          if not vdcs:
              vdcs = self.vdcs
          if not isinstance(vdcs, (list, tuple)):
              vdcs = [vdcs]
          nodes = []
          for vdc in vdcs:
              res = self.connection.request(get_url_path(vdc.id))
              elms = res.object.findall(fixxpath(
                  res.object, "ResourceEntities/ResourceEntity")
              )
              vapps = [
                  (i.get('name'), i.get('href'))
                  for i in elms if
                  i.get('type') == 'application/vnd.vmware.vcloud.vApp+xml' and
                  i.get('name')
              ]
  
              for vapp_name, vapp_href in vapps:
                  try:
                      res = self.connection.request(
                          get_url_path(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 (e.args[0].tag.endswith('Error') and
                              e.args[0].get('minorErrorCode') ==
                              'ACCESS_TO_RESOURCE_IS_FORBIDDEN'):
                          raise
  
          return nodes
  
      def _to_size(self, ram):
          ns = NodeSize(
              id=None,
              name="%s Ram" % ram,
              ram=ram,
              disk=None,
              bandwidth=None,
              price=None,
              driver=self.connection.driver
          )
          return ns
  
      def list_sizes(self, location=None):
          sizes = [self._to_size(i) for i in VIRTUAL_MEMORY_VALS]
          return sizes
  
      def _get_catalogitems_hrefs(self, catalog):
          """Given a catalog href returns contained catalog item hrefs"""
          res = self.connection.request(
              get_url_path(catalog),
              headers={
                  'Content-Type': 'application/vnd.vmware.vcloud.catalog+xml'
              }
          ).object
  
          cat_items = res.findall(fixxpath(res, "CatalogItems/CatalogItem"))
          cat_item_hrefs = [i.get('href')
                            for i in cat_items
                            if i.get('type') ==
                            'application/vnd.vmware.vcloud.catalogItem+xml']
  
          return cat_item_hrefs
  
      def _get_catalogitem(self, catalog_item):
          """Given a catalog item href returns elementree"""
          res = self.connection.request(
              get_url_path(catalog_item),
              headers={
                  'Content-Type': 'application/vnd.vmware.vcloud.catalogItem+xml'
              }
          ).object
  
          return res
  
      def list_images(self, location=None):
          images = []
          for vdc in self.vdcs:
              res = self.connection.request(get_url_path(vdc.id)).object
              res_ents = res.findall(fixxpath(
                  res, "ResourceEntities/ResourceEntity")
              )
              images += [
                  self._to_image(i)
                  for i in res_ents
                  if i.get('type') ==
                  'application/vnd.vmware.vcloud.vAppTemplate+xml'
              ]
  
          for catalog in self._get_catalog_hrefs():
              for cat_item in self._get_catalogitems_hrefs(catalog):
                  res = self._get_catalogitem(cat_item)
                  res_ents = res.findall(fixxpath(res, 'Entity'))
                  images += [
                      self._to_image(i)
                      for i in res_ents
                      if i.get('type') ==
                      'application/vnd.vmware.vcloud.vAppTemplate+xml'
                  ]
  
          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.
  
          :keyword    ex_network: link to a "Network" e.g.,
                      ``https://services.vcloudexpress...``
          :type       ex_network: ``str``
  
          :keyword    ex_vdc: Name of organisation's virtual data
                              center where vApp VMs will be deployed.
          :type       ex_vdc: ``str``
  
          :keyword    ex_cpus: number of virtual cpus (limit depends on provider)
          :type       ex_cpus: ``int``
  
          :type       ex_row: ``str``
  
          :type       ex_group: ``str``
          """
          name = kwargs['name']
          image = kwargs['image']
          size = kwargs['size']
  
          # Some providers don't require a network link
          try:
              network = kwargs.get('ex_network', self.networks[0].get('href'))
          except IndexError:
              network = ''
  
          password = None
          auth = self._get_and_check_auth(kwargs.get('auth'))
          password = auth.password
  
          instantiate_xml = InstantiateVAppXML(
              name=name,
              template=image.id,
              net_href=network,
              cpus=str(kwargs.get('ex_cpus', 1)),
              memory=str(size.ram),
              password=password,
              row=kwargs.get('ex_row', None),
              group=kwargs.get('ex_group', None)
          )
  
          vdc = self._get_vdc(kwargs.get('ex_vdc', None))
  
          # Instantiate VM and get identifier.
          content_type = \
              'application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml'
          res = self.connection.request(
              '%s/action/instantiateVAppTemplate' % get_url_path(vdc.id),
              data=instantiate_xml.tostring(),
              method='POST',
              headers={'Content-Type': content_type}
          )
          vapp_path = get_url_path(res.object.get('href'))
  
          # Deploy the VM from the identifier.
          res = self.connection.request('%s/action/deploy' % vapp_path,
                                        method='POST')
  
          self._wait_for_task_completion(res.object.get('href'))
  
          # Power on the VM.
          res = self.connection.request('%s/power/action/powerOn' % vapp_path,
                                        method='POST')
  
          res = self.connection.request(vapp_path)
          node = self._to_node(res.object)
  
          if getattr(auth, "generated", False):
              node.extra['password'] = auth.password
  
          return node
  
  
  class HostingComConnection(VCloudConnection):
  
      """
      vCloud connection subclass for Hosting.com
      """
  
      host = "vcloud.safesecureweb.com"
  
      def _get_auth_headers(self):
          """hosting.com doesn't follow the standard vCloud authentication API"""
          return {
              'Authentication': base64.b64encode(b('%s:%s' % (self.user_id,
                                                              self.key))),
              'Content-Length': '0'
          }
  
  
  class HostingComDriver(VCloudNodeDriver):
  
      """
      vCloud node driver for Hosting.com
      """
      connectionCls = HostingComConnection
  
  
  class TerremarkConnection(VCloudConnection):
  
      """
      vCloud connection subclass for Terremark
      """
  
      host = "services.vcloudexpress.terremark.com"
  
  
  class TerremarkDriver(VCloudNodeDriver):
  
      """
      vCloud node driver for Terremark
      """
  
      connectionCls = TerremarkConnection
  
      def list_locations(self):
          return [NodeLocation(0, "Terremark Texas", 'US', self)]
  
  
  class VCloud_1_5_Connection(VCloudConnection):
  
      def _get_auth_headers(self):
          """Compatibility for using v1.5 API under vCloud Director 5.1"""
          return {
              'Authorization': "Basic %s" % base64.b64encode(
                  b('%s:%s' % (self.user_id, self.key))).decode('utf-8'),
              'Content-Length': '0',
              'Accept': 'application/*+xml;version=1.5'
          }
  
      def _get_auth_token(self):
          if not self.token:
              # Log In
              self.connection.request(method='POST', url='/api/sessions',
                                      headers=self._get_auth_headers())
  
              resp = self.connection.getresponse()
              headers = resp.headers
  
              # Set authorization token
              try:
                  self.token = headers['x-vcloud-authorization']
              except KeyError:
                  raise InvalidCredsError()
  
              # Get the URL of the Organization
              body = ET.XML(resp.text)
              self.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')
              )
  
              if self.proxy_url is not None:
                  self.connection.set_http_proxy(self.proxy_url)
              self.connection.request(method='GET', url=org_list_url,
                                      headers=self.add_default_headers({}))
              body = ET.XML(self.connection.getresponse().text)
              self.driver.org = get_url_path(
                  next((org for org in body.findall(fixxpath(body, 'Org'))
                       if org.get('name') == self.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 VCloud_5_5_Connection(VCloud_1_5_Connection):
      def add_default_headers(self, headers):
          headers['Accept'] = 'application/*+xml;version=5.5'
          headers['x-vcloud-authorization'] = self.token
          return headers
  
  
  class Instantiate_1_5_VAppXML(object):
  
      def __init__(self, name, template, network, vm_network=None,
                   vm_fence=None):
          self.name = name
          self.template = template
          self.network = network
          self.vm_network = vm_network
          self.vm_fence = vm_fence
          self._build_xmltree()
  
      def tostring(self):
          return ET.tostring(self.root)
  
      def _build_xmltree(self):
          self.root = self._make_instantiation_root()
  
          if self.network is not None:
              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):
          if self.vm_network is None:
              # Don't set a custom vApp VM network name
              parent.set('networkName', self.network.get('name'))
          else:
              # Set a custom vApp VM network name
              parent.set('networkName', self.vm_network)
          configuration = ET.SubElement(parent, 'Configuration')
          ET.SubElement(configuration, 'ParentNetwork',
                        {'href': self.network.get('href')})
  
          if self.vm_fence is None:
              fencemode = self.network.find(fixxpath(self.network,
                                            'Configuration/FenceMode')).text
          else:
              fencemode = self.vm_fence
          ET.SubElement(configuration, 'FenceMode').text = 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.STOPPED,
                        '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: ``str``
  
          :param vdcs: None, vDC or a list of vDCs to search in. If None all vDCs
                       will be searched.
          :type vdcs: :class:`Vdc`
  
          :return: node instance or None if not found
          :rtype: :class:`Node` or ``None``
          """
          if not vdcs:
              vdcs = self.vdcs
          if not getattr(vdcs, '__iter__', False):
              vdcs = [vdcs]
          for vdc in vdcs:
              res = self.connection.request(get_url_path(vdc.id))
              xpath = fixxpath(res.object, "ResourceEntities/ResourceEntity")
              entity_elems = res.object.findall(xpath)
              for entity_elem in entity_elems:
                  if entity_elem.get('type') == \
                          'application/vnd.vmware.vcloud.vApp+xml' and \
                          entity_elem.get('name') == node_name:
                      path = get_url_path(entity_elem.get('href'))
                      headers = {'Content-Type':
                                 'application/vnd.vmware.vcloud.vApp+xml'}
                      res = self.connection.request(path,
                                                    headers=headers)
                      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 == httplib.ACCEPTED
  
      def reboot_node(self, node):
          res = self.connection.request('%s/power/action/reset'
                                        % get_url_path(node.id),
                                        method='POST')
          if res.status in [httplib.ACCEPTED, httplib.NO_CONTENT]:
              self._wait_for_task_completion(res.object.get('href'))
              return True
          else:
              return False
  
      def ex_deploy_node(self, node, ex_force_customization=False):
          """
          Deploys existing node. Equal to vApp "start" operation.
  
          :param  node: The node to be deployed
          :type   node: :class:`Node`
  
          :param  ex_force_customization: Used to specify whether to force
                                          customization on deployment,
                                          if not set default value is False.
          :type   ex_force_customization: ``bool``
  
          :rtype: :class:`Node`
          """
          if ex_force_customization:
              vms = self._get_vm_elements(node.id)
              for vm in vms:
                  self._ex_deploy_node_or_vm(vm.get('href'),
                                             ex_force_customization=True)
          else:
              self._ex_deploy_node_or_vm(node.id)
  
          res = self.connection.request(get_url_path(node.id))
          return self._to_node(res.object)
  
      def _ex_deploy_node_or_vm(self, vapp_or_vm_path,
                                ex_force_customization=False):
          data = {'powerOn': 'true',
                  'forceCustomization': str(ex_force_customization).lower(),
                  'xmlns': 'http://www.vmware.com/vcloud/v1.5'}
          deploy_xml = ET.Element('DeployVAppParams', data)
          path = get_url_path(vapp_or_vm_path)
          headers = {
              'Content-Type':
              'application/vnd.vmware.vcloud.deployVAppParams+xml'
          }
          res = self.connection.request('%s/action/deploy' % path,
                                        data=ET.tostring(deploy_xml),
                                        method='POST',
                                        headers=headers)
          self._wait_for_task_completion(res.object.get('href'))
  
      def ex_undeploy_node(self, node):
          """
          Undeploys existing node. Equal to vApp "stop" operation.
  
          :param  node: The node to be deployed
          :type   node: :class:`Node`
  
          :rtype: :class:`Node`
          """
          data = {'xmlns': 'http://www.vmware.com/vcloud/v1.5'}
          undeploy_xml = ET.Element('UndeployVAppParams', data)
          undeploy_power_action_xml = ET.SubElement(undeploy_xml,
                                                    'UndeployPowerAction')
          undeploy_power_action_xml.text = 'shutdown'
  
          headers = {
              'Content-Type':
              'application/vnd.vmware.vcloud.undeployVAppParams+xml'
          }
  
          try:
              res = self.connection.request(
                  '%s/action/undeploy' % get_url_path(node.id),
                  data=ET.tostring(undeploy_xml),
                  method='POST',
                  headers=headers)
  
              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=headers)
              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_power_off_node(self, node):
          """
          Powers on all VMs under specified node. VMs need to be This operation
          is allowed only when the vApp/VM is powered on.
  
          :param  node: The node to be powered off
          :type   node: :class:`Node`
  
          :rtype: :class:`Node`
          """
          return self._perform_power_operation(node, 'powerOff')
  
      def ex_power_on_node(self, node):
          """
          Powers on all VMs under specified node. This operation is allowed
          only when the vApp/VM is powered off or suspended.
  
          :param  node: The node to be powered on
          :type   node: :class:`Node`
  
          :rtype: :class:`Node`
          """
          return self._perform_power_operation(node, 'powerOn')
  
      def ex_shutdown_node(self, node):
          """
          Shutdowns all VMs under specified node. This operation is allowed only
          when the vApp/VM is powered on.
  
          :param  node: The node to be shut down
          :type   node: :class:`Node`
  
          :rtype: :class:`Node`
          """
          return self._perform_power_operation(node, 'shutdown')
  
      def ex_suspend_node(self, node):
          """
          Suspends all VMs under specified node. This operation is allowed only
          when the vApp/VM is powered on.
  
          :param  node: The node to be suspended
          :type   node: :class:`Node`
  
          :rtype: :class:`Node`
          """
          return self._perform_power_operation(node, 'suspend')
  
      def _perform_power_operation(self, node, operation):
          res = self.connection.request(
              '%s/power/action/%s' % (get_url_path(node.id), operation),
              method='POST')
          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_get_control_access(self, node):
          """
          Returns the control access settings for specified node.
  
          :param  node: node to get the control access for
          :type   node: :class:`Node`
  
          :rtype: :class:`ControlAccess`
          """
          res = self.connection.request(
              '%s/controlAccess' % get_url_path(node.id))
          everyone_access_level = None
          is_shared_elem = res.object.find(
              fixxpath(res.object, "IsSharedToEveryone"))
          if is_shared_elem is not None and is_shared_elem.text == 'true':
              everyone_access_level = res.object.find(
                  fixxpath(res.object, "EveryoneAccessLevel")).text
  
          # Parse all subjects
          subjects = []
          xpath = fixxpath(res.object, "AccessSettings/AccessSetting")
          for elem in res.object.findall(xpath):
              access_level = elem.find(fixxpath(res.object, "AccessLevel")).text
              subject_elem = elem.find(fixxpath(res.object, "Subject"))
              if subject_elem.get('type') == \
                 'application/vnd.vmware.admin.group+xml':
                  subj_type = 'group'
              else:
                  subj_type = 'user'
  
              path = get_url_path(subject_elem.get('href'))
              res = self.connection.request(path)
              name = res.object.get('name')
              subject = Subject(type=subj_type,
                                name=name,
                                access_level=access_level,
                                id=subject_elem.get('href'))
              subjects.append(subject)
  
          return ControlAccess(node, everyone_access_level, subjects)
  
      def ex_set_control_access(self, node, control_access):
          """
          Sets control access for the specified node.
  
          :param  node: node
          :type   node: :class:`Node`
  
          :param  control_access: control access settings
          :type   control_access: :class:`ControlAccess`
  
          :rtype: ``None``
          """
          xml = ET.Element('ControlAccessParams',
                           {'xmlns': 'http://www.vmware.com/vcloud/v1.5'})
          shared_to_everyone = ET.SubElement(xml, 'IsSharedToEveryone')
          if control_access.everyone_access_level:
              shared_to_everyone.text = 'true'
              everyone_access_level = ET.SubElement(xml, 'EveryoneAccessLevel')
              everyone_access_level.text = control_access.everyone_access_level
          else:
              shared_to_everyone.text = 'false'
  
          # Set subjects
          if control_access.subjects:
              access_settings_elem = ET.SubElement(xml, 'AccessSettings')
          for subject in control_access.subjects:
              setting = ET.SubElement(access_settings_elem, 'AccessSetting')
              if subject.id:
                  href = subject.id
              else:
                  res = self.ex_query(type=subject.type, filter='name==' +
                                      subject.name)
                  if not res:
                      raise LibcloudError('Specified subject "%s %s" not found '
                                          % (subject.type, subject.name))
                  href = res[0]['href']
              ET.SubElement(setting, 'Subject', {'href': href})
              ET.SubElement(setting, 'AccessLevel').text = subject.access_level
  
          headers = {
              'Content-Type': 'application/vnd.vmware.vcloud.controlAccess+xml'
          }
          self.connection.request(
              '%s/action/controlAccess' % get_url_path(node.id),
              data=ET.tostring(xml),
              headers=headers,
              method='POST')
  
      def ex_get_metadata(self, node):
          """
          :param  node: node
          :type   node: :class:`Node`
  
          :return: dictionary mapping metadata keys to metadata values
          :rtype: dictionary mapping ``str`` to ``str``
          """
          res = self.connection.request('%s/metadata' % (get_url_path(node.id)))
          xpath = fixxpath(res.object, 'MetadataEntry')
          metadata_entries = res.object.findall(xpath)
          res_dict = {}
  
          for entry in metadata_entries:
              key = entry.findtext(fixxpath(res.object, 'Key'))
              value = entry.findtext(fixxpath(res.object, 'Value'))
              res_dict[key] = value
  
          return res_dict
  
      def ex_set_metadata_entry(self, node, key, value):
          """
          :param  node: node
          :type   node: :class:`Node`
  
          :param key: metadata key to be set
          :type key: ``str``
  
          :param value: metadata value to be set
          :type value: ``str``
  
          :rtype: ``None``
          """
          metadata_elem = ET.Element(
              'Metadata',
              {'xmlns': "http://www.vmware.com/vcloud/v1.5",
               'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance"}
          )
          entry = ET.SubElement(metadata_elem, 'MetadataEntry')
          key_elem = ET.SubElement(entry, 'Key')
          key_elem.text = key
          value_elem = ET.SubElement(entry, 'Value')
          value_elem.text = value
  
          # send it back to the server
          res = self.connection.request(
              '%s/metadata' % get_url_path(node.id),
              data=ET.tostring(metadata_elem),
              headers={
                  'Content-Type': 'application/vnd.vmware.vcloud.metadata+xml'
              },
              method='POST')
          self._wait_for_task_completion(res.object.get('href'))
  
      def ex_query(self, type, filter=None, page=1, page_size=100, sort_asc=None,
                   sort_desc=None):
          """
          Queries vCloud for specified type. See
          http://www.vmware.com/pdf/vcd_15_api_guide.pdf for details. Each
          element of the returned list is a dictionary with all attributes from
          the record.
  
          :param type: type to query (r.g. user, group, vApp etc.)
          :type  type: ``str``
  
          :param filter: filter expression (see documentation for syntax)
          :type  filter: ``str``
  
          :param page: page number
          :type  page: ``int``
  
          :param page_size: page size
          :type  page_size: ``int``
  
          :param sort_asc: sort in ascending order by specified field
          :type  sort_asc: ``str``
  
          :param sort_desc: sort in descending order by specified field
          :type  sort_desc: ``str``
  
          :rtype: ``list`` of dict
          """
          # This is a workaround for filter parameter encoding
          # the urllib encodes (name==Developers%20Only) into
          # %28name%3D%3DDevelopers%20Only%29) which is not accepted by vCloud
          params = {
              'type': type,
              'pageSize': page_size,
              'page': page,
          }
          if sort_asc:
              params['sortAsc'] = sort_asc
          if sort_desc:
              params['sortDesc'] = sort_desc
  
          url = '/api/query?' + urlencode(params)
          if filter:
              if not filter.startswith('('):
                  filter = '(' + filter + ')'
              url += '&filter=' + filter.replace(' ', '+')
  
          results = []
          res = self.connection.request(url)
          for elem in res.object:
              if not elem.tag.endswith('Link'):
                  result = elem.attrib
                  result['type'] = elem.tag.split('}')[1]
                  results.append(result)
          return results
  
      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.
  
  
          @inherits: :class:`NodeDriver.create_node`
  
          :keyword    image:  OS Image to boot on node. (required). Can be a
                              NodeImage or existing Node that will be cloned.
          :type       image:  :class:`NodeImage` or :class:`Node`
  
          :keyword    ex_network: Organisation's network name for attaching vApp
                                  VMs to.
          :type       ex_network: ``str``
  
          :keyword    ex_vdc: Name of organisation's virtual data center where
                              vApp VMs will be deployed.
          :type       ex_vdc: ``str``
  
          :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: ``list`` of ``str``
  
          :keyword    ex_vm_cpu: number of virtual CPUs/cores to allocate for
                                 each vApp VM.
          :type       ex_vm_cpu: ``int``
  
          :keyword    ex_vm_memory: amount of memory in MB to allocate for each
                                    vApp VM.
          :type       ex_vm_memory: ``int``
  
          :keyword    ex_vm_script: full path to file containing guest
                                    customisation script for each vApp VM.
                                    Useful for creating users & pushing out
                                    public SSH keys etc.
          :type       ex_vm_script: ``str``
  
          :keyword    ex_vm_network: Override default vApp VM network name.
                                     Useful for when you've imported an OVF
                                     originating from outside of the vCloud.
          :type       ex_vm_network: ``str``
  
          :keyword    ex_vm_fence: Fence mode for connecting the vApp VM network
                                   (ex_vm_network) to the parent
                                   organisation network (ex_network).
          :type       ex_vm_fence: ``str``
  
          :keyword    ex_vm_ipmode: IP address allocation mode for all vApp VM
                                    network connections.
          :type       ex_vm_ipmode: ``str``
  
          :keyword    ex_deploy: set to False if the node shouldn't be deployed
                                 (started) after creation
          :type       ex_deploy: ``bool``
  
          :keyword    ex_force_customization: Used to specify whether to force
                                              customization on deployment,
                                              if not set default value is False.
          :type       ex_force_customization: ``bool``
  
          :keyword    ex_clone_timeout: timeout in seconds for clone/instantiate
                                        VM operation.
                                        Cloning might be a time consuming
                                        operation especially when linked clones
                                        are disabled or VMs are created on
                                        different datastores.
                                        Overrides the default task completion
                                        value.
          :type       ex_clone_timeout: ``int``
  
          :keyword    ex_admin_password: set the node admin password explicitly.
          :type       ex_admin_password: ``str``
          """
          name = kwargs['name']
          image = kwargs['image']
          ex_vm_names = kwargs.get('ex_vm_names')
          ex_vm_cpu = kwargs.get('ex_vm_cpu')
          ex_vm_memory = kwargs.get('ex_vm_memory')
          ex_vm_script = kwargs.get('ex_vm_script')
          ex_vm_fence = kwargs.get('ex_vm_fence', None)
          ex_network = kwargs.get('ex_network', None)
          ex_vm_network = kwargs.get('ex_vm_network', None)
          ex_vm_ipmode = kwargs.get('ex_vm_ipmode', None)
          ex_deploy = kwargs.get('ex_deploy', True)
          ex_force_customization = kwargs.get('ex_force_customization', False)
          ex_vdc = kwargs.get('ex_vdc', None)
          ex_clone_timeout = kwargs.get('ex_clone_timeout',
                                        DEFAULT_TASK_COMPLETION_TIMEOUT)
          ex_admin_password = kwargs.get('ex_admin_password', None)
  
          self._validate_vm_names(ex_vm_names)
          self._validate_vm_cpu(ex_vm_cpu)
          self._validate_vm_memory(ex_vm_memory)
          self._validate_vm_fence(ex_vm_fence)
          self._validate_vm_ipmode(ex_vm_ipmode)
          ex_vm_script = self._validate_vm_script(ex_vm_script)
  
          # Some providers don't require a network link
          if ex_network:
              network_href = self._get_network_href(ex_network)
              network_elem = self.connection.request(
                  get_url_path(network_href)).object
          else:
              network_elem = None
  
          vdc = self._get_vdc(ex_vdc)
  
          if self._is_node(image):
              vapp_name, vapp_href = self._clone_node(name,
                                                      image,
                                                      vdc,
                                                      ex_clone_timeout)
          else:
              vapp_name, vapp_href = self._instantiate_node(name, image,
                                                            network_elem,
                                                            vdc, ex_vm_network,
                                                            ex_vm_fence,
                                                            ex_clone_timeout)
  
          self._change_vm_names(vapp_href, ex_vm_names)
          self._change_vm_cpu(vapp_href, ex_vm_cpu)
          self._change_vm_memory(vapp_href, ex_vm_memory)
          self._change_vm_script(vapp_href, ex_vm_script)
          self._change_vm_ipmode(vapp_href, ex_vm_ipmode)
  
          if ex_admin_password is not None:
              self.ex_change_vm_admin_password(vapp_href, ex_admin_password)
  
          # Power on the VM.
          if ex_deploy:
              res = self.connection.request(get_url_path(vapp_href))
              node = self._to_node(res.object)
              # Retry 3 times: when instantiating large number of VMs at the same
              # time some may fail on resource allocation
              retry = 3
              while True:
                  try:
                      self.ex_deploy_node(node, ex_force_customization)
                      break
                  except Exception:
                      if retry <= 0:
                          raise
                      retry -= 1
                      time.sleep(10)
  
          res = self.connection.request(get_url_path(vapp_href))
          node = self._to_node(res.object)
          return node
  
      def _instantiate_node(self, name, image, network_elem, vdc, vm_network,
                            vm_fence, instantiate_timeout):
          instantiate_xml = Instantiate_1_5_VAppXML(
              name=name,
              template=image.id,
              network=network_elem,
              vm_network=vm_network,
              vm_fence=vm_fence
          )
  
          # Instantiate VM and get identifier.
          headers = {
              'Content-Type':
              'application/vnd.vmware.vcloud.instantiateVAppTemplateParams+xml'
          }
          res = self.connection.request(
              '%s/action/instantiateVAppTemplate' % get_url_path(vdc.id),
              data=instantiate_xml.tostring(),
              method='POST',
              headers=headers
          )
          vapp_name = res.object.get('name')
          vapp_href = res.object.get('href')
  
          task_href = res.object.find(fixxpath(res.object, "Tasks/Task")).get(
              'href')
          self._wait_for_task_completion(task_href, instantiate_timeout)
          return vapp_name, vapp_href
  
      def _clone_node(self, name, sourceNode, vdc, clone_timeout):
          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})
  
          headers = {
              'Content-Type': 'application/vnd.vmware.vcloud.cloneVAppParams+xml'
          }
          res = self.connection.request(
              '%s/action/cloneVApp' % get_url_path(vdc.id),
              data=ET.tostring(clone_xml),
              method='POST',
              headers=headers
          )
          vapp_name = res.object.get('name')
          vapp_href = res.object.get('href')
  
          task_href = res.object.find(
              fixxpath(res.object, "Tasks/Task")).get('href')
          self._wait_for_task_completion(task_href, clone_timeout)
  
          res = self.connection.request(get_url_path(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'
  
              headers = {
                  'Content-Type':
                  'application/vnd.vmware.vcloud.networkConnectionSection+xml'
              }
              res = self.connection.request(
                  '%s/networkConnectionSection' % get_url_path(vm.get('href')),
                  data=ET.tostring(network_xml),
                  method='PUT',
                  headers=headers
              )
              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')))
  
              headers = {
                  'Content-Type':
                  'application/vnd.vmware.vcloud.networkConnectionSection+xml'
              }
              res = self.connection.request(
                  '%s/networkConnectionSection' % get_url_path(vm.get('href')),
                  data=ET.tostring(network_xml),
                  method='PUT',
                  headers=headers
              )
              self._wait_for_task_completion(res.object.get('href'))
  
          return vapp_name, vapp_href
  
      def ex_set_vm_cpu(self, vapp_or_vm_id, vm_cpu):
          """
          Sets the number of virtual CPUs for the specified VM or VMs under
          the vApp. If the vapp_or_vm_id param represents a link to an vApp
          all VMs that are attached to this vApp will be modified.
  
          Please ensure that hot-adding a virtual CPU is enabled for the
          powered on virtual machines. Otherwise use this method on undeployed
          vApp.
  
          :keyword    vapp_or_vm_id: vApp or VM ID that will be modified. If
                                     a vApp ID is used here all attached VMs
                                     will be modified
          :type       vapp_or_vm_id: ``str``
  
          :keyword    vm_cpu: number of virtual CPUs/cores to allocate for
                              specified VMs
          :type       vm_cpu: ``int``
  
          :rtype: ``None``
          """
          self._validate_vm_cpu(vm_cpu)
          self._change_vm_cpu(vapp_or_vm_id, vm_cpu)
  
      def ex_set_vm_memory(self, vapp_or_vm_id, vm_memory):
          """
          Sets the virtual memory in MB to allocate for the specified VM or
          VMs under the vApp. If the vapp_or_vm_id param represents a link
          to an vApp all VMs that are attached to this vApp will be modified.
  
          Please ensure that hot-change of virtual memory is enabled for the
          powered on virtual machines. Otherwise use this method on undeployed
          vApp.
  
          :keyword    vapp_or_vm_id: vApp or VM ID that will be modified. If
                                     a vApp ID is used here all attached VMs
                                     will be modified
          :type       vapp_or_vm_id: ``str``
  
          :keyword    vm_memory: virtual memory in MB to allocate for the
                                 specified VM or VMs
          :type       vm_memory: ``int``
  
          :rtype: ``None``
          """
          self._validate_vm_memory(vm_memory)
          self._change_vm_memory(vapp_or_vm_id, vm_memory)
  
      def ex_add_vm_disk(self, vapp_or_vm_id, vm_disk_size):
          """
          Adds a virtual disk to the specified VM or VMs under the vApp. If the
          vapp_or_vm_id param represents a link to an vApp all VMs that are
          attached to this vApp will be modified.
  
          :keyword    vapp_or_vm_id: vApp or VM ID that will be modified. If a
                                     vApp ID is used here all attached VMs
                                     will be modified
          :type       vapp_or_vm_id: ``str``
  
          :keyword    vm_disk_size: the disk capacity in GB that will be added
                                    to the specified VM or VMs
          :type       vm_disk_size: ``int``
  
          :rtype: ``None``
          """
          self._validate_vm_disk_size(vm_disk_size)
          self._add_vm_disk(vapp_or_vm_id, vm_disk_size)
  
      @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])$')  # NOQA
          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.')
  
      @staticmethod
      def _validate_vm_memory(vm_memory):
          if vm_memory is None:
              return
          elif vm_memory not in VIRTUAL_MEMORY_VALS:
              raise ValueError(
                  '%s is not a valid vApp VM memory value' % vm_memory)
  
      @staticmethod
      def _validate_vm_cpu(vm_cpu):
          if vm_cpu is None:
              return
          elif vm_cpu not in VIRTUAL_CPU_VALS_1_5:
              raise ValueError('%s is not a valid vApp VM CPU value' % vm_cpu)
  
      @staticmethod
      def _validate_vm_disk_size(vm_disk):
          if vm_disk is None:
              return
          elif int(vm_disk) < 0:
              raise ValueError('%s is not a valid vApp VM disk space value',
                               vm_disk)
  
      @staticmethod
      def _validate_vm_script(vm_script):
          if vm_script is None:
              return
          # Try to locate the script file
          if not os.path.isabs(vm_script):
              vm_script = os.path.expanduser(vm_script)
              vm_script = os.path.abspath(vm_script)
          if not os.path.isfile(vm_script):
              raise LibcloudError(
                  "%s the VM script file does not exist" % vm_script)
          try:
              open(vm_script).read()
          except:
              raise
          return vm_script
  
      @staticmethod
      def _validate_vm_fence(vm_fence):
          if vm_fence is None:
              return
          elif vm_fence not in FENCE_MODE_VALS_1_5:
              raise ValueError('%s is not a valid fencing mode value' % vm_fence)
  
      @staticmethod
      def _validate_vm_ipmode(vm_ipmode):
          if vm_ipmode is None:
              return
          elif vm_ipmode == 'MANUAL':
              raise NotImplementedError(
                  'MANUAL IP mode: The interface for supplying '
                  'IPAddress does not exist yet')
          elif vm_ipmode not in IP_MODE_VALS_1_5:
              raise ValueError(
                  '%s is not a valid IP address allocation mode value'
                  % vm_ipmode)
  
      def _change_vm_names(self, vapp_or_vm_id, vm_names):
          if vm_names is None:
              return
  
          vms = self._get_vm_elements(vapp_or_vm_id)
          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)
  
              headers = {
                  'Content-Type':
                  'application/vnd.vmware.vcloud.guestCustomizationSection+xml'
              }
              res = self.connection.request(
                  '%s/guestCustomizationSection' % get_url_path(vm.get('href')),
                  data=ET.tostring(res.object),
                  method='PUT',
                  headers=headers
              )
              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 _change_vm_cpu(self, vapp_or_vm_id, vm_cpu):
          if vm_cpu is None:
              return
  
          vms = self._get_vm_elements(vapp_or_vm_id)
          for vm in vms:
              # Get virtualHardwareSection/cpu section
              res = self.connection.request(
                  '%s/virtualHardwareSection/cpu' % get_url_path(vm.get('href')))
  
              # Update VirtualQuantity field
              xpath = ('{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/'
                       'CIM_ResourceAllocationSettingData}VirtualQuantity')
              res.object.find(xpath).text = str(vm_cpu)
  
              headers = {
                  'Content-Type': 'application/vnd.vmware.vcloud.rasdItem+xml'
              }
              res = self.connection.request(
                  '%s/virtualHardwareSection/cpu' % get_url_path(vm.get('href')),
                  data=ET.tostring(res.object),
                  method='PUT',
                  headers=headers
              )
              self._wait_for_task_completion(res.object.get('href'))
  
      def _change_vm_memory(self, vapp_or_vm_id, vm_memory):
          if vm_memory is None:
              return
  
          vms = self._get_vm_elements(vapp_or_vm_id)
          for vm in vms:
              # Get virtualHardwareSection/memory section
              res = self.connection.request(
                  '%s/virtualHardwareSection/memory' %
                  get_url_path(vm.get('href')))
  
              # Update VirtualQuantity field
              xpath = ('{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/'
                       'CIM_ResourceAllocationSettingData}VirtualQuantity')
              res.object.find(xpath).text = str(vm_memory)
  
              headers = {
                  'Content-Type': 'application/vnd.vmware.vcloud.rasdItem+xml'
              }
              res = self.connection.request(
                  '%s/virtualHardwareSection/memory' % get_url_path(
                      vm.get('href')),
                  data=ET.tostring(res.object),
                  method='PUT',
                  headers=headers
              )
              self._wait_for_task_completion(res.object.get('href'))
  
      def _add_vm_disk(self, vapp_or_vm_id, vm_disk):
          if vm_disk is None:
              return
  
          rasd_ns = ('{http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/'
                     'CIM_ResourceAllocationSettingData}')
  
          vms = self._get_vm_elements(vapp_or_vm_id)
          for vm in vms:
              # Get virtualHardwareSection/disks section
              res = self.connection.request(
                  '%s/virtualHardwareSection/disks' %
                  get_url_path(vm.get('href')))
  
              existing_ids = []
              new_disk = None
              for item in res.object.findall(fixxpath(res.object, 'Item')):
                  # Clean Items from unnecessary stuff
                  for elem in item:
                      if elem.tag == '%sInstanceID' % rasd_ns:
                          existing_ids.append(int(elem.text))
                      if elem.tag in ['%sAddressOnParent' % rasd_ns,
                                      '%sParent' % rasd_ns]:
                          item.remove(elem)
                  if item.find('%sHostResource' % rasd_ns) is not None:
                      new_disk = item
  
              new_disk = copy.deepcopy(new_disk)
              disk_id = max(existing_ids) + 1
              new_disk.find('%sInstanceID' % rasd_ns).text = str(disk_id)
              new_disk.find('%sElementName' %
                            rasd_ns).text = 'Hard Disk ' + str(disk_id)
              new_disk.find('%sHostResource' % rasd_ns).set(
                  fixxpath(new_disk, 'capacity'), str(int(vm_disk) * 1024))
              res.object.append(new_disk)
  
              headers = {
                  'Content-Type':
                  'application/vnd.vmware.vcloud.rasditemslist+xml'
              }
              res = self.connection.request(
                  '%s/virtualHardwareSection/disks' % get_url_path(
                      vm.get('href')),
                  data=ET.tostring(res.object),
                  method='PUT',
                  headers=headers
              )
              self._wait_for_task_completion(res.object.get('href'))
  
      def _change_vm_script(self, vapp_or_vm_id, vm_script):
          if vm_script is None:
              return
  
          vms = self._get_vm_elements(vapp_or_vm_id)
          try:
              script = open(vm_script).read()
          except:
              return
  
          # ElementTree escapes script characters automatically. Escape
          # requirements:
          # http://www.vmware.com/support/vcd/doc/rest-api-doc-1.5-html/types/
          # GuestCustomizationSectionType.html
          for vm in vms:
              # Get GuestCustomizationSection
              res = self.connection.request(
                  '%s/guestCustomizationSection' % get_url_path(vm.get('href')))
  
              # Attempt to update any existing CustomizationScript element
              try:
                  res.object.find(
                      fixxpath(res.object, 'CustomizationScript')).text = script
              except:
                  # CustomizationScript section does not exist, insert it just
                  # before ComputerName
                  for i, e in enumerate(res.object):
                      if e.tag == \
                              '{http://www.vmware.com/vcloud/v1.5}ComputerName':
                          break
                  e = ET.Element(
                      '{http://www.vmware.com/vcloud/v1.5}CustomizationScript')
                  e.text = script
                  res.object.insert(i, e)
  
              # Remove AdminPassword from customization section due to an API
              # quirk
              admin_pass = res.object.find(fixxpath(res.object, 'AdminPassword'))
              if admin_pass is not None:
                  res.object.remove(admin_pass)
  
              # Update VM's GuestCustomizationSection
              headers = {
                  'Content-Type':
                  'application/vnd.vmware.vcloud.guestCustomizationSection+xml'
              }
              res = self.connection.request(
                  '%s/guestCustomizationSection' % get_url_path(vm.get('href')),
                  data=ET.tostring(res.object),
                  method='PUT',
                  headers=headers
              )
              self._wait_for_task_completion(res.object.get('href'))
  
      def _change_vm_ipmode(self, vapp_or_vm_id, vm_ipmode):
          if vm_ipmode is None:
              return
  
          vms = self._get_vm_elements(vapp_or_vm_id)
  
          for vm in vms:
              res = self.connection.request(
                  '%s/networkConnectionSection' % get_url_path(vm.get('href')))
              net_conns = res.object.findall(
                  fixxpath(res.object, 'NetworkConnection'))
              for c in net_conns:
                  c.find(fixxpath(c, 'IpAddressAllocationMode')).text = vm_ipmode
  
              headers = {
                  'Content-Type':
                  'application/vnd.vmware.vcloud.networkConnectionSection+xml'
              }
  
              res = self.connection.request(
                  '%s/networkConnectionSection' % get_url_path(vm.get('href')),
                  data=ET.tostring(res.object),
                  method='PUT',
                  headers=headers
              )
              self._wait_for_task_completion(res.object.get('href'))
  
      def _update_or_insert_section(self, res, section, prev_section, text):
          try:
              res.object.find(
                  fixxpath(res.object, section)).text = text
          except:
              # "section" section does not exist, insert it just
              # before "prev_section"
              for i, e in enumerate(res.object):
                  tag = '{http://www.vmware.com/vcloud/v1.5}%s' % prev_section
                  if e.tag == tag:
                      break
              e = ET.Element(
                  '{http://www.vmware.com/vcloud/v1.5}%s' % section)
              e.text = text
              res.object.insert(i, e)
          return res
  
      def ex_change_vm_admin_password(self, vapp_or_vm_id, ex_admin_password):
          """
          Changes the admin (or root) password of VM or VMs under the vApp. If
          the vapp_or_vm_id param represents a link to an vApp all VMs that
          are attached to this vApp will be modified.
  
          :keyword    vapp_or_vm_id: vApp or VM ID that will be modified. If a
                                     vApp ID is used here all attached VMs
                                     will be modified
          :type       vapp_or_vm_id: ``str``
  
          :keyword    ex_admin_password: admin password to be used.
          :type       ex_admin_password: ``str``
  
          :rtype: ``None``
          """
          if ex_admin_password is None:
              return
  
          vms = self._get_vm_elements(vapp_or_vm_id)
          for vm in vms:
              # Get GuestCustomizationSection
              res = self.connection.request(
                  '%s/guestCustomizationSection' % get_url_path(vm.get('href')))
  
              headers = {
                  'Content-Type':
                  'application/vnd.vmware.vcloud.guestCustomizationSection+xml'
              }
  
              # Fix API quirk.
              # If AdminAutoLogonEnabled==False the guestCustomizationSection
              # must have AdminAutoLogonCount==0, even though
              # it might have AdminAutoLogonCount==1 when requesting it for
              # the first time.
              auto_logon = res.object.find(
                  fixxpath(res.object, "AdminAutoLogonEnabled"))
              if auto_logon is not None and auto_logon.text == 'false':
                  self._update_or_insert_section(res,
                                                 "AdminAutoLogonCount",
                                                 "ResetPasswordRequired",
                                                 '0')
  
              # If we are establishing a password we do not want it
              # to be automatically chosen.
              self._update_or_insert_section(res,
                                             'AdminPasswordAuto',
                                             'AdminPassword',
                                             'false')
  
              # API does not allow to set AdminPassword if
              # AdminPasswordEnabled is not enabled.
              self._update_or_insert_section(res,
                                             'AdminPasswordEnabled',
                                             'AdminPasswordAuto',
                                             'true')
  
              self._update_or_insert_section(res,
                                             'AdminPassword',
                                             'AdminAutoLogonEnabled',
                                             ex_admin_password)
  
              res = self.connection.request(
                  '%s/guestCustomizationSection' % get_url_path(vm.get('href')),
                  data=ET.tostring(res.object),
                  method='PUT',
                  headers=headers
              )
              self._wait_for_task_completion(res.object.get('href'))
  
      def _get_network_href(self, network_name):
          network_href = None
  
          # Find the organisation's network href
          res = self.connection.request(self.org)
          links = res.object.findall(fixxpath(res.object, 'Link'))
          for l in links:
              if l.attrib['type'] == \
                      'application/vnd.vmware.vcloud.orgNetwork+xml' \
                      and l.attrib['name'] == network_name:
                  network_href = l.attrib['href']
  
          if network_href is None:
              raise ValueError(
                  '%s is not a valid organisation network name' % network_name)
          else:
              return network_href
  
      def _get_vm_elements(self, vapp_or_vm_id):
          res = self.connection.request(get_url_path(vapp_or_vm_id))
          if res.object.tag.endswith('VApp'):
              vms = res.object.findall(fixxpath(res.object, 'Children/Vm'))
          elif res.object.tag.endswith('Vm'):
              vms = [res.object]
          else:
              raise ValueError(
                  'Specified ID value is not a valid VApp or Vm identifier.')
          return vms
  
      def _is_node(self, node_or_image):
          return isinstance(node_or_image, Node)
  
      def _to_node(self, node_elm):
          # Parse snapshots and VMs as extra
          if node_elm.find(fixxpath(node_elm, "SnapshotSection")) is None:
              snapshots = None
          else:
              snapshots = []
              for snapshot_elem in node_elm.findall(
                      fixxpath(node_elm, 'SnapshotSection/Snapshot')):
                  snapshots.append({
                      "created": snapshot_elem.get("created"),
                      "poweredOn": snapshot_elem.get("poweredOn"),
                      "size": snapshot_elem.get("size"),
                  })
  
          vms = []
          for vm_elem in node_elm.findall(fixxpath(node_elm, 'Children/Vm')):
              public_ips = []
              private_ips = []
  
              xpath = fixxpath(vm_elem,
                               'NetworkConnectionSection/NetworkConnection')
              for connection in vm_elem.findall(xpath):
                  ip = connection.find(fixxpath(connection, "IpAddress"))
                  if ip is not None:
                      private_ips.append(ip.text)
                  external_ip = connection.find(
                      fixxpath(connection, "ExternalIpAddress"))
                  if external_ip is not None:
                      public_ips.append(external_ip.text)
                  elif ip is not None:
                      public_ips.append(ip.text)
  
              xpath = ('{http://schemas.dmtf.org/ovf/envelope/1}'
                       'OperatingSystemSection')
              os_type_elem = vm_elem.find(xpath)
              if os_type_elem is not None:
                  os_type = os_type_elem.get(
                      '{http://www.vmware.com/schema/ovf}osType')
              else:
                  os_type = None
              vm = {
                  'id': vm_elem.get('href'),
                  'name': vm_elem.get('name'),
                  'state': self.NODE_STATE_MAP[vm_elem.get('status')],
                  'public_ips': public_ips,
                  'private_ips': private_ips,
                  'os_type': os_type
              }
              vms.append(vm)
  
          # Take the node IP addresses from all VMs
          public_ips = []
          private_ips = []
          for vm in vms:
              public_ips.extend(vm['public_ips'])
              private_ips.extend(vm['private_ips'])
  
          # Find vDC
          vdc_id = next(link.get('href') for link
                        in node_elm.findall(fixxpath(node_elm, 'Link'))
                        if link.get('type') ==
                        'application/vnd.vmware.vcloud.vdc+xml')
          vdc = next(vdc for vdc in self.vdcs if vdc.id == vdc_id)
  
          extra = {'vdc': vdc.name, 'vms': vms}
          if snapshots is not None:
              extra['snapshots'] = snapshots
  
          node = Node(id=node_elm.get('href'),
                      name=node_elm.get('name'),
                      state=self.NODE_STATE_MAP[node_elm.get('status')],
                      public_ips=public_ips,
                      private_ips=private_ips,
                      driver=self.connection.driver,
                      extra=extra)
          return node
  
      def _to_vdc(self, vdc_elm):
  
          def get_capacity_values(capacity_elm):
              if capacity_elm is None:
                  return None
              limit = int(capacity_elm.findtext(fixxpath(capacity_elm, 'Limit')))
              used = int(capacity_elm.findtext(fixxpath(capacity_elm, 'Used')))
              units = capacity_elm.findtext(fixxpath(capacity_elm, 'Units'))
              return Capacity(limit, used, units)
  
          cpu = get_capacity_values(
              vdc_elm.find(fixxpath(vdc_elm, 'ComputeCapacity/Cpu')))
          memory = get_capacity_values(
              vdc_elm.find(fixxpath(vdc_elm, 'ComputeCapacity/Memory')))
          storage = get_capacity_values(
              vdc_elm.find(fixxpath(vdc_elm, 'StorageCapacity')))
  
          return Vdc(id=vdc_elm.get('href'),
                     name=vdc_elm.get('name'),
                     driver=self,
                     allocation_model=vdc_elm.findtext(
                         fixxpath(vdc_elm, 'AllocationModel')),
                     cpu=cpu,
                     memory=memory,
                     storage=storage)
  
  
  class VCloud_5_1_NodeDriver(VCloud_1_5_NodeDriver):
  
      @staticmethod
      def _validate_vm_memory(vm_memory):
          if vm_memory is None:
              return None
          elif (vm_memory % 4) != 0:
              # The vcd 5.1 virtual machine memory size must be a multiple of 4
              # MB
              raise ValueError(
                  '%s is not a valid vApp VM memory value' % (vm_memory))
  
  
  class VCloud_5_5_NodeDriver(VCloud_5_1_NodeDriver):
      '''Use 5.5 Connection class to explicitly set 5.5 for the version in
      Accept headers
      '''
      connectionCls = VCloud_5_5_Connection
  
      def ex_create_snapshot(self, node):
          """
          Creates new snapshot of a virtual machine or of all
          the virtual machines in a vApp. Prior to creation of the new
          snapshots, any existing user created snapshots associated
          with the virtual machines are removed.
  
          :param  node: node
          :type   node: :class:`Node`
  
          :rtype: :class:`Node`
          """
          snapshot_xml = ET.Element(
              "CreateSnapshotParams",
              {'memory': 'true',
               'name': 'name',
               'quiesce': 'true',
               'xmlns': "http://www.vmware.com/vcloud/v1.5",
               'xmlns:xsi': "http://www.w3.org/2001/XMLSchema-instance"}
          )
          ET.SubElement(snapshot_xml, 'Description').text = 'Description'
          content_type = 'application/vnd.vmware.vcloud.createSnapshotParams+xml'
          headers = {
              'Content-Type': content_type
          }
          return self._perform_snapshot_operation(node,
                                                  "createSnapshot",
                                                  snapshot_xml,
                                                  headers)
  
      def ex_remove_snapshots(self, node):
          """
          Removes all user created snapshots for a vApp or virtual machine.
  
          :param  node: node
          :type   node: :class:`Node`
  
          :rtype: :class:`Node`
          """
          return self._perform_snapshot_operation(node,
                                                  "removeAllSnapshots",
                                                  None,
                                                  None)
  
      def ex_revert_to_snapshot(self, node):
          """
          Reverts a vApp or virtual machine to the current snapshot, if any.
  
          :param  node: node
          :type   node: :class:`Node`
  
          :rtype: :class:`Node`
          """
          return self._perform_snapshot_operation(node,
                                                  "revertToCurrentSnapshot",
                                                  None,
                                                  None)
  
      def _perform_snapshot_operation(self, node, operation, xml_data, headers):
          res = self.connection.request(
              '%s/action/%s' % (get_url_path(node.id), operation),
              data=ET.tostring(xml_data) if xml_data else None,
              method='POST',
              headers=headers)
          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_acquire_mks_ticket(self, vapp_or_vm_id, vm_num=0):
          """
          Retrieve a mks ticket that you can use to gain access to the console
          of a running VM. If successful, returns a dict with the following
          keys:
  
            - host: host (or proxy) through which the console connection
                  is made
            - vmx: a reference to the VMX file of the VM for which this
                 ticket was issued
            - ticket: screen ticket to use to authenticate the client
            - port: host port to be used for console access
  
          :param  vapp_or_vm_id: vApp or VM ID you want to connect to.
          :type   vapp_or_vm_id: ``str``
  
          :param  vm_num: If a vApp ID is provided, vm_num is position in the
                  vApp VM list of the VM you want to get a screen ticket.
                  Default is 0.
          :type   vm_num: ``int``
  
          :rtype: ``dict``
          """
          vm = self._get_vm_elements(vapp_or_vm_id)[vm_num]
          try:
              res = self.connection.request('%s/screen/action/acquireMksTicket' %
                                            (get_url_path(vm.get('href'))),
                                            method='POST')
              output = {
                  "host": res.object.find(fixxpath(res.object, 'Host')).text,
                  "vmx": res.object.find(fixxpath(res.object, 'Vmx')).text,
                  "ticket": res.object.find(fixxpath(res.object, 'Ticket')).text,
                  "port": res.object.find(fixxpath(res.object, 'Port')).text,
              }
              return output
          except:
              return None
  