text: # Copyright (c) 2010 Cloud.com, Inc
  # Copyright (c) 2012 Cloudbase Solutions Srl
  #
  #    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 Hyper-V Nova Compute driver.
  """
  
  import functools
  import platform
  import sys
  
  from nova import context as nova_context
  from nova import exception
  from nova.image import glance
  from nova import objects
  from nova.virt import driver
  from os_win import exceptions as os_win_exc
  from os_win import utilsfactory
  from oslo_log import log as logging
  import six
  
  from compute_hyperv.nova import coordination
  from compute_hyperv.nova import eventhandler
  from compute_hyperv.nova import hostops
  from compute_hyperv.nova import imagecache
  from compute_hyperv.nova import livemigrationops
  from compute_hyperv.nova import migrationops
  from compute_hyperv.nova import pathutils
  from compute_hyperv.nova import rdpconsoleops
  from compute_hyperv.nova import serialconsoleops
  from compute_hyperv.nova import snapshotops
  from compute_hyperv.nova import vmops
  from compute_hyperv.nova import volumeops
  
  
  LOG = logging.getLogger(__name__)
  
  
  def convert_exceptions(function, exception_map):
      expected_exceptions = tuple(exception_map.keys())
  
      @functools.wraps(function)
      def wrapper(*args, **kwargs):
          try:
              return function(*args, **kwargs)
          except expected_exceptions as ex:
              raised_exception = exception_map.get(type(ex))
              if not raised_exception:
                  # exception might be a subclass of an expected exception.
                  for expected in expected_exceptions:
                      if isinstance(ex, expected):
                          raised_exception = exception_map[expected]
                          break
  
              exc_info = sys.exc_info()
              # NOTE(claudiub): Python 3 raises the exception object given as
              # the second argument in six.reraise.
              # The original message will be maintained by passing the original
              # exception.
              exc = raised_exception(six.text_type(exc_info[1]))
              six.reraise(raised_exception, exc, exc_info[2])
      return wrapper
  
  
  def decorate_all_methods(decorator, *args, **kwargs):
      def decorate(cls):
          for attr in cls.__dict__:
              class_member = getattr(cls, attr)
              if callable(class_member):
                  setattr(cls, attr, decorator(class_member, *args, **kwargs))
          return cls
  
      return decorate
  
  
  exception_conversion_map = {
      # expected_exception: converted_exception
      os_win_exc.OSWinException: exception.NovaException,
      os_win_exc.HyperVVMNotFoundException: exception.InstanceNotFound,
  }
  
  # NOTE(claudiub): the purpose of the decorator below is to prevent any
  # os_win exceptions (subclasses of OSWinException) to leak outside of the
  # HyperVDriver.
  
  
  @decorate_all_methods(convert_exceptions, exception_conversion_map)
  class HyperVDriver(driver.ComputeDriver):
      capabilities = {
          "has_imagecache": True,
          "supports_evacuate": True,
          "supports_migrate_to_same_host": False,
          "supports_attach_interface": True,
          "supports_device_tagging": True,
          "supports_tagged_attach_interface": True,
          "supports_tagged_attach_volume": True,
          "supports_extend_volume": True,
          "supports_multiattach": False,
          "supports_trusted_certs": True,
  
          # Supported image types
          "supports_image_type_vhd": True,
          "supports_image_type_vhdx": True,
      }
  
      use_coordination = False
  
      def __init__(self, virtapi):
          # check if the current version of Windows is supported before any
          # further driver initialisation.
          self._check_minimum_windows_version()
  
          # We'll initialize coordination as early as possible, avoiding
          # the risk of using locks before the mechanism is enabled.
          if self.use_coordination:
              coordination.COORDINATOR.start()
  
          super(HyperVDriver, self).__init__(virtapi)
  
          self._hostops = hostops.HostOps()
          self._volumeops = volumeops.VolumeOps()
          self._vmops = vmops.VMOps(virtapi)
          self._snapshotops = snapshotops.SnapshotOps()
          self._livemigrationops = livemigrationops.LiveMigrationOps()
          self._migrationops = migrationops.MigrationOps()
          self._rdpconsoleops = rdpconsoleops.RDPConsoleOps()
          self._serialconsoleops = serialconsoleops.SerialConsoleOps()
          self._imagecache = imagecache.ImageCache()
          self._image_api = glance.API()
          self._pathutils = pathutils.PathUtils()
          self._event_handler = eventhandler.InstanceEventHandler()
  
      def _check_minimum_windows_version(self):
          hostutils = utilsfactory.get_hostutils()
          if not hostutils.check_min_windows_version(6, 2):
              # the version is of Windows is older than Windows Server 2012 R2.
              # Log an error, letting users know that this version is not
              # supported any longer.
              LOG.error('You are running nova-compute on an unsupported '
                        'version of Windows (older than Windows / Hyper-V '
                        'Server 2012). The support for this version of '
                        'Windows has been removed in Mitaka.')
              raise exception.HypervisorTooOld(version='6.2')
          elif not hostutils.check_min_windows_version(6, 3):
              # TODO(claudiub): replace the warning with an exception in Rocky.
              LOG.warning('You are running nova-compute on Windows / Hyper-V '
                          'Server 2012. The support for this version of Windows '
                          'has been deprecated In Queens, and will be removed '
                          'in Rocky.')
  
      @property
      def need_legacy_block_device_info(self):
          return False
  
      def init_host(self, host):
          self._serialconsoleops.start_console_handlers()
  
          self._set_event_handler_callbacks()
          self._event_handler.start_listener()
  
          instance_path = self._pathutils.get_instances_dir()
          self._pathutils.check_create_dir(instance_path)
  
      def _set_event_handler_callbacks(self):
          # Subclasses may override this.
          self._event_handler.add_callback(self.emit_event)
          self._event_handler.add_callback(
              self._vmops.instance_state_change_callback)
  
      def list_instance_uuids(self):
          return self._vmops.list_instance_uuids()
  
      def list_instances(self):
          return self._vmops.list_instances()
  
      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):
          image_meta = self._recreate_image_meta(context, instance, image_meta)
          self._vmops.spawn(context, instance, image_meta, injected_files,
                            admin_password, network_info, block_device_info,
                            power_on)
  
      def reboot(self, context, instance, network_info, reboot_type,
                 block_device_info=None, bad_volumes_callback=None,
                 accel_info=None):
          self._vmops.reboot(instance, network_info, reboot_type)
  
      def destroy(self, context, instance, network_info, block_device_info=None,
                  destroy_disks=True):
          self._vmops.destroy(instance, network_info, block_device_info,
                              destroy_disks)
  
      def cleanup(self, context, instance, network_info, block_device_info=None,
                  destroy_disks=True, migrate_data=None, destroy_vifs=True):
          """Cleanup after instance being destroyed by Hypervisor."""
          self.unplug_vifs(instance, network_info)
  
      def get_info(self, instance, use_cache=True):
          return self._vmops.get_info(instance)
  
      def attach_volume(self, context, connection_info, instance, mountpoint,
                        disk_bus=None, device_type=None, encryption=None):
          self._volumeops.attach_volume(context,
                                        connection_info,
                                        instance,
                                        update_device_metadata=True)
  
      def detach_volume(self, context, connection_info, instance, mountpoint,
                        encryption=None):
          context = nova_context.get_admin_context()
          # The nova compute manager only updates the device metadata in
          # case of tagged devices. We're including untagged devices as well.
          self._volumeops.detach_volume(context,
                                        connection_info,
                                        instance,
                                        update_device_metadata=True)
  
      def extend_volume(self, connection_info, instance, requested_size):
          self._volumeops.extend_volume(connection_info)
  
      def get_volume_connector(self, instance):
          return self._volumeops.get_volume_connector()
  
      def get_available_resource(self, nodename):
          return self._hostops.get_available_resource()
  
      def get_available_nodes(self, refresh=False):
          return [platform.node()]
  
      def host_power_action(self, action):
          return self._hostops.host_power_action(action)
  
      def snapshot(self, context, instance, image_id, update_task_state):
          self._snapshotops.snapshot(context, instance, image_id,
                                     update_task_state)
  
      def volume_snapshot_create(self, context, instance, volume_id,
                                 create_info):
          self._volumeops.volume_snapshot_create(context, instance, volume_id,
                                                 create_info)
  
      def volume_snapshot_delete(self, context, instance, volume_id,
                                 snapshot_id, delete_info):
          self._volumeops.volume_snapshot_delete(context, instance, volume_id,
                                                 snapshot_id, delete_info)
  
      def pause(self, instance):
          self._vmops.pause(instance)
  
      def unpause(self, instance):
          self._vmops.unpause(instance)
  
      def suspend(self, context, instance):
          self._vmops.suspend(instance)
  
      def resume(self, context, instance, network_info, block_device_info=None):
          self._vmops.resume(instance)
  
      def power_off(self, instance, timeout=0, retry_interval=0):
          self._vmops.power_off(instance, timeout, retry_interval)
  
      def power_on(self, context, instance, network_info,
                   block_device_info=None, accel_info=None):
          self._vmops.power_on(instance, block_device_info, network_info)
  
      def resume_state_on_host_boot(self, context, instance, network_info,
                                    block_device_info=None):
          """Resume guest state when a host is booted."""
          self._vmops.resume_state_on_host_boot(context, instance, network_info,
                                                block_device_info)
  
      def live_migration(self, context, instance, dest, post_method,
                         recover_method, block_migration=False,
                         migrate_data=None):
          self._livemigrationops.live_migration(context, instance, dest,
                                                post_method, recover_method,
                                                block_migration, migrate_data)
  
      def rollback_live_migration_at_destination(self, context, instance,
                                                 network_info,
                                                 block_device_info,
                                                 destroy_disks=True,
                                                 migrate_data=None):
          self.destroy(context, instance, network_info, block_device_info,
                       destroy_disks=destroy_disks)
  
      def pre_live_migration(self, context, instance, block_device_info,
                             network_info, disk_info, migrate_data):
          self._livemigrationops.pre_live_migration(context, instance,
                                                    block_device_info,
                                                    network_info)
          return migrate_data
  
      def post_live_migration(self, context, instance, block_device_info,
                              migrate_data=None):
          self._livemigrationops.post_live_migration(context, instance,
                                                     block_device_info,
                                                     migrate_data)
  
      def post_live_migration_at_source(self, context, instance, network_info):
          """Unplug VIFs from networks at source."""
          self._vmops.unplug_vifs(instance, network_info)
  
      def post_live_migration_at_destination(self, context, instance,
                                             network_info,
                                             block_migration=False,
                                             block_device_info=None):
          self._livemigrationops.post_live_migration_at_destination(
              context,
              instance,
              network_info,
              block_migration)
  
      def check_can_live_migrate_destination(self, context, instance,
                                             src_compute_info, dst_compute_info,
                                             block_migration=False,
                                             disk_over_commit=False):
          return self._livemigrationops.check_can_live_migrate_destination(
              context, instance, src_compute_info, dst_compute_info,
              block_migration, disk_over_commit)
  
      def cleanup_live_migration_destination_check(self, context,
                                                   dest_check_data):
          self._livemigrationops.cleanup_live_migration_destination_check(
              context, dest_check_data)
  
      def check_can_live_migrate_source(self, context, instance,
                                        dest_check_data, block_device_info=None):
          return self._livemigrationops.check_can_live_migrate_source(
              context, instance, dest_check_data)
  
      def get_instance_disk_info(self, instance, block_device_info=None):
          pass
  
      def plug_vifs(self, instance, network_info):
          """Plug VIFs into networks."""
          self._vmops.plug_vifs(instance, network_info)
  
      def unplug_vifs(self, instance, network_info):
          """Unplug VIFs from networks."""
          self._vmops.unplug_vifs(instance, network_info)
  
      def ensure_filtering_rules_for_instance(self, instance, network_info):
          LOG.debug("ensure_filtering_rules_for_instance called",
                    instance=instance)
  
      def unfilter_instance(self, instance, network_info):
          LOG.debug("unfilter_instance called", instance=instance)
  
      def migrate_disk_and_power_off(self, context, instance, dest,
                                     flavor, network_info,
                                     block_device_info=None,
                                     timeout=0, retry_interval=0):
          return self._migrationops.migrate_disk_and_power_off(context,
                                                               instance, dest,
                                                               flavor,
                                                               network_info,
                                                               block_device_info,
                                                               timeout,
                                                               retry_interval)
  
      def confirm_migration(self, context, migration, instance, network_info):
          self._migrationops.confirm_migration(context, migration,
                                               instance, network_info)
  
      def finish_revert_migration(self, context, instance, network_info,
                                  migration, block_device_info=None,
                                  power_on=True):
          self._migrationops.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):
          image_meta = self._recreate_image_meta(context, instance, image_meta)
          self._migrationops.finish_migration(context, migration, instance,
                                              disk_info, network_info,
                                              image_meta, resize_instance,
                                              block_device_info, power_on)
  
      def get_host_ip_addr(self):
          return self._hostops.get_host_ip_addr()
  
      def get_host_uptime(self):
          return self._hostops.get_host_uptime()
  
      def get_rdp_console(self, context, instance):
          return self._rdpconsoleops.get_rdp_console(instance)
  
      def get_serial_console(self, context, instance):
          return self._serialconsoleops.get_serial_console(instance.name)
  
      def get_console_output(self, context, instance):
          return self._serialconsoleops.get_console_output(instance.name)
  
      def manage_image_cache(self, context, all_instances):
          self._imagecache.update(context, all_instances)
  
      def cache_image(self, context, image_id):
          image_path, fetched = self._imagecache.cache_image(
              context, image_id)
          return fetched
  
      def attach_interface(self, context, instance, image_meta, vif):
          self._vmops.attach_interface(context, instance, vif)
  
      def detach_interface(self, context, instance, vif):
          # The device metadata gets updated outside the driver.
          return self._vmops.detach_interface(instance, vif)
  
      def rescue(self, context, instance, network_info, image_meta,
                 rescue_password, block_device_info):
          image_meta = self._recreate_image_meta(context, instance, image_meta)
          self._vmops.rescue_instance(context, instance, network_info,
                                      image_meta, rescue_password)
  
      def unrescue(
          self,
          context: nova_context.RequestContext,
          instance: 'objects.Instance',
      ):
          self._vmops.unrescue_instance(instance)
  
      def host_maintenance_mode(self, host, mode):
          return self._hostops.host_maintenance_mode(host, mode)
  
      def _recreate_image_meta(self, context, instance, image_meta):
          # TODO(claudiub): Cleanup this method. instance.system_metadata might
          # already contain all the image metadata properties we need anyways.
          if image_meta.obj_attr_is_set("id"):
              image_ref = image_meta.id
          else:
              image_ref = instance.system_metadata['image_base_image_ref']
  
          if image_ref:
              image_meta = self._image_api.get(context, image_ref)
          else:
              # boot from volume does not have an image_ref.
              image_meta = image_meta.obj_to_primitive()['nova_object.data']
              image_meta['properties'] = {k.replace('image_', '', 1): v for k, v
                                          in instance.system_metadata.items()}
          image_meta["id"] = image_ref
          return image_meta
  
      def check_instance_shared_storage_local(self, context, instance):
          """Check if instance files located on shared storage.
  
          This runs check on the destination host, and then calls
          back to the source host to check the results.
  
          :param context: security context
          :param instance: nova.objects.instance.Instance object
          :returns: A dict containing the tempfile info.
          """
          return self._pathutils.check_instance_shared_storage_local(instance)
  
      def check_instance_shared_storage_remote(self, context, data):
          return self._pathutils.check_instance_shared_storage_remote(data)
  
      def check_instance_shared_storage_cleanup(self, context, data):
          return self._pathutils.check_instance_shared_storage_cleanup(data)
  
      def update_provider_tree(self, provider_tree, nodename, allocations=None):
          inventory = provider_tree.data(nodename).inventory
          alloc_ratios = self._get_allocation_ratios(inventory)
  
          self._hostops.update_provider_tree(
              provider_tree, nodename, alloc_ratios, allocations)
  