# Copyright 2014, 2015 IBM Corp.
#
# 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.

from nova import block_device
from nova.compute import task_states
from nova.compute import utils as compute_utils
from nova.console import type as console_type
from nova import context as ctx
from nova import exception
from nova import image
from nova.objects import flavor as flavor_obj
from nova import utils as n_utils
from nova.virt import configdrive
from nova.virt import driver
import re
import time

from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
import six
from taskflow import engines as tf_eng
from taskflow.patterns import linear_flow as tf_lf

from pypowervm import adapter as pvm_apt
from pypowervm import exceptions as pvm_exc
from pypowervm.helpers import log_helper as log_hlp
from pypowervm.helpers import vios_busy as vio_hlp
from pypowervm.tasks import power as pvm_pwr
from pypowervm.tasks import vterm as pvm_vterm
from pypowervm.utils import retry as pvm_retry
from pypowervm.wrappers import base_partition as pvm_bp
from pypowervm.wrappers import managed_system as pvm_ms
from pypowervm.wrappers import virtual_io_server as pvm_vios

from nova_powervm.virt.powervm.disk import driver as disk_dvr
from nova_powervm.virt.powervm import host as pvm_host
from nova_powervm.virt.powervm.i18n import _
from nova_powervm.virt.powervm.i18n import _LE
from nova_powervm.virt.powervm.i18n import _LI
from nova_powervm.virt.powervm.i18n import _LW
from nova_powervm.virt.powervm import image as img
from nova_powervm.virt.powervm import live_migration as lpm
from nova_powervm.virt.powervm import mgmt
from nova_powervm.virt.powervm.tasks import image as tf_img
from nova_powervm.virt.powervm.tasks import network as tf_net
from nova_powervm.virt.powervm.tasks import storage as tf_stg
from nova_powervm.virt.powervm.tasks import vm as tf_vm
from nova_powervm.virt.powervm import vios
from nova_powervm.virt.powervm import vm
from nova_powervm.virt.powervm import volume as vol_attach

LOG = logging.getLogger(__name__)
CONF = cfg.CONF

# Defines, for all cinder volume types, which volume driver to use.  Currently
# only supports Fibre Channel, which has multiple options for connections.
# The connection strategy is defined above.
VOLUME_DRIVER_MAPPINGS = {
    'fibre_channel': vol_attach.FC_STRATEGY_MAPPING[
        CONF.powervm.fc_attach_strategy]
}

DISK_ADPT_NS = 'nova_powervm.virt.powervm.disk'
DISK_ADPT_MAPPINGS = {
    'localdisk': 'localdisk.LocalStorage',
    'ssp': 'ssp.SSPDiskAdapter'
}


class PowerVMDriver(driver.ComputeDriver):

    """PowerVM Implementation of Compute Driver."""

    def __init__(self, virtapi):
        super(PowerVMDriver, self).__init__(virtapi)

    def init_host(self, host):
        """Initialize anything that is necessary for the driver to function,
        including catching up with currently running VM's on the given host.
        """

        # Live migrations
        self.live_migrations = {}
        # Get an adapter
        self._get_adapter()
        # First need to resolve the managed host UUID
        self._get_host_uuid()
        # Get the management partition
        self.mp_uuid = mgmt.get_mgmt_partition(self.adapter).uuid

        # Initialize the disk adapter.  Sets self.disk_dvr
        self._get_disk_adapter()
        self.image_api = image.API()

        # Init Host CPU Statistics
        self.host_cpu_stats = pvm_host.HostCPUStats(self.adapter,
                                                    self.host_uuid)

        LOG.info(_LI("The compute driver has been initialized."))

    def _get_adapter(self):
        self.session = pvm_apt.Session()
        self.adapter = pvm_apt.Adapter(
            self.session, helpers=[log_hlp.log_helper,
                                   vio_hlp.vios_busy_retry_helper])

    def _get_disk_adapter(self):
        conn_info = {'adapter': self.adapter, 'host_uuid': self.host_uuid,
                     'mp_uuid': self.mp_uuid}

        self.disk_dvr = importutils.import_object_ns(
            DISK_ADPT_NS, DISK_ADPT_MAPPINGS[CONF.powervm.disk_driver],
            conn_info)

    def _get_host_uuid(self):
        """Get the System wrapper and its UUID for the (single) host."""
        syswraps = pvm_ms.System.wrap(
            self.adapter.read(pvm_ms.System.schema_type))
        if len(syswraps) != 1:
            raise Exception(
                _("Expected exactly one host; found %d"), len(syswraps))
        self.host_wrapper = syswraps[0]
        self.host_uuid = self.host_wrapper.uuid
        LOG.info(_LI("Host UUID is:%s"), self.host_uuid)

    @staticmethod
    def _log_operation(op, instance):
        """Log entry point of driver operations
        """
        LOG.info(_LI('Operation: %(op)s. Virtual machine display name: '
                     '%(display_name)s, name: %(name)s, UUID: %(uuid)s'),
                 {'op': op, 'display_name': instance.display_name,
                  'name': instance.name, 'uuid': instance.uuid})

    def get_info(self, instance):
        """Get the current status of an instance, by name (not ID!)

        Returns a dict containing:

        :state:           the running state, one of the power_state codes
        :max_mem:         (int) the maximum memory in KBytes allowed
        :mem:             (int) the memory in KBytes used by the domain
        :num_cpu:         (int) the number of virtual CPUs for the domain
        :cpu_time:        (int) the CPU time used in nanoseconds
        """
        info = vm.InstanceInfo(self.adapter, instance.name,
                               vm.get_pvm_uuid(instance))
        return info

    def instance_exists(self, instance):
        """Checks existence of an instance on the host.

        :param instance: The instance to lookup

        Returns True if an instance with the supplied ID exists on
        the host, False otherwise.
        """
        return vm.instance_exists(self.adapter, instance, self.host_uuid)

    def list_instances(self):
        """Return the names of all the instances known to the virtualization
        layer, as a list.
        """
        lpar_list = vm.get_lpar_names(self.adapter)
        return lpar_list

    def get_host_cpu_stats(self):
        """Return the current CPU state of the host."""
        return self.host_cpu_stats.get_host_cpu_stats()

    def spawn(self, context, instance, image_meta, injected_files,
              admin_password, network_info=None, block_device_info=None,
              flavor=None):
        """Create a new instance/VM/domain on the virtualization platform.

        Once this successfully completes, the instance should be
        running (power_state.RUNNING).

        If this fails, any partial instance should be completely
        cleaned up, and the virtualization platform should be in the state
        that it was before this call began.

        :param context: security context
        :param instance: Instance object as returned by DB layer.
                         This function should use the data there to guide
                         the creation of the new instance.
        :param image_meta: image object returned by nova.image.glance that
                           defines the image from which to boot this instance
        :param injected_files: User files to inject into instance.
        :param admin_password: Administrator password to set in instance.
        :param network_info:
           :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
        :param block_device_info: Information about block devices to be
                                  attached to the instance.
        :param flavor: The flavor for the instance to be spawned.
        """
        self._log_operation('spawn', instance)
        if not flavor:
            admin_ctx = ctx.get_admin_context(read_deleted='yes')
            flavor = (
                flavor_obj.Flavor.get_by_id(admin_ctx,
                                            instance.instance_type_id))

        # Extract the block devices.
        bdms = self._extract_bdm(block_device_info)

        # Define the flow
        flow_spawn = tf_lf.Flow("spawn")

        # Create the transaction manager (FeedTask) for Storage I/O.
        xag = self._get_inst_xag(instance, bdms)
        stg_ftsk = vios.build_tx_feed_task(self.adapter, self.host_uuid,
                                           xag=xag)

        # Create the LPAR
        flow_spawn.add(tf_vm.Create(self.adapter, self.host_wrapper, instance,
                                    flavor, stg_ftsk))

        # Create a flow for the IO
        flow_spawn.add(tf_net.PlugVifs(self.virtapi, self.adapter, instance,
                                       network_info, self.host_uuid))
        flow_spawn.add(tf_net.PlugMgmtVif(self.adapter, instance,
                                          self.host_uuid))

        # Only add the image disk if this is from Glance.
        if not self._is_booted_from_volume(block_device_info):
            # Creates the boot image.
            flow_spawn.add(tf_stg.CreateDiskForImg(
                self.disk_dvr, context, instance, image_meta,
                disk_size=flavor.root_gb))

            # Connects up the disk to the LPAR
            flow_spawn.add(tf_stg.ConnectDisk(self.disk_dvr, context, instance,
                                              stg_ftsk=stg_ftsk))

        # Determine if there are volumes to connect.  If so, add a connection
        # for each type.
        self._add_volume_connection_tasks(
            context, instance, bdms, flow_spawn, stg_ftsk)

        # If the config drive is needed, add those steps.  Should be done
        # after all the other I/O.
        if configdrive.required_by(instance):
            flow_spawn.add(tf_stg.CreateAndConnectCfgDrive(
                self.adapter, self.host_uuid, instance, injected_files,
                network_info, admin_password, stg_ftsk=stg_ftsk))

        # Add the transaction manager flow to the end of the 'I/O
        # connection' tasks.  This will run all the connections in parallel.
        flow_spawn.add(stg_ftsk)

        # Update load source of IBMi VM
        distro = instance.system_metadata.get('image_os_distro', '')
        if distro.lower() == img.OSDistro.OS400:
            boot_type = self._get_boot_connectivity_type(
                context, bdms, block_device_info)
            flow_spawn.add(tf_vm.UpdateIBMiSettings(
                self.adapter, instance, self.host_uuid, boot_type))

        # Last step is to power on the system.
        flow_spawn.add(tf_vm.PowerOn(self.adapter, self.host_uuid, instance))

        # Run the flow.
        tf_eng.run(flow_spawn)

    def _add_volume_connection_tasks(self, context, instance, bdms,
                                     flow, stg_ftsk):
        """Determine if there are volumes to connect to this instance.

        If there are volumes to connect to this instance add a task to the
        flow for each volume.

        :param context: security context.
        :param instance: Instance object as returned by DB layer.
        :param bdms: block device mappings.
        :param flow: the flow to add the tasks to.
        :param stg_ftsk: the storage task flow.
        """
        for bdm in bdms or []:
            conn_info = bdm.get('connection_info')
            vol_drv = self._get_inst_vol_adpt(
                context, instance, conn_info=conn_info, stg_ftsk=stg_ftsk)

            # First connect the volume.  This will update the
            # connection_info.
            flow.add(tf_stg.ConnectVolume(vol_drv))

            # Save the BDM so that the updated connection info is
            # persisted.
            flow.add(tf_stg.SaveBDM(bdm, instance))

    def _add_volume_disconnection_tasks(self, context, instance, bdms,
                                        flow, stg_ftsk):
        """Determine if there are volumes to disconnect from this instance.

        If there are volumes to disconnect from this instance add a task to the
        flow for each volume.

        :param context: security context.
        :param instance: Instance object as returned by DB layer.
        :param bdms: block device mappings.
        :param flow: the flow to add the tasks to.
        :param stg_ftsk: the storage task flow.
        """
        for bdm in bdms or []:
            conn_info = bdm.get('connection_info')
            vol_drv = self._get_inst_vol_adpt(
                context, instance, conn_info=conn_info,
                stg_ftsk=stg_ftsk)
            flow.add(tf_stg.DisconnectVolume(vol_drv))

    def _is_booted_from_volume(self, block_device_info):
        """Determine whether the root device is listed in block_device_info.

        If it is, this can be considered a 'boot from Cinder Volume'.

        :param block_device_info: The block device info from the compute
                                  manager.
        :return: True if the root device is in block_device_info and False if
                 it is not.
        """
        root_bdm = block_device.get_root_bdm(
            driver.block_device_info_get_mapping(block_device_info))
        return (root_bdm is not None)

    @property
    def need_legacy_block_device_info(self):
        return False

    def destroy(self, context, instance, network_info, block_device_info=None,
                destroy_disks=True, migrate_data=None):
        """Destroy (shutdown and delete) the specified instance.

        If the instance is not found (for example if networking failed), this
        function should still succeed.  It's probably a good idea to log a
        warning in that case.

        :param context: security context
        :param instance: Instance object as returned by DB layer.
        :param network_info:
           :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
        :param block_device_info: Information about block devices that should
                                  be detached from the instance.
        :param destroy_disks: Indicates if disks should be destroyed
        :param migrate_data: implementation specific params
        """

        def _run_flow():
            # Extract the block devices.
            bdms = self._extract_bdm(block_device_info)

            # Define the flow
            flow = tf_lf.Flow("destroy")

            # Power Off the LPAR
            flow.add(tf_vm.PowerOff(self.adapter, self.host_uuid,
                                    pvm_inst_uuid, instance))

            # Create the transaction manager (FeedTask) for Storage I/O.
            xag = self._get_inst_xag(instance, bdms)
            stg_ftsk = vios.build_tx_feed_task(self.adapter, self.host_uuid,
                                               xag=xag)

            # Add the disconnect/deletion of the vOpt to the transaction
            # manager.
            flow.add(tf_stg.DeleteVOpt(self.adapter, self.host_uuid, instance,
                                       pvm_inst_uuid, stg_ftsk=stg_ftsk))

            # Determine if there are volumes to disconnect.  If so, remove each
            # volume (within the transaction manager)
            self._add_volume_disconnection_tasks(context, instance, bdms, flow,
                                                 stg_ftsk)

            # Only attach the disk adapters if this is not a boot from volume.
            destroy_disk_task = None
            if not self._is_booted_from_volume(block_device_info):
                # Detach the disk storage adapters (when the stg_ftsk runs)
                flow.add(tf_stg.DetachDisk(
                    self.disk_dvr, context, instance, stg_ftsk))

                # Delete the storage disks
                if destroy_disks:
                    destroy_disk_task = tf_stg.DeleteDisk(
                        self.disk_dvr, context, instance)

            # Add the transaction manager flow to the end of the 'storage
            # connection' tasks.  This will run all the connections in parallel
            flow.add(stg_ftsk)

            # The disks shouldn't be destroyed until the unmappings are done.
            if destroy_disk_task:
                flow.add(destroy_disk_task)

            # Last step is to delete the LPAR from the system.
            # Note: If moving to a Graph Flow, will need to change to depend on
            # the prior step.
            flow.add(tf_vm.Delete(self.adapter, pvm_inst_uuid, instance))

            # Build the engine & run!
            engine = tf_eng.load(flow)
            engine.run()

        self._log_operation('destroy', instance)
        if instance.task_state == task_states.RESIZE_REVERTING:
            # This destroy is part of resize, just skip destroying
            # TODO(IBM): What to do longer term
            LOG.info(_LI('Ignoring destroy call during resize revert.'))
            return

        try:
            pvm_inst_uuid = vm.get_pvm_uuid(instance)
            _run_flow()
        except exception.InstanceNotFound:
            LOG.warn(_LW('VM was not found during destroy operation.'),
                     instance=instance)
            return
        except pvm_exc.HttpError as e:
            # See if we were operating on the LPAR that we're deleting
            # and it wasn't found
            resp = e.response
            exp = '/ManagedSystem/.*/LogicalPartition/.*-.*-.*-.*-.*'
            if (resp.status == 404 and re.search(exp, resp.reqpath)):
                # It's the LPAR, so just return.
                LOG.warn(_LW('VM was not found during destroy operation.'),
                         instance=instance)
                return
            else:
                raise

    def attach_volume(self, context, connection_info, instance, mountpoint,
                      disk_bus=None, device_type=None, encryption=None):
        """Attach the volume to the instance at mountpoint using info."""
        self._log_operation('attach_volume', instance)

        # Define the flow
        flow = tf_lf.Flow("attach_volume")

        # Get the LPAR Wrapper
        flow.add(tf_vm.Get(self.adapter, self.host_uuid, instance))

        # Determine if there are volumes to connect.  If so, add a connection
        # for each type.
        vol_drv = self._get_inst_vol_adpt(context, instance,
                                          conn_info=connection_info)
        flow.add(tf_stg.ConnectVolume(vol_drv))

        # Build the engine & run!
        engine = tf_eng.load(flow)
        engine.run()

        # The volume connector may have updated the system metadata.  Save
        # the instance to persist the data.  Spawn/destroy auto saves instance,
        # but the attach does not.  Detach does not need this save - as the
        # detach flows do not (currently) modify system metadata.  May need
        # to revise in the future as volume connectors evolve.
        instance.save()

    def detach_volume(self, connection_info, instance, mountpoint,
                      encryption=None):
        """Detach the volume attached to the instance."""
        self._log_operation('detach_volume', instance)

        # Get a volume adapter for this volume
        vol_drv = self._get_inst_vol_adpt(ctx.get_admin_context(), instance,
                                          conn_info=connection_info)

        # Before attempting to detach a volume, ensure the instance exists
        # If a live migration fails, the compute manager will call detach
        # for each volume attached to the instance, against the destination
        # host.  If the migration failed, then the VM is probably not on
        # the destination host.
        if not vm.instance_exists(self.adapter, instance, self.host_uuid):
            LOG.info(_LI('During volume detach, the instance was not found'
                         ' on this host.'), instance=instance)

            # Check if there is live migration cleanup to do on this volume.
            mig = self.live_migrations.get(instance.uuid, None)
            if mig is not None and isinstance(mig, lpm.LiveMigrationDest):
                mig.cleanup_volume(vol_drv)
            return

        # Define the flow
        flow = tf_lf.Flow("detach_volume")

        # Add a task to detach the volume
        flow.add(tf_stg.DisconnectVolume(vol_drv))

        # Build the engine & run!
        engine = tf_eng.load(flow)
        engine.run()

    def snapshot(self, context, instance, image_id, update_task_state):
        """Snapshots the specified instance.

        :param context: security context
        :param instance: Instance object as returned by DB layer.
        :param image_id: Reference to a pre-created image that will
                         hold the snapshot.
        :param update_task_state: Callable to update the state of the snapshot
                                  task with one of the IMAGE_* consts from
                                  nova.compute.task_states.  Call spec
                                  (inferred from compute driver source):
            update_task_state(task_state, expected_task_state=None)
                param task_state: The nova.compute.task_states.IMAGE_* state to
                                  set.
                param expected_state: The nova.compute.task_state.IMAGE_* state
                                      which should be in place before this
                                      update.  The driver will raise if this
                                      doesn't match.
        """
        self._log_operation('snapshot', instance)

        # Define the flow
        flow = tf_lf.Flow("snapshot")

        # Notify that we're starting the process
        flow.add(tf_img.UpdateTaskState(update_task_state,
                                        task_states.IMAGE_PENDING_UPLOAD))

        # Connect the instance's boot disk to the management partition, and
        # scan the scsi bus and bring the device into the management partition.
        flow.add(tf_stg.InstanceDiskToMgmt(self.disk_dvr, instance))

        # Notify that the upload is in progress
        flow.add(tf_img.UpdateTaskState(
            update_task_state, task_states.IMAGE_UPLOADING,
            expected_state=task_states.IMAGE_PENDING_UPLOAD))

        # Stream the disk to glance
        flow.add(tf_img.StreamToGlance(context, self.image_api, image_id,
                                       instance))

        # Disconnect the boot disk from the management partition and delete the
        # device
        flow.add(tf_stg.RemoveInstanceDiskFromMgmt(self.disk_dvr, instance))

        # Build the engine & run
        tf_eng.load(flow).run()

    def rescue(self, context, instance, network_info, image_meta,
               rescue_password):
        """Rescue the specified instance.

        :param instance: nova.objects.instance.Instance
        """
        self._log_operation('rescue', instance)

        # We need the image size, which isn't in the system meta data
        # so get the all the info.
        image_meta = self.image_api.get(context, image_meta['id'])

        pvm_inst_uuid = vm.get_pvm_uuid(instance)
        # Define the flow
        flow = tf_lf.Flow("rescue")

        # Get the LPAR Wrapper
        flow.add(tf_vm.Get(self.adapter, self.host_uuid, instance))

        # Power Off the LPAR
        flow.add(tf_vm.PowerOff(self.adapter, self.host_uuid,
                                pvm_inst_uuid, instance))

        # Creates the boot image.
        flow.add(tf_stg.CreateDiskForImg(
            self.disk_dvr, context, instance, image_meta,
            image_type=disk_dvr.DiskType.RESCUE))

        # Connects up the disk to the LPAR
        flow.add(tf_stg.ConnectDisk(self.disk_dvr, context, instance))

        # Last step is to power on the system.
        flow.add(tf_vm.PowerOn(
            self.adapter, self.host_uuid, instance,
            pwr_opts={pvm_pwr.BootMode.KEY: pvm_pwr.BootMode.SMS}))

        # Build the engine & run!
        engine = tf_eng.load(flow)
        engine.run()

    def unrescue(self, instance, network_info):
        """Unrescue the specified instance.

        :param instance: nova.objects.instance.Instance
        """
        self._log_operation('unrescue', instance)

        pvm_inst_uuid = vm.get_pvm_uuid(instance)
        context = ctx.get_admin_context()

        # Define the flow
        flow = tf_lf.Flow("unrescue")

        # Get the LPAR Wrapper
        flow.add(tf_vm.Get(self.adapter, self.host_uuid, instance))

        # Power Off the LPAR
        flow.add(tf_vm.PowerOff(self.adapter, self.host_uuid,
                                pvm_inst_uuid, instance))

        # Detach the disk adapter for the rescue image
        flow.add(tf_stg.DetachDisk(self.disk_dvr, context, instance,
                                   disk_type=[disk_dvr.DiskType.RESCUE]))

        # Delete the storage disk for the rescue image
        flow.add(tf_stg.DeleteDisk(self.disk_dvr, context, instance))

        # Last step is to power on the system.
        flow.add(tf_vm.PowerOn(self.adapter, self.host_uuid, instance))

        # Build the engine & run!
        engine = tf_eng.load(flow)
        engine.run()

    def power_off(self, instance, timeout=0, retry_interval=0):
        """Power off the specified instance.

        :param instance: nova.objects.instance.Instance
        :param timeout: time to wait for GuestOS to shutdown
        :param retry_interval: How often to signal guest while
                               waiting for it to shutdown
        """
        self._log_operation('power_off', instance)
        vm.power_off(self.adapter, instance, self.host_uuid)

    def power_on(self, context, instance, network_info,
                 block_device_info=None):
        """Power on the specified instance.

        :param instance: nova.objects.instance.Instance
        """
        self._log_operation('power_on', instance)
        vm.power_on(self.adapter, instance, self.host_uuid)

    def reboot(self, context, instance, network_info, reboot_type,
               block_device_info=None, bad_volumes_callback=None):
        """Reboot the specified instance.

        After this is called successfully, the instance's state
        goes back to power_state.RUNNING. The virtualization
        platform should ensure that the reboot action has completed
        successfully even in cases in which the underlying domain/vm
        is paused or halted/stopped.

        :param instance: nova.objects.instance.Instance
        :param network_info:
           :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
        :param reboot_type: Either a HARD or SOFT reboot
        :param block_device_info: Info pertaining to attached volumes
        :param bad_volumes_callback: Function to handle any bad volumes
            encountered
        """
        self._log_operation(reboot_type + ' reboot', instance)
        force_immediate = reboot_type == 'HARD'
        entry = vm.get_instance_wrapper(self.adapter, instance, self.host_uuid)
        if entry.state != pvm_bp.LPARState.NOT_ACTIVATED:
            pvm_pwr.power_off(entry, self.host_uuid, restart=True,
                              force_immediate=force_immediate)
        else:
            # pypowervm does NOT throw an exception if "already down".
            # Any other exception from pypowervm is a legitimate failure;
            # let it raise up.
            # If we get here, pypowervm thinks the instance is down.
            pvm_pwr.power_on(entry, self.host_uuid)

        # Again, pypowervm exceptions are sufficient to indicate real failure.
        # Otherwise, pypowervm thinks the instance is up.
        return True

    def get_available_resource(self, nodename):
        """Retrieve resource information.

        This method is called when nova-compute launches, and
        as part of a periodic task

        :param nodename:
            node which the caller want to get resources from
            a driver that manages only one node can safely ignore this
        :return: Dictionary describing resources
        """

        resp = self.adapter.read(pvm_ms.System.schema_type,
                                 root_id=self.host_uuid)
        if resp:
            self.host_wrapper = pvm_ms.System.wrap(resp.entry)
        # Get host information
        data = pvm_host.build_host_resource_from_ms(self.host_wrapper)

        # Add the disk information
        data["local_gb"] = self.disk_dvr.capacity
        data["local_gb_used"] = self.disk_dvr.capacity_used

        return data

    def get_host_uptime(self):
        """Returns the result of calling "uptime" on the target host."""
        # trivial implementation from libvirt/driver.py for consistency
        out, err = n_utils.execute('env', 'LANG=C', 'uptime')
        return out

    def attach_interface(self, instance, image_meta, vif):
        """Attach an interface to the instance."""
        self.plug_vifs(instance, [vif])

    def detach_interface(self, instance, vif):
        """Detach an interface from the instance."""
        self.unplug_vifs(instance, [vif])

    def plug_vifs(self, instance, network_info):
        """Plug VIFs into networks."""
        self._log_operation('plug_vifs', instance)

        # Define the flow
        flow = tf_lf.Flow("plug_vifs")

        # Get the LPAR Wrapper
        flow.add(tf_vm.Get(self.adapter, self.host_uuid, instance))

        # Run the attach
        flow.add(tf_net.PlugVifs(self.virtapi, self.adapter, instance,
                                 network_info, self.host_uuid))

        # Build the engine & run!
        engine = tf_eng.load(flow)
        try:
            engine.run()
        except exception.InstanceNotFound:
            raise exception.VirtualInterfacePlugException(
                _("Plug vif failed because instance %s was not found.")
                % instance.name)
        except Exception as e:
            LOG.exception(e)
            raise exception.VirtualInterfacePlugException(
                _("Plug vif failed because of an unexpected error."))

    def unplug_vifs(self, instance, network_info):
        """Unplug VIFs from networks."""
        self._log_operation('unplug_vifs', instance)

        # Define the flow
        flow = tf_lf.Flow("unplug_vifs")

        # Get the LPAR Wrapper
        flow.add(tf_vm.Get(self.adapter, self.host_uuid, instance))

        # Run the detach
        flow.add(tf_net.UnplugVifs(self.adapter, instance, network_info,
                                   self.host_uuid))

        # Build the engine & run!
        engine = tf_eng.load(flow)
        try:
            engine.run()
        except Exception as e:
            LOG.exception(e)
            raise exception.InterfaceDetachFailed(instance_uuid=instance.uuid)

    def get_available_nodes(self, refresh=False):
        """Returns nodenames of all nodes managed by the compute service.

        This method is for multi compute-nodes support. If a driver supports
        multi compute-nodes, this method returns a list of nodenames managed
        by the service. Otherwise, this method should return
        [hypervisor_hostname].
        """

        return [self.host_wrapper.mtms.mtms_str]

    def legacy_nwinfo(self):
        """Indicate if the driver requires the legacy network_info format.
        """
        return False

    def get_host_ip_addr(self):
        """Retrieves the IP address of the dom0
        """
        # This code was pulled from the libvirt driver.
        ips = compute_utils.get_machine_ips()
        if CONF.my_ip not in ips:
            LOG.warn(_LW('my_ip address (%(my_ip)s) was not found on '
                         'any of the interfaces: %(ifaces)s'),
                     {'my_ip': CONF.my_ip, 'ifaces': ", ".join(ips)})
        return CONF.my_ip

    def get_volume_connector(self, instance):
        """Get connector information for the instance for attaching to volumes.

        Connector information is a dictionary representing the ip of the
        machine that will be making the connection, the name of the iscsi
        initiator and the hostname of the machine as follows::

            {
                'ip': ip,
                'initiator': initiator,
                'host': hostname
            }

        """
        # The host ID
        connector = {'host': CONF.host}

        # The WWPNs in case of FC connection.
        vol_drv = self._get_inst_vol_adpt(ctx.get_admin_context(),
                                          instance)

        # The WWPNs in case of FC connection.
        if vol_drv is not None:
            # Override the host name.
            # TODO(IBM) See if there is a way to support a FC host name that
            # is independent of overall host name.
            connector['host'] = vol_drv.host_name()

            # Set the WWPNs
            wwpn_list = vol_drv.wwpns()
            if wwpn_list is not None:
                connector["wwpns"] = wwpn_list
        return connector

    def _remove_volume_connections(self, context, instance, block_device_info):
        """Removes the volume connections for the instance.

        During resize disconnect if there are any volumes connected
        to an instance.
        :param context: security context
        :param instance: Instance object
        :param block_device_info: Information about block devices that should
                                  be detached from the instance.
        """
        # Extract the block devices.
        bdms = self._extract_bdm(block_device_info)
        # Nothing needed if there isn't a bdms.
        if not bdms:
            return

        # Define the flow
        flow = tf_lf.Flow("resize_vm")

        # Create the transaction manager (FeedTask) for Storage I/O.
        xag = self._get_inst_xag(instance, bdms)
        stg_ftsk = vios.build_tx_feed_task(self.adapter, self.host_uuid,
                                           xag=xag)

        # Determine if there are volumes to disconnect.  If so, remove each
        # volume (within the transaction manager)
        self._add_volume_disconnection_tasks(context, instance, bdms, flow,
                                             stg_ftsk)
        if len(flow):
            # Add the transaction manager flow to the end of the 'storage
            # disconnection' tasks.  This will run all the connections in
            # parallel
            flow.add(stg_ftsk)

            # Build the engine & run
            tf_eng.run(flow)

    def migrate_disk_and_power_off(self, context, instance, dest,
                                   flavor, network_info,
                                   block_device_info=None,
                                   timeout=0, retry_interval=0):

        disk_info = {}

        # We may be passed a flavor that is in dict format, but the
        # downstream code is expecting an object, so convert it.
        if flavor and not isinstance(flavor, flavor_obj.Flavor):
            flav_obj = flavor_obj.Flavor.get_by_id(context, flavor['id'])
        else:
            flav_obj = flavor

        if flav_obj and flav_obj.root_gb < instance.root_gb:
            raise exception.InstanceFaultRollback(
                exception.ResizeError(reason=_('Cannot reduce disk size.')))

        if dest == self.get_host_ip_addr():
            self._log_operation('resize', instance)
            # This is a local resize
            # Check for disk resizes before VM resources
            if flav_obj.root_gb > instance.root_gb:
                vm.power_off(self.adapter, instance, self.host_uuid)
                # Resize the root disk
                self.disk_dvr.extend_disk(context, instance, dict(type='boot'),
                                          flav_obj.root_gb)

            # Do any VM resource changes
            self._resize_vm(context, instance, flav_obj, retry_interval)

            # If everything has gone well up to this point, the compute
            # manager is going to terminate the volume connections for the
            # instance, so we need to remove our mappings.
            # Remove the volume connections for the BDMs
            self._remove_volume_connections(context, instance,
                                            block_device_info)
        else:
            self._log_operation('migration', instance)
            raise NotImplementedError()

        # TODO(IBM): The caller is expecting disk info returned
        return disk_info

    def _resize_vm(self, context, instance, flav_obj, retry_interval=0):

        def _delay(attempt, max_attempts, *args, **kwds):
            LOG.info(_LI('Retrying to update VM.'), instance=instance)
            time.sleep(retry_interval)

        @pvm_retry.retry(delay_func=_delay)
        def _update_vm():
            LOG.debug('Resizing instance %s.', instance.name,
                      instance=instance)
            entry = vm.get_instance_wrapper(self.adapter, instance,
                                            self.host_uuid)

            pwrd = vm.power_off(self.adapter, instance,
                                self.host_uuid, entry=entry)
            # If it was powered off then the etag changed, fetch it again
            if pwrd:
                entry = vm.get_instance_wrapper(self.adapter, instance,
                                                self.host_uuid)

            vm.update(self.adapter, self.host_wrapper,
                      instance, flav_obj, entry=entry)

        # Update the VM
        _update_vm()

    def finish_migration(self, context, migration, instance, disk_info,
                         network_info, image_meta, resize_instance,
                         block_device_info=None, power_on=True):
        """Completes a resize.

        :param context: the context for the migration/resize
        :param migration: the migrate/resize information
        :param instance: nova.objects.instance.Instance being migrated/resized
        :param disk_info: the newly transferred disk information
        :param network_info:
           :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
        :param image_meta: image object returned by nova.image.glance that
                           defines the image from which this instance
                           was created
        :param resize_instance: True if the instance disks are being resized,
                                False otherwise
        :param block_device_info: instance volume block device info
        :param power_on: True if the instance should be powered on, False
                         otherwise
        """

        # Extract the block devices.
        bdms = self._extract_bdm(block_device_info)
        # Nothing needed if there isn't a bdms or a requirement to power-on.
        if not bdms and not power_on:
            return

        # Define the flow
        flow = tf_lf.Flow("finish_migration")

        if bdms:
            # Create the transaction manager (FeedTask) for Storage I/O.
            xag = self._get_inst_xag(instance, bdms)
            stg_ftsk = vios.build_tx_feed_task(self.adapter, self.host_uuid,
                                               xag=xag)
            # Determine if there are volumes to connect.  If so, add a
            # connection for each type.
            self._add_volume_connection_tasks(context, instance, bdms,
                                              flow, stg_ftsk)
            if len(flow):
                # Add the transaction manager flow to the end of the 'storage
                # connection' tasks to run all the connections in parallel
                flow.add(stg_ftsk)

        if power_on:
            # Get the lpar wrapper (required by power-on), then power-on
            flow.add(tf_vm.Get(self.adapter, self.host_uuid, instance))
            flow.add(tf_vm.PowerOn(self.adapter, self.host_uuid, instance))

        if len(flow):
            tf_eng.run(flow)

    def confirm_migration(self, migration, instance, network_info):
        """Confirms a resize, destroying the source VM.

        :param migration: the migrate/resize information
        :param instance: nova.objects.instance.Instance
        :param network_info:
           :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
        """
        # TODO(IBM): Anything to do here?
        pass

    def finish_revert_migration(self, context, instance, network_info,
                                block_device_info=None, power_on=True):
        """Finish reverting a resize.

        :param context: the context for the finish_revert_migration
        :param instance: nova.objects.instance.Instance being migrated/resized
        :param network_info:
           :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
        :param block_device_info: instance volume block device info
        :param power_on: True if the instance should be powered on, False
                         otherwise
        """
        self._log_operation('revert resize', instance)
        # TODO(IBM): What to do here?  Do we want to recreate the LPAR
        # Or just change the settings back to the flavor?

        # Get the flavor from the instance, so we can revert it
        admin_ctx = ctx.get_admin_context(read_deleted='yes')
        flav_obj = (
            flavor_obj.Flavor.get_by_id(admin_ctx,
                                        instance.instance_type_id))
        # TODO(IBM)  Get the entry once for both power_off and update
        vm.power_off(self.adapter, instance, self.host_uuid)
        vm.update(self.adapter, self.host_wrapper, instance, flav_obj)

        if power_on:
            vm.power_on(self.adapter, instance, self.host_uuid)

    def ensure_filtering_rules_for_instance(self, instance, network_info):
        """Setting up filtering rules and waiting for its completion.

        To migrate an instance, filtering rules to hypervisors
        and firewalls are inevitable on destination host.
        ( Waiting only for filtering rules to hypervisor,
        since filtering rules to firewall rules can be set faster).

        Concretely, the below method must be called.
        - setup_basic_filtering (for nova-basic, etc.)
        - prepare_instance_filter(for nova-instance-instance-xxx, etc.)

        to_xml may have to be called since it defines PROJNET, PROJMASK.
        but libvirt migrates those value through migrateToURI(),
        so , no need to be called.

        Don't use thread for this method since migration should
        not be started when setting-up filtering rules operations
        are not completed.

        :param instance: nova.objects.instance.Instance object

        """
        # No op for PowerVM
        pass

    def check_can_live_migrate_destination(self, context, instance,
                                           src_compute_info, dst_compute_info,
                                           block_migration=False,
                                           disk_over_commit=False):
        """Check if it is possible to execute live migration.

        This runs checks on the destination host, and then calls
        back to the source host to check the results.

        :param context: security context
        :param instance: nova.db.sqlalchemy.models.Instance
        :param src_compute_info: Info about the sending machine
        :param dst_compute_info: Info about the receiving machine
        :param block_migration: if true, prepare for block migration
        :param disk_over_commit: if true, allow disk over commit

        :returns: a dict containing migration info (hypervisor-dependent)
        """
        LOG.info(_LI("Checking live migration capability on destination "
                     "host."), instance=instance)

        mig = lpm.LiveMigrationDest(self, instance)
        self.live_migrations[instance.uuid] = mig
        return mig.check_destination(context, src_compute_info,
                                     dst_compute_info)

    def check_can_live_migrate_destination_cleanup(self, context,
                                                   dest_check_data):
        """Do required cleanup on dest host after check_can_live_migrate calls

        :param context: security context
        :param dest_check_data: result of check_can_live_migrate_destination
        """
        LOG.info(_LI("Cleaning up from checking live migration capability "
                     "on destination."))

    def check_can_live_migrate_source(self, context, instance,
                                      dest_check_data, block_device_info=None):
        """Check if it is possible to execute live migration.

        This checks if the live migration can succeed, based on the
        results from check_can_live_migrate_destination.

        :param context: security context
        :param instance: nova.db.sqlalchemy.models.Instance
        :param dest_check_data: result of check_can_live_migrate_destination
        :param block_device_info: result of _get_instance_block_device_info
        :returns: a dict containing migration info (hypervisor-dependent)
        """
        LOG.info(_LI("Checking live migration capability on source host."),
                 instance=instance)
        mig = lpm.LiveMigrationSrc(self, instance, dest_check_data)
        self.live_migrations[instance.uuid] = mig

        # Get a volume driver for each volume
        vol_drvs = self._build_vol_drivers(context, instance,
                                           block_device_info)

        return mig.check_source(context, block_device_info, vol_drvs)

    def pre_live_migration(self, context, instance, block_device_info,
                           network_info, disk_info, migrate_data=None):
        """Prepare an instance for live migration

        :param context: security context
        :param instance: nova.objects.instance.Instance object
        :param block_device_info: instance block device information
        :param network_info: instance network information
        :param disk_info: instance disk information
        :param migrate_data: implementation specific data dict.
        """
        LOG.info(_LI("Pre live migration processing."),
                 instance=instance)
        mig = self.live_migrations[instance.uuid]

        # Get a volume driver for each volume
        vol_drvs = self._build_vol_drivers(context, instance,
                                           block_device_info)

        # Run pre-live migration
        return mig.pre_live_migration(context, block_device_info, network_info,
                                      disk_info, migrate_data, vol_drvs)

    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.

        :param context: security context
        :param instance:
            nova.db.sqlalchemy.models.Instance object
            instance object that is migrated.
        :param dest: destination host
        :param post_method:
            post operation method.
            expected nova.compute.manager._post_live_migration.
        :param recover_method:
            recovery method when any exception occurs.
            expected nova.compute.manager._rollback_live_migration.
        :param block_migration: if true, migrate VM disk.
        :param migrate_data: implementation specific params.

        """
        self._log_operation('live_migration', instance)
        try:
            mig = self.live_migrations[instance.uuid]
            try:
                mig.live_migration(context, migrate_data)
            except pvm_exc.JobRequestTimedOut as timeout_ex:
                # If the migration operation exceeds configured timeout
                LOG.error(_LE("Live migration timed out. Aborting migration"),
                          instance=instance)
                mig.migration_abort()
                self._migration_exception_util(context, instance, dest,
                                               recover_method,
                                               block_migration, migrate_data,
                                               mig, ex=timeout_ex)
            except Exception as e:
                LOG.exception(e)
                self._migration_exception_util(context, instance, dest,
                                               recover_method,
                                               block_migration, migrate_data,
                                               mig, ex=e)

            LOG.debug("Calling post live migration method.", instance=instance)
            # Post method to update host in OpenStack and finish live-migration
            post_method(context, instance, dest, block_migration, migrate_data)
        finally:
            # Remove the migration record on the source side.
            del self.live_migrations[instance.uuid]

    def _migration_exception_util(self, context, instance, dest,
                                  recover_method, block_migration,
                                  migrate_data, mig, ex):
        """Migration exception utility.

        :param context: security context
        :param instance:
            nova.db.sqlalchemy.models.Instance object
            instance object that is migrated.
        :param dest: destination host
        :param recover_method:
            recovery method when any exception occurs.
            expected nova.compute.manager._rollback_live_migration.
        :param block_migration: if true, migrate VM disk.
        :param migrate_data: implementation specific params.
        :param mig: live_migration object
        :param ex: exception reason

        """
        LOG.warn(_LW("Rolling back live migration."),
                 instance=instance)
        try:
            mig.rollback_live_migration(context)
            recover_method(context, instance, dest, block_migration,
                           migrate_data)
        except Exception as e:
            LOG.exception(e)

        raise lpm.LiveMigrationFailed(name=instance.name,
                                      reason=six.text_type(ex))

    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.

        :param context: security context
        :param instance: instance object that was being migrated
        :param network_info: instance network information
        :param block_device_info: instance block device information
        :param destroy_disks:
            if true, destroy disks at destination during cleanup
        :param migrate_data: implementation specific params

        """
        del self.live_migrations[instance.uuid]

    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
        """
        # Defer to the disk driver method.
        return self.disk_dvr.check_instance_shared_storage_local(
            context, instance)

    def check_instance_shared_storage_remote(self, context, data):
        """Check if instance files located on shared storage.

        :param context: security context
        :param data: result of check_instance_shared_storage_local
        """
        # Defer to the disk driver method.
        return self.disk_dvr.check_instance_shared_storage_remote(
            context, data)

    def check_instance_shared_storage_cleanup(self, context, data):
        """Do cleanup on host after check_instance_shared_storage calls

        :param context: security context
        :param data: result of check_instance_shared_storage_local
        """
        # Defer to the disk driver method.
        return self.disk_dvr.check_instance_shared_storage_cleanup(
            context, data)

    def post_live_migration(self, context, instance, block_device_info,
                            migrate_data=None):
        """Post operation of live migration at source host.

        :param context: security context
        :instance: instance object that was migrated
        :block_device_info: instance block device information
        :param migrate_data: if not None, it is a dict which has data
        """
        # Build the volume drivers
        vol_drvs = self._build_vol_drivers(context, instance,
                                           block_device_info)

        mig = self.live_migrations[instance.uuid]
        mig.post_live_migration(vol_drvs, migrate_data)

    def post_live_migration_at_source(self, context, instance, network_info):
        """Unplug VIFs from networks at source.

        :param context: security context
        :param instance: instance object reference
        :param network_info: instance network information
        """
        LOG.info(_LI("Post live migration processing on source host."),
                 instance=instance)
        mig = self.live_migrations[instance.uuid]
        mig.post_live_migration_at_source(network_info)

    def post_live_migration_at_destination(self, context, instance,
                                           network_info,
                                           block_migration=False,
                                           block_device_info=None):
        """Post operation of live migration at destination host.

        :param context: security context
        :param instance: instance object that is migrated
        :param network_info: instance network information
        :param block_migration: if true, post operation of block_migration.
        """
        LOG.info(_LI("Post live migration processing on destination host."),
                 instance=instance)
        mig = self.live_migrations[instance.uuid]
        mig.instance = instance

        # Build the volume drivers
        vol_drvs = self._build_vol_drivers(context, instance,
                                           block_device_info)

        # Run post live migration
        mig.post_live_migration_at_destination(network_info, vol_drvs)
        del self.live_migrations[instance.uuid]

    def _build_vol_drivers(self, context, instance, block_device_info):
        """Builds the volume connector drivers for a block device info."""
        # Get a volume driver for each volume
        vol_drvs = []
        bdms = self._extract_bdm(block_device_info)
        for bdm in bdms or []:
            vol_drvs.append(
                self._get_inst_vol_adpt(
                    context, instance, conn_info=bdm.get('connection_info')))
        return vol_drvs

    def unfilter_instance(self, instance, network_info):
        """Stop filtering instance."""
        # No op for PowerVM
        pass

    @staticmethod
    def _extract_bdm(block_device_info):
        """Returns the block device mapping out of the block device info.

        The block device mapping is a list of instances of block device
        classes from nova.virt.block_device.  Each block device
        represents one volume connection.

        An example string representation of the a DriverVolumeBlockDevice
        from the early Liberty time frame is:
        {'guest_format': None,
        'boot_index': 0,
        'mount_device': u'/dev/sda',
        'connection_info': {u'driver_volume_type': u'fibre_channel',
                            u'serial': u'e11765ea-dd14-4aa9-a953-4fd6b4999635',
                            u'data': {u'initiator_target_map':
                                        {u'21000024ff747e59':
                                            [u'500507680220E522',
                                            u'500507680210E522'],
                                        u'21000024ff747e58':
                                            [u'500507680220E522',
                                            u'500507680210E522']},
                                        u'vendor': u'IBM',
                                        u'target_discovered':False,
                                        u'target_UID': u'600507680282...',
                                        u'qos_specs': None,
                                        u'volume_id': u'e11765ea-...',
                                        u'target_lun': u'2',
                                        u'access_mode': u'rw',
                                        u'target_wwn': u'500507680220E522'}
                            },
        'disk_bus': None,
        'device_type': u'disk',
        'delete_on_termination': True}
        """
        if block_device_info is None:
            return []
        return block_device_info.get('block_device_mapping', [])

    def get_vnc_console(self, context, instance):
        """Get connection info for a vnc console.

        :param context: security context
        :param instance: nova.objects.instance.Instance

        :return: An instance of console.type.ConsoleVNC
        """
        self._log_operation('get_vnc_console', instance)
        lpar_uuid = vm.get_pvm_uuid(instance)
        port = pvm_vterm.open_localhost_vnc_vterm(self.adapter, lpar_uuid)
        host = CONF.vnc.vncserver_proxyclient_address
        return console_type.ConsoleVNC(host=host, port=port)

    def _get_inst_xag(self, instance, bdms):
        """Returns the extended attributes required for a given instance.

        This is used in coordination with the FeedTask.  It identifies ahead
        of time what each request requires for its general operations.

        :param instance: Nova instance for which the volume adapter is needed.
        :param bdms: The BDMs for the operation.
        :return: List of extended attributes required for the operation.
        """
        # All operations for deploy/destroy require scsi by default.  This is
        # either vopt, local/SSP disks, etc...
        xags = {pvm_vios.VIOS.xags.SCSI_MAPPING}
        if not bdms:
            LOG.debug('Instance XAGs for VM %(inst)s is %(xags)s.',
                      {'inst': instance.name,
                       'xags': ','.join([x.name for x in xags])})
            return list(xags)

        # If we have any volumes, add the volumes required mapping XAGs.
        adp_type = vol_attach.FC_STRATEGY_MAPPING[
            CONF.powervm.fc_attach_strategy]
        vol_cls = importutils.import_class(adp_type)
        xags.update(set(vol_cls.min_xags()))
        LOG.debug('Instance XAGs for VM %(inst)s is %(xags)s.',
                  {'inst': instance.name,
                   'xags': ','.join([x.name for x in xags])})
        return list(xags)

    def _get_inst_vol_adpt(self, context, instance, conn_info=None,
                           stg_ftsk=None):
        """Returns the appropriate volume driver based on connection type.

        Checks the connection info for connection-type and return the
        connector, if no connection info is provided returns the default
        connector.
        :param context: security context
        :param instance: Nova instance for which the volume adapter is needed.
        :param conn_info: BDM connection information of the instance to
                          get the volume adapter type (vSCSI/NPIV) requested.
        :param stg_ftsk: (Optional) The FeedTask that can be used to defer the
                         mapping actions against the Virtual I/O Server for. If
                         not provided, then the connect/disconnect actions will
                         be immediate.
        :return: Returns the volume adapter, if conn_info is not passed then
                 returns the volume adapter based on the CONF
                 fc_attach_strategy property (npiv/vscsi). Otherwise returns
                 the adapter based on the connection-type of
                 connection_info.
        """
        adp_type = vol_attach.FC_STRATEGY_MAPPING[
            CONF.powervm.fc_attach_strategy]
        vol_cls = importutils.import_class(adp_type)
        if conn_info:
            LOG.debug('Volume Adapter returned for connection_info=%s' %
                      conn_info)
        LOG.debug('Volume Adapter class %(cls)s for instance %(inst)s' %
                  {'cls': vol_cls.__name__, 'inst': instance.name})
        return vol_cls(self.adapter, self.host_uuid,
                       instance, conn_info, stg_ftsk=stg_ftsk)

    def _get_boot_connectivity_type(self, context, bdms, block_device_info):
        """Get connectivity information for the instance.

        :param context: security context
        :param bdms: The BDMs for the operation. If boot volume of
                     the instance is ssp lu or local disk, the bdms is None.
        :param block_device_info: Instance volume block device info.
        :return: Returns the boot connectivity type,
                 If boot volume is a npiv volume, returns 'npiv'
                 Otherwise, return 'vscsi'.
        """
        # Set default boot_conn_type as 'vscsi'
        boot_conn_type = 'vscsi'
        if self._is_booted_from_volume(block_device_info) and bdms is not None:
            for bdm in bdms:
                if bdm.get('boot_index') == 0:
                    conn_info = bdm.get('connection_info')
                    connectivity_type = conn_info['data']['connection-type']
                    boot_conn_type = ('vscsi' if connectivity_type ==
                                      'pv_vscsi' else connectivity_type)
                    return boot_conn_type
        else:
            return boot_conn_type


def _inst_dict(input_dict):
    """Builds a dictionary with instances as values based on the input classes.

    :param input_dict: A dictionary with keys, whose values are class
                       names.
    :return: A dictionary with the same keys.  But the values are instances
             of the class.  No parameters are passed in  to the init methods.
    """
    response = dict()

    for key in input_dict.keys():
        class_inst = importutils.import_class(input_dict[key])
        response[key] = class_inst()

    return response
