text: # Copyright 2013 Cloudbase Solutions Srl
  # All Rights Reserved.
  #
  #    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.
  
  import platform
  
  from oslo_log import log as logging
  
  from os_win._i18n import _
  from os_win import exceptions
  from os_win.utils import _wqlutils
  from os_win.utils.compute import migrationutils
  from os_win.utils.compute import vmutils
  
  LOG = logging.getLogger(__name__)
  
  
  class LiveMigrationUtils(migrationutils.MigrationUtils):
      _STORAGE_ALLOC_SETTING_DATA_CLASS = 'Msvm_StorageAllocationSettingData'
      _CIM_RES_ALLOC_SETTING_DATA_CLASS = 'CIM_ResourceAllocationSettingData'
  
      _MIGRATION_TYPE_VIRTUAL_SYSTEM = 32768
      _MIGRATION_TYPE_VIRTUAL_SYSTEM_AND_STORAGE = 32771
      _MIGRATION_TYPE_STAGED = 32770
  
      def __init__(self):
          super(LiveMigrationUtils, self).__init__()
  
      def _get_conn_v2(self, host='localhost'):
          try:
              return self._get_wmi_obj(self._wmi_namespace % host,
                                       compatibility_mode=True)
          except exceptions.x_wmi as ex:
              LOG.exception('Get version 2 connection error')
              if ex.com_error.hresult == -2147217394:
                  msg = (_('Live migration is not supported on target host "%s"')
                         % host)
              elif ex.com_error.hresult == -2147023174:
                  msg = (_('Target live migration host "%s" is unreachable')
                         % host)
              else:
                  msg = _('Live migration failed: %r') % ex
              raise exceptions.HyperVException(msg)
  
      def check_live_migration_config(self):
          migration_svc = (
              self._compat_conn.Msvm_VirtualSystemMigrationService()[0])
          vsmssd = (
              self._compat_conn.Msvm_VirtualSystemMigrationServiceSettingData())
          vsmssd = vsmssd[0]
          if not vsmssd.EnableVirtualSystemMigration:
              raise exceptions.HyperVException(
                  _('Live migration is not enabled on this host'))
          if not migration_svc.MigrationServiceListenerIPAddressList:
              raise exceptions.HyperVException(
                  _('Live migration networks are not configured on this host'))
  
      def _get_vm(self, conn_v2, vm_name):
          vms = conn_v2.Msvm_ComputerSystem(ElementName=vm_name)
          n = len(vms)
          if not n:
              raise exceptions.HyperVVMNotFoundException(vm_name=vm_name)
          elif n > 1:
              raise exceptions.HyperVException(_('Duplicate VM name found: %s')
                                               % vm_name)
          return vms[0]
  
      def _create_planned_vm(self, conn_v2_local, conn_v2_remote,
                             vm, ip_addr_list, dest_host):
          # Staged
          vsmsd = conn_v2_remote.Msvm_VirtualSystemMigrationSettingData(
              MigrationType=self._MIGRATION_TYPE_STAGED)[0]
          vsmsd.DestinationIPAddressList = ip_addr_list
          migration_setting_data = vsmsd.GetText_(1)
  
          LOG.debug("Creating planned VM for VM: %s", vm.ElementName)
          migr_svc = conn_v2_remote.Msvm_VirtualSystemMigrationService()[0]
          (job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost(
              ComputerSystem=vm.path_(),
              DestinationHost=dest_host,
              MigrationSettingData=migration_setting_data)
          self._jobutils.check_ret_val(ret_val, job_path)
  
          return conn_v2_local.Msvm_PlannedComputerSystem(Name=vm.Name)[0]
  
      def _get_disk_data(self, vm_name, vmutils_remote, disk_path_mapping):
          disk_paths = {}
          phys_disk_resources = vmutils_remote.get_vm_disks(vm_name)[1]
  
          for disk in phys_disk_resources:
              rasd_rel_path = disk.path().RelPath
              # We set this when volumes are attached.
              serial = disk.ElementName
              disk_paths[rasd_rel_path] = disk_path_mapping[serial]
          return disk_paths
  
      def _update_planned_vm_disk_resources(self, conn_v2_local,
                                            planned_vm, vm_name,
                                            disk_paths_remote):
          updated_resource_setting_data = []
          sasds = _wqlutils.get_element_associated_class(
              self._compat_conn, self._CIM_RES_ALLOC_SETTING_DATA_CLASS,
              element_uuid=planned_vm.Name)
          for sasd in sasds:
              if (sasd.ResourceType == 17 and sasd.ResourceSubType ==
                      "Microsoft:Hyper-V:Physical Disk Drive" and
                      sasd.HostResource):
                  # Replace the local disk target with the correct remote one
                  old_disk_path = sasd.HostResource[0]
                  new_disk_path = disk_paths_remote.pop(sasd.path().RelPath)
  
                  LOG.debug("Replacing host resource "
                            "%(old_disk_path)s with "
                            "%(new_disk_path)s on planned VM %(vm_name)s",
                            {'old_disk_path': old_disk_path,
                             'new_disk_path': new_disk_path,
                             'vm_name': vm_name})
                  sasd.HostResource = [new_disk_path]
                  updated_resource_setting_data.append(sasd.GetText_(1))
  
          LOG.debug("Updating remote planned VM disk paths for VM: %s",
                    vm_name)
          vsmsvc = conn_v2_local.Msvm_VirtualSystemManagementService()[0]
          (res_settings, job_path, ret_val) = vsmsvc.ModifyResourceSettings(
              ResourceSettings=updated_resource_setting_data)
          self._jobutils.check_ret_val(ret_val, job_path)
  
      def _get_vhd_setting_data(self, vm):
          new_resource_setting_data = []
          sasds = _wqlutils.get_element_associated_class(
              self._compat_conn, self._STORAGE_ALLOC_SETTING_DATA_CLASS,
              element_uuid=vm.Name)
          for sasd in sasds:
              if (sasd.ResourceType == 31 and sasd.ResourceSubType ==
                      "Microsoft:Hyper-V:Virtual Hard Disk"):
                  new_resource_setting_data.append(sasd.GetText_(1))
          return new_resource_setting_data
  
      def _live_migrate_vm(self, conn_v2_local, vm, planned_vm, rmt_ip_addr_list,
                           new_resource_setting_data, dest_host, migration_type):
          # VirtualSystemAndStorage
          vsmsd = conn_v2_local.Msvm_VirtualSystemMigrationSettingData(
              MigrationType=migration_type)[0]
          vsmsd.DestinationIPAddressList = rmt_ip_addr_list
          if planned_vm:
              vsmsd.DestinationPlannedVirtualSystemId = planned_vm.Name
          migration_setting_data = vsmsd.GetText_(1)
  
          migr_svc = conn_v2_local.Msvm_VirtualSystemMigrationService()[0]
  
          LOG.debug("Starting live migration for VM: %s", vm.ElementName)
          (job_path, ret_val) = migr_svc.MigrateVirtualSystemToHost(
              ComputerSystem=vm.path_(),
              DestinationHost=dest_host,
              MigrationSettingData=migration_setting_data,
              NewResourceSettingData=new_resource_setting_data)
          self._jobutils.check_ret_val(ret_val, job_path)
  
      def _get_ip_address_list(self, conn_v2, hostname):
          LOG.debug("Getting live migration networks for host: %s",
                    hostname)
          migr_svc_rmt = conn_v2.Msvm_VirtualSystemMigrationService()[0]
          return migr_svc_rmt.MigrationServiceListenerIPAddressList
  
      def live_migrate_vm(self, vm_name, dest_host, migrate_disks=True):
          self.check_live_migration_config()
  
          conn_v2_remote = self._get_conn_v2(dest_host)
  
          vm = self._get_vm(self._compat_conn, vm_name)
  
          rmt_ip_addr_list = self._get_ip_address_list(conn_v2_remote,
                                                       dest_host)
  
          planned_vm = self._get_planned_vm(vm_name, conn_v2_remote)
  
          if migrate_disks:
              new_resource_setting_data = self._get_vhd_setting_data(vm)
              migration_type = self._MIGRATION_TYPE_VIRTUAL_SYSTEM_AND_STORAGE
          else:
              new_resource_setting_data = None
              migration_type = self._MIGRATION_TYPE_VIRTUAL_SYSTEM
  
          self._live_migrate_vm(self._compat_conn, vm, planned_vm,
                                rmt_ip_addr_list, new_resource_setting_data,
                                dest_host, migration_type)
  
      def create_planned_vm(self, vm_name, src_host, disk_path_mapping):
          # This is run on the destination host.
          dest_host = platform.node()
          vmutils_remote = vmutils.VMUtils(src_host)
  
          conn_v2_remote = self._get_conn_v2(src_host)
          vm = self._get_vm(conn_v2_remote, vm_name)
  
          # Make sure there are no planned VMs already.
          self.destroy_existing_planned_vm(vm_name)
  
          ip_addr_list = self._get_ip_address_list(self._compat_conn,
                                                   dest_host)
  
          disk_paths = self._get_disk_data(vm_name, vmutils_remote,
                                           disk_path_mapping)
  
          planned_vm = self._create_planned_vm(self._compat_conn,
                                               conn_v2_remote,
                                               vm, ip_addr_list,
                                               dest_host)
          self._update_planned_vm_disk_resources(self._compat_conn, planned_vm,
                                                 vm_name, disk_paths)
  