text: # Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
  # Copyright (c) 2012 VMware, Inc.
  # Copyright (c) 2011 Citrix Systems, Inc.
  # Copyright 2011 OpenStack Foundation
  #
  #    Licensed 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.
  
  """
  A connection to the VMware vCenter platform.
  """
  
  import os
  import re
  
  import os_resource_classes as orc
  from oslo_log import log as logging
  from oslo_utils import excutils
  from oslo_utils import units
  from oslo_utils import versionutils as v_utils
  from oslo_vmware import api
  from oslo_vmware import exceptions as vexc
  from oslo_vmware import pbm
  from oslo_vmware import vim
  from oslo_vmware import vim_util
  
  from nova.compute import power_state
  from nova.compute import task_states
  from nova.compute import utils as compute_utils
  import nova.conf
  from nova import context as nova_context
  from nova import exception
  from nova.i18n import _
  from nova import objects
  import nova.privsep.path
  from nova.virt import driver
  from nova.virt.vmwareapi import constants
  from nova.virt.vmwareapi import ds_util
  from nova.virt.vmwareapi import error_util
  from nova.virt.vmwareapi import host
  from nova.virt.vmwareapi import vim_util as nova_vim_util
  from nova.virt.vmwareapi import vm_util
  from nova.virt.vmwareapi import vmops
  from nova.virt.vmwareapi import volumeops
  
  LOG = logging.getLogger(__name__)
  
  CONF = nova.conf.CONF
  
  TIME_BETWEEN_API_CALL_RETRIES = 1.0
  MAX_CONSOLE_BYTES = 100 * units.Ki
  
  
  class VMwareVCDriver(driver.ComputeDriver):
      """The VC host connection object."""
  
      capabilities = {
          "has_imagecache": True,
          "supports_evacuate": False,
          "supports_migrate_to_same_host": True,
          "supports_attach_interface": True,
          "supports_multiattach": False,
          "supports_trusted_certs": False,
          "supports_pcpus": False,
          "supports_accelerators": False,
  
          # Image type support flags
          "supports_image_type_aki": False,
          "supports_image_type_ami": False,
          "supports_image_type_ari": False,
          "supports_image_type_iso": True,
          "supports_image_type_qcow2": False,
          "supports_image_type_raw": False,
          "supports_image_type_vdi": False,
          "supports_image_type_vhd": False,
          "supports_image_type_vhdx": False,
          "supports_image_type_vmdk": True,
          "supports_image_type_ploop": False,
      }
  
      # Legacy nodename is of the form: <mo id>(<cluster name>)
      # e.g. domain-26(TestCluster)
      # We assume <mo id> consists of alphanumeric, _ and -.
      # We assume cluster name is everything between the first ( and the last ).
      # We pull out <mo id> for re-use.
      LEGACY_NODENAME = re.compile(r'([\w-]+)\(.+\)')
  
      # The vCenter driver includes API that acts on ESX hosts or groups
      # of ESX hosts in clusters or non-cluster logical-groupings.
      #
      # vCenter is not a hypervisor itself, it works with multiple
      # hypervisor host machines and their guests. This fact can
      # subtly alter how vSphere and OpenStack interoperate.
  
      def __init__(self, virtapi, scheme="https"):
          super(VMwareVCDriver, self).__init__(virtapi)
  
          if (CONF.vmware.host_ip is None or
              CONF.vmware.host_username is None or
              CONF.vmware.host_password is None):
              raise Exception(_("Must specify host_ip, host_username and "
                                "host_password to use vmwareapi.VMwareVCDriver"))
  
          self._datastore_regex = None
          if CONF.vmware.datastore_regex:
              try:
                  self._datastore_regex = re.compile(CONF.vmware.datastore_regex)
              except re.error:
                  raise exception.InvalidInput(reason=
                      _("Invalid Regular Expression %s")
                      % CONF.vmware.datastore_regex)
  
          self._session = VMwareAPISession(scheme=scheme)
  
          self._check_min_version()
  
          # Update the PBM location if necessary
          if CONF.vmware.pbm_enabled:
              self._update_pbm_location()
  
          self._validate_configuration()
          self._cluster_name = CONF.vmware.cluster_name
          self._cluster_ref = vm_util.get_cluster_ref_by_name(self._session,
                                                              self._cluster_name)
          if self._cluster_ref is None:
              raise exception.NotFound(_("The specified cluster '%s' was not "
                                         "found in vCenter")
                                       % self._cluster_name)
          self._vcenter_uuid = self._get_vcenter_uuid()
          self._nodename = \
              self._create_nodename(vim_util.get_moref_value(self._cluster_ref))
          self._volumeops = volumeops.VMwareVolumeOps(self._session,
                                                      self._cluster_ref)
          self._vmops = vmops.VMwareVMOps(self._session,
                                          virtapi,
                                          self._volumeops,
                                          self._cluster_ref,
                                          datastore_regex=self._datastore_regex)
          self._vc_state = host.VCState(self._session,
                                        self._nodename,
                                        self._cluster_ref,
                                        self._datastore_regex)
  
          # Register the OpenStack extension
          self._register_openstack_extension()
  
      def _check_min_version(self):
          min_version = v_utils.convert_version_to_int(constants.MIN_VC_VERSION)
          next_min_ver = v_utils.convert_version_to_int(
              constants.NEXT_MIN_VC_VERSION)
          vc_version = vim_util.get_vc_version(self._session)
          LOG.info("VMware vCenter version: %s", vc_version)
          if v_utils.convert_version_to_int(vc_version) < min_version:
              raise exception.NovaException(
                  _('Detected vCenter version %(version)s. Nova requires VMware '
                    'vCenter version %(min_version)s or greater.') % {
                        'version': vc_version,
                        'min_version': constants.MIN_VC_VERSION})
          elif v_utils.convert_version_to_int(vc_version) < next_min_ver:
              LOG.warning('Running Nova with a VMware vCenter version less '
                          'than %(version)s is deprecated. The required '
                          'minimum version of vCenter will be raised to '
                          '%(version)s in the 16.0.0 release.',
                          {'version': constants.NEXT_MIN_VC_VERSION})
  
      def _update_pbm_location(self):
          if CONF.vmware.pbm_wsdl_location:
              pbm_wsdl_loc = CONF.vmware.pbm_wsdl_location
          else:
              version = vim_util.get_vc_version(self._session)
              pbm_wsdl_loc = pbm.get_pbm_wsdl_location(version)
          self._session.pbm_wsdl_loc_set(pbm_wsdl_loc)
  
      def _validate_configuration(self):
          if CONF.vmware.pbm_enabled:
              if not CONF.vmware.pbm_default_policy:
                  raise error_util.PbmDefaultPolicyUnspecified()
              if not pbm.get_profile_id_by_name(
                              self._session,
                              CONF.vmware.pbm_default_policy):
                  raise error_util.PbmDefaultPolicyDoesNotExist()
              if CONF.vmware.datastore_regex:
                  LOG.warning("datastore_regex is ignored when PBM is enabled")
                  self._datastore_regex = None
  
      def init_host(self, host):
          vim = self._session.vim
          if vim is None:
              self._session._create_session()
  
      def cleanup_host(self, host):
          self._session.logout()
  
      def _register_openstack_extension(self):
          # Register an 'OpenStack' extension in vCenter
          os_extension = self._session._call_method(vim_util, 'find_extension',
                                                    constants.EXTENSION_KEY)
          if os_extension is None:
              try:
                  self._session._call_method(vim_util, 'register_extension',
                                             constants.EXTENSION_KEY,
                                             constants.EXTENSION_TYPE_INSTANCE)
                  LOG.info('Registered extension %s with vCenter',
                           constants.EXTENSION_KEY)
              except vexc.VimFaultException as e:
                  with excutils.save_and_reraise_exception() as ctx:
                      if 'InvalidArgument' in e.fault_list:
                          LOG.debug('Extension %s already exists.',
                                    constants.EXTENSION_KEY)
                          ctx.reraise = False
          else:
              LOG.debug('Extension %s already exists.', constants.EXTENSION_KEY)
  
      def cleanup(self, context, instance, network_info, block_device_info=None,
                  destroy_disks=True, migrate_data=None, destroy_vifs=True,
                  destroy_secrets=True):
          """Cleanup after instance being destroyed by Hypervisor."""
          pass
  
      def resume_state_on_host_boot(self, context, instance, network_info,
                                    block_device_info=None):
          """resume guest state when a host is booted."""
          # Check if the instance is running already and avoid doing
          # anything if it is.
          state = vm_util.get_vm_state(self._session, instance)
          ignored_states = [power_state.RUNNING, power_state.SUSPENDED]
          if state in ignored_states:
              return
          # Instance is not up and could be in an unknown state.
          # Be as absolute as possible about getting it back into
          # a known and running state.
          self.reboot(context, instance, network_info, 'hard',
                      block_device_info)
  
      def list_instance_uuids(self):
          """List VM instance UUIDs."""
          return self._vmops.list_instances()
  
      def list_instances(self):
          """List VM instances from the single compute node."""
          return self._vmops.list_instances()
  
      def migrate_disk_and_power_off(self, context, instance, dest,
                                     flavor, network_info,
                                     block_device_info=None,
                                     timeout=0, retry_interval=0):
          """Transfers the disk of a running instance in multiple phases, turning
          off the instance before the end.
          """
          # TODO(PhilDay): Add support for timeout (clean shutdown)
          return self._vmops.migrate_disk_and_power_off(context, instance,
                                                        dest, flavor)
  
      def confirm_migration(self, context, migration, instance, network_info):
          """Confirms a resize, destroying the source VM."""
          self._vmops.confirm_migration(migration, instance, network_info)
  
      def finish_revert_migration(self, context, instance, network_info,
                                  migration, block_device_info=None,
                                  power_on=True):
          """Finish reverting a resize, powering back on the instance."""
          self._vmops.finish_revert_migration(context, instance, network_info,
                                              block_device_info, power_on)
  
      def finish_migration(self, context, migration, instance, disk_info,
                           network_info, image_meta, resize_instance,
                           allocations, block_device_info=None, power_on=True):
          """Completes a resize, turning on the migrated instance."""
          self._vmops.finish_migration(context, migration, instance, disk_info,
                                       network_info, image_meta, resize_instance,
                                       block_device_info, power_on)
  
      def pre_live_migration(self, context, instance, block_device_info,
                             network_info, disk_info, migrate_data):
          return migrate_data
  
      def post_live_migration_at_source(self, context, instance, network_info):
          pass
  
      def post_live_migration_at_destination(self, context, instance,
                                             network_info,
                                             block_migration=False,
                                             block_device_info=None):
          pass
  
      def cleanup_live_migration_destination_check(self, context,
                                                   dest_check_data):
          pass
  
      def live_migration(self, context, instance, dest,
                         post_method, recover_method, block_migration=False,
                         migrate_data=None):
          """Live migration of an instance to another host."""
          self._vmops.live_migration(context, instance, dest, post_method,
                                     recover_method, block_migration,
                                     migrate_data)
  
      def check_can_live_migrate_source(self, context, instance,
                                        dest_check_data, block_device_info=None):
          cluster_name = dest_check_data.cluster_name
          cluster_ref = vm_util.get_cluster_ref_by_name(self._session,
                                                        cluster_name)
          if cluster_ref is None:
              msg = (_("Cannot find destination cluster %s for live migration") %
                     cluster_name)
              raise exception.MigrationPreCheckError(reason=msg)
          res_pool_ref = vm_util.get_res_pool_ref(self._session, cluster_ref)
          if res_pool_ref is None:
              msg = _("Cannot find destination resource pool for live migration")
              raise exception.MigrationPreCheckError(reason=msg)
          return dest_check_data
  
      def check_can_live_migrate_destination(self, context, instance,
                                             src_compute_info, dst_compute_info,
                                             block_migration=False,
                                             disk_over_commit=False):
          # the information that we need for the destination compute node
          # is the name of its cluster and datastore regex
          data = objects.VMwareLiveMigrateData()
          data.cluster_name = CONF.vmware.cluster_name
          data.datastore_regex = CONF.vmware.datastore_regex
          return data
  
      def rollback_live_migration_at_destination(self, context, instance,
                                                 network_info,
                                                 block_device_info,
                                                 destroy_disks=True,
                                                 migrate_data=None):
          """Clean up destination node after a failed live migration."""
          self.destroy(context, instance, network_info, block_device_info)
  
      def get_instance_disk_info(self, instance, block_device_info=None):
          pass
  
      def get_vnc_console(self, context, instance):
          """Return link to instance's VNC console using vCenter logic."""
          # vCenter does not actually run the VNC service
          # itself. You must talk to the VNC host underneath vCenter.
          return self._vmops.get_vnc_console(instance)
  
      def get_mks_console(self, context, instance):
          return self._vmops.get_mks_console(instance)
  
      def get_console_output(self, context, instance):
          if not CONF.vmware.serial_log_dir:
              LOG.error("The 'serial_log_dir' config option is not set!")
              return
          fname = instance.uuid.replace('-', '')
          path = os.path.join(CONF.vmware.serial_log_dir, fname)
          if not os.path.exists(path):
              LOG.warning('The console log is missing. Check your VSPC '
                          'configuration', instance=instance)
              return b""
          read_log_data, remaining = nova.privsep.path.last_bytes(
              path, MAX_CONSOLE_BYTES)
          return read_log_data
  
      def _get_vcenter_uuid(self):
          """Retrieves the vCenter UUID."""
  
          about = self._session._call_method(nova_vim_util, 'get_about_info')
          return about.instanceUuid
  
      def _create_nodename(self, mo_id):
          """Return a nodename which uniquely describes a cluster.
  
          The name will be of the form:
            <mo id>.<vcenter uuid>
          e.g.
            domain-26.9d51f082-58a4-4449-beed-6fd205a5726b
          """
  
          return '%s.%s' % (mo_id, self._vcenter_uuid)
  
      def _get_available_resources(self, host_stats):
          return {'vcpus': host_stats['vcpus'],
                 'memory_mb': host_stats['host_memory_total'],
                 'local_gb': host_stats['disk_total'],
                 'vcpus_used': 0,
                 'memory_mb_used': host_stats['host_memory_total'] -
                                   host_stats['host_memory_free'],
                 'local_gb_used': host_stats['disk_used'],
                 'hypervisor_type': host_stats['hypervisor_type'],
                 'hypervisor_version': host_stats['hypervisor_version'],
                 'hypervisor_hostname': host_stats['hypervisor_hostname'],
                  # The VMWare driver manages multiple hosts, so there are
                  # likely many different CPU models in use. As such it is
                  # impossible to provide any meaningful info on the CPU
                  # model of the "host"
                 'cpu_info': None,
                 'supported_instances': host_stats['supported_instances'],
                 'numa_topology': None,
                 }
  
      def get_available_resource(self, nodename):
          """Retrieve resource info.
  
          This method is called when nova-compute launches, and
          as part of a periodic task.
  
          :returns: dictionary describing resources
  
          """
          host_stats = self._vc_state.get_host_stats(refresh=True)
          stats_dict = self._get_available_resources(host_stats)
          return stats_dict
  
      def get_available_nodes(self, refresh=False):
          """Returns nodenames of all nodes managed by the compute service.
  
          This driver supports only one compute node.
          """
          return [self._nodename]
  
      def update_provider_tree(self, provider_tree, nodename, allocations=None):
          """Update a ProviderTree object with current resource provider,
          inventory information and CPU traits.
  
          :param nova.compute.provider_tree.ProviderTree provider_tree:
              A nova.compute.provider_tree.ProviderTree object representing all
              the providers in the tree associated with the compute node, and any
              sharing providers (those with the ``MISC_SHARES_VIA_AGGREGATE``
              trait) associated via aggregate with any of those providers (but
              not *their* tree- or aggregate-associated providers), as currently
              known by placement.
          :param nodename:
              String name of the compute node (i.e.
              ComputeNode.hypervisor_hostname) for which the caller is requesting
              updated provider information.
          :param allocations:
              Dict of allocation data of the form:
                { $CONSUMER_UUID: {
                      # The shape of each "allocations" dict below is identical
                      # to the return from GET /allocations/{consumer_uuid}
                      "allocations": {
                          $RP_UUID: {
                              "generation": $RP_GEN,
                              "resources": {
                                  $RESOURCE_CLASS: $AMOUNT,
                                  ...
                              },
                          },
                          ...
                      },
                      "project_id": $PROJ_ID,
                      "user_id": $USER_ID,
                      "consumer_generation": $CONSUMER_GEN,
                  },
                  ...
                }
              If None, and the method determines that any inventory needs to be
              moved (from one provider to another and/or to a different resource
              class), the ReshapeNeeded exception must be raised. Otherwise, this
              dict must be edited in place to indicate the desired final state of
              allocations.
          :raises ReshapeNeeded: If allocations is None and any inventory needs
              to be moved from one provider to another and/or to a different
              resource class. At this time the VMware driver does not reshape.
          :raises: ReshapeFailed if the requested tree reshape fails for
              whatever reason.
          """
          # NOTE(cdent): This is a side-effecty method, we are changing the
          # the provider tree in place (on purpose).
          inv = provider_tree.data(nodename).inventory
          ratios = self._get_allocation_ratios(inv)
          stats = vm_util.get_stats_from_cluster(self._session,
                                                 self._cluster_ref)
          datastores = ds_util.get_available_datastores(self._session,
                                                        self._cluster_ref,
                                                        self._datastore_regex)
          total_disk_capacity = sum([ds.capacity for ds in datastores])
          max_free_space = max([ds.freespace for ds in datastores])
          reserved_disk_gb = compute_utils.convert_mb_to_ceil_gb(
              CONF.reserved_host_disk_mb)
          result = {
              orc.VCPU: {
                  'total': stats['cpu']['vcpus'],
                  'reserved': CONF.reserved_host_cpus,
                  'min_unit': 1,
                  'max_unit': stats['cpu']['max_vcpus_per_host'],
                  'step_size': 1,
                  'allocation_ratio': ratios[orc.VCPU],
              },
              orc.MEMORY_MB: {
                  'total': stats['mem']['total'],
                  'reserved': CONF.reserved_host_memory_mb,
                  'min_unit': 1,
                  'max_unit': stats['mem']['max_mem_mb_per_host'],
                  'step_size': 1,
                  'allocation_ratio': ratios[orc.MEMORY_MB],
              },
          }
  
          # If a sharing DISK_GB provider exists in the provider tree, then our
          # storage is shared, and we should not report the DISK_GB inventory in
          # the compute node provider.
          # TODO(cdent): We don't do this yet, in part because of the issues
          # in bug #1784020, but also because we can represent all datastores
          # as shared providers and should do once update_provider_tree is
          # working well.
          if provider_tree.has_sharing_provider(orc.DISK_GB):
              LOG.debug('Ignoring sharing provider - see bug #1784020')
          result[orc.DISK_GB] = {
              'total': total_disk_capacity // units.Gi,
              'reserved': reserved_disk_gb,
              'min_unit': 1,
              'max_unit': max_free_space // units.Gi,
              'step_size': 1,
              'allocation_ratio': ratios[orc.DISK_GB],
          }
  
          provider_tree.update_inventory(nodename, result)
  
          # TODO(cdent): Here is where additional functionality would be added.
          # In the libvirt driver this is where nested GPUs are reported and
          # where cpu traits are added. In the vmware world, this is where we
          # would add nested providers representing tenant VDC and similar.
  
      def spawn(self, context, instance, image_meta, injected_files,
                admin_password, allocations, network_info=None,
                block_device_info=None, power_on=True, accel_info=None):
          """Create VM instance."""
          self._vmops.spawn(context, instance, image_meta, injected_files,
                            admin_password, network_info, block_device_info)
  
      def attach_volume(self, context, connection_info, instance, mountpoint,
                        disk_bus=None, device_type=None, encryption=None):
          """Attach volume storage to VM instance."""
          return self._volumeops.attach_volume(connection_info, instance)
  
      def detach_volume(self, context, connection_info, instance, mountpoint,
                        encryption=None):
          """Detach volume storage to VM instance."""
          # NOTE(claudiub): if context parameter is to be used in the future,
          # the _detach_instance_volumes method will have to be updated as well.
          return self._volumeops.detach_volume(connection_info, instance)
  
      def get_volume_connector(self, instance):
          """Return volume connector information."""
          return self._volumeops.get_volume_connector(instance)
  
      def get_host_ip_addr(self):
          """Returns the IP address of the vCenter host."""
          return CONF.vmware.host_ip
  
      def snapshot(self, context, instance, image_id, update_task_state):
          """Create snapshot from a running VM instance."""
          self._vmops.snapshot(context, instance, image_id, update_task_state)
  
      def reboot(self, context, instance, network_info, reboot_type,
                 block_device_info=None, bad_volumes_callback=None,
                 accel_info=None):
          """Reboot VM instance."""
          self._vmops.reboot(instance, network_info, reboot_type)
  
      def _detach_instance_volumes(self, instance, block_device_info):
          # We need to detach attached volumes
          block_device_mapping = driver.block_device_info_get_mapping(
              block_device_info)
          if block_device_mapping:
              # Certain disk types, for example 'IDE' do not support hot
              # plugging. Hence we need to power off the instance and update
              # the instance state.
              self._vmops.power_off(instance)
              for disk in block_device_mapping:
                  connection_info = disk['connection_info']
                  try:
                      # NOTE(claudiub): Passing None as the context, as it is
                      # not currently used.
                      self.detach_volume(None, connection_info, instance,
                                         disk.get('device_name'))
                  except exception.DiskNotFound:
                      LOG.warning('The volume %s does not exist!',
                                  disk.get('device_name'),
                                  instance=instance)
                  except Exception as e:
                      with excutils.save_and_reraise_exception():
                          LOG.error("Failed to detach %(device_name)s. "
                                    "Exception: %(exc)s",
                                    {'device_name': disk.get('device_name'),
                                     'exc': e},
                                    instance=instance)
  
      def destroy(self, context, instance, network_info, block_device_info=None,
                  destroy_disks=True, destroy_secrets=True):
          """Destroy VM instance."""
  
          # Destroy gets triggered when Resource Claim in resource_tracker
          # is not successful. When resource claim is not successful,
          # node is not set in instance. Perform destroy only if node is set
          if not instance.node:
              return
  
          # A resize uses the same instance on the VC. We do not delete that
          # VM in the event of a revert
          if instance.task_state == task_states.RESIZE_REVERTING:
              return
  
          # We need to detach attached volumes
          if block_device_info is not None:
              try:
                  self._detach_instance_volumes(instance, block_device_info)
              except (vexc.ManagedObjectNotFoundException,
                      exception.InstanceNotFound):
                  LOG.warning('Instance does not exists. Proceeding to '
                              'delete instance properties on datastore',
                              instance=instance)
          self._vmops.destroy(instance, destroy_disks)
  
      def pause(self, instance):
          """Pause VM instance."""
          self._vmops.pause(instance)
  
      def unpause(self, instance):
          """Unpause paused VM instance."""
          self._vmops.unpause(instance)
  
      def suspend(self, context, instance):
          """Suspend the specified instance."""
          self._vmops.suspend(instance)
  
      def resume(self, context, instance, network_info, block_device_info=None):
          """Resume the suspended VM instance."""
          self._vmops.resume(instance)
  
      def rescue(self, context, instance, network_info, image_meta,
                 rescue_password, block_device_info):
          """Rescue the specified instance."""
          self._vmops.rescue(context, instance, network_info, image_meta)
  
      def unrescue(
          self,
          context: nova_context.RequestContext,
          instance: 'objects.Instance',
      ):
          """Unrescue the specified instance."""
          self._vmops.unrescue(instance)
  
      def power_off(self, instance, timeout=0, retry_interval=0):
          """Power off the specified instance."""
          self._vmops.power_off(instance, timeout, retry_interval)
  
      def power_on(self, context, instance, network_info,
                   block_device_info=None, accel_info=None):
          """Power on the specified instance."""
          self._vmops.power_on(instance)
  
      def poll_rebooting_instances(self, timeout, instances):
          """Poll for rebooting instances."""
          self._vmops.poll_rebooting_instances(timeout, instances)
  
      def get_info(self, instance, use_cache=True):
          """Return info about the VM instance."""
          return self._vmops.get_info(instance)
  
      def get_diagnostics(self, instance):
          """Return data about VM diagnostics."""
          return self._vmops.get_diagnostics(instance)
  
      def get_instance_diagnostics(self, instance):
          """Return data about VM diagnostics."""
          return self._vmops.get_instance_diagnostics(instance)
  
      def host_power_action(self, action):
          """Host operations not supported by VC driver.
  
          This needs to override the ESX driver implementation.
          """
          raise NotImplementedError()
  
      def host_maintenance_mode(self, host, mode):
          """Host operations not supported by VC driver.
  
          This needs to override the ESX driver implementation.
          """
          raise NotImplementedError()
  
      def set_host_enabled(self, enabled):
          """Host operations not supported by VC driver.
  
          This needs to override the ESX driver implementation.
          """
          raise NotImplementedError()
  
      def get_host_uptime(self):
          """Host uptime operation not supported by VC driver."""
  
          msg = _("Multiple hosts may be managed by the VMWare "
                  "vCenter driver; therefore we do not return "
                  "uptime for just one host.")
          raise NotImplementedError(msg)
  
      def inject_network_info(self, instance, nw_info):
          """inject network info for specified instance."""
          self._vmops.inject_network_info(instance, nw_info)
  
      def manage_image_cache(self, context, all_instances):
          """Manage the local cache of images."""
          self._vmops.manage_image_cache(context, all_instances)
  
      def instance_exists(self, instance):
          """Efficient override of base instance_exists method."""
          return self._vmops.instance_exists(instance)
  
      def attach_interface(self, context, instance, image_meta, vif):
          """Attach an interface to the instance."""
          self._vmops.attach_interface(context, instance, image_meta, vif)
  
      def detach_interface(self, context, instance, vif):
          """Detach an interface from the instance."""
          self._vmops.detach_interface(context, instance, vif)
  
  
  class VMwareAPISession(api.VMwareAPISession):
      """Sets up a session with the VC/ESX host and handles all
      the calls made to the host.
      """
  
      def __init__(self, host_ip=CONF.vmware.host_ip,
                   host_port=CONF.vmware.host_port,
                   username=CONF.vmware.host_username,
                   password=CONF.vmware.host_password,
                   retry_count=CONF.vmware.api_retry_count,
                   scheme="https",
                   cacert=CONF.vmware.ca_file,
                   insecure=CONF.vmware.insecure,
                   pool_size=CONF.vmware.connection_pool_size):
          super(VMwareAPISession, self).__init__(
                  host=host_ip,
                  port=host_port,
                  server_username=username,
                  server_password=password,
                  api_retry_count=retry_count,
                  task_poll_interval=CONF.vmware.task_poll_interval,
                  scheme=scheme,
                  create_session=True,
                  cacert=cacert,
                  insecure=insecure,
                  pool_size=pool_size)
  
      def _is_vim_object(self, module):
          """Check if the module is a VIM Object instance."""
          return isinstance(module, vim.Vim)
  
      def _call_method(self, module, method, *args, **kwargs):
          """Calls a method within the module specified with
          args provided.
          """
          if not self._is_vim_object(module):
              return self.invoke_api(module, method, self.vim, *args, **kwargs)
          else:
              return self.invoke_api(module, method, *args, **kwargs)
  
      def _wait_for_task(self, task_ref):
          """Return a Deferred that will give the result of the given task.
          The task is polled until it completes.
          """
          return self.wait_for_task(task_ref)
  