The vscsi taskflow operations should be given unique names. If not, the duplicate names can occur when there are multiple Virtual I/O Servers. Change-Id: Ib095fec0d7a083d929e810112a08b4aa79ba7228 Closes-Bug: 1486045
346 lines
15 KiB
Python
346 lines
15 KiB
Python
# Copyright 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.i18n import _, _LI, _LW, _LE
|
|
|
|
from oslo_config import cfg
|
|
from oslo_log import log as logging
|
|
from taskflow import task
|
|
|
|
from nova_powervm.virt.powervm import exception as p_exc
|
|
from nova_powervm.virt.powervm import vios
|
|
from nova_powervm.virt.powervm import vm
|
|
from nova_powervm.virt.powervm.volume import driver as v_driver
|
|
|
|
from pypowervm.tasks import hdisk
|
|
from pypowervm.tasks import scsi_mapper as tsk_map
|
|
from pypowervm.wrappers import storage as pvm_stor
|
|
from pypowervm.wrappers import virtual_io_server as pvm_vios
|
|
|
|
import six
|
|
|
|
CONF = cfg.CONF
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def _build_udid_key(vios_uuid, volume_id):
|
|
"""This method will build the udid dictionary key.
|
|
|
|
:param vios_uuid: The UUID of the vios for the pypowervm adapter.
|
|
:param volume_id: The lun volume id
|
|
:return: The udid dictionary key
|
|
"""
|
|
return vios_uuid + volume_id
|
|
|
|
|
|
class VscsiVolumeAdapter(v_driver.FibreChannelVolumeAdapter):
|
|
"""The vSCSI implementation of the Volume Adapter.
|
|
|
|
vSCSI is the internal mechanism to link a given hdisk on the Virtual
|
|
I/O Server to a Virtual Machine. This volume driver will take the
|
|
information from the driver and link it to a given virtual machine.
|
|
"""
|
|
|
|
def __init__(self, adapter, host_uuid, instance, connection_info,
|
|
tx_mgr=None):
|
|
"""Initializes the vSCSI Volume Adapter.
|
|
|
|
:param adapter: The pypowervm adapter.
|
|
:param host_uuid: The pypowervm UUID of the host.
|
|
:param instance: The nova instance that the volume should connect to.
|
|
:param connection_info: Comes from the BDM.
|
|
:param tx_mgr: (Optional) The pypowervm transaction FeedTask for
|
|
the I/O Operations. If provided, the Virtual I/O Server
|
|
mapping updates will be added to the FeedTask. This
|
|
defers the updates to some later point in time. If the
|
|
FeedTask is not provided, the updates will be run
|
|
immediately when this method is executed.
|
|
"""
|
|
super(VscsiVolumeAdapter, self).__init__(
|
|
adapter, host_uuid, instance, connection_info, tx_mgr=tx_mgr)
|
|
self._pfc_wwpns = None
|
|
self._vioses_modified = []
|
|
|
|
@property
|
|
def min_xags(self):
|
|
"""List of pypowervm XAGs needed to support this adapter."""
|
|
return [pvm_vios.VIOS.xags.STORAGE]
|
|
|
|
def _connect_volume(self):
|
|
"""Connects the volume."""
|
|
# Get the initiators
|
|
volume_id = self.connection_info['data']['volume_id']
|
|
self._vioses_modified = []
|
|
|
|
# Iterate through host vios list to find valid hdisks and map to VM.
|
|
for vios_w in self.tx_mgr.feed:
|
|
if self._connect_volume_to_vio(vios_w, volume_id):
|
|
self._vioses_modified.append(vios_w.uuid)
|
|
|
|
# A valid hdisk was not found so log and exit
|
|
if len(self._vioses_modified) == 0:
|
|
msg = (_('Failed to discover valid hdisk on any Virtual I/O '
|
|
'Server for volume %(volume_id)s.') %
|
|
{'volume_id': volume_id})
|
|
LOG.error(msg)
|
|
ex_args = {'volume_id': volume_id, 'reason': msg,
|
|
'instance_name': self.instance.name}
|
|
raise p_exc.VolumeAttachFailed(**ex_args)
|
|
|
|
def _connect_volume_to_vio(self, vios_w, volume_id):
|
|
"""Attempts to connect a volume to a given VIO.
|
|
|
|
:param vios_w: The Virtual I/O Server wrapper to connect to.
|
|
:param volume_id: The volume identifier.
|
|
:return: True if the volume was connected. False if the volume was not
|
|
(could be the Virtual I/O Server does not have connectivity
|
|
to the hdisk).
|
|
"""
|
|
# Get the initiatior WWPNs, targets and Lun for the given VIOS.
|
|
vio_wwpns, t_wwpns, lun = self._get_hdisk_itls(vios_w)
|
|
|
|
# Build the ITL map and discover the hdisks on the Virtual I/O
|
|
# Server (if any).
|
|
itls = hdisk.build_itls(vio_wwpns, t_wwpns, lun)
|
|
if len(itls) == 0:
|
|
LOG.debug('No ITLs for VIOS %(vios)s for volume %(volume_id)s.'
|
|
% {'vios': vios_w.name, 'volume_id': volume_id})
|
|
return False
|
|
|
|
status, device_name, udid = hdisk.discover_hdisk(
|
|
self.adapter, vios_w.uuid, itls)
|
|
if device_name is not None and status in [
|
|
hdisk.LUAStatus.DEVICE_AVAILABLE,
|
|
hdisk.LUAStatus.FOUND_ITL_ERR]:
|
|
LOG.info(_LI('Discovered %(hdisk)s on vios %(vios)s for '
|
|
'volume %(volume_id)s. Status code: %(status)s.'),
|
|
{'hdisk': device_name, 'vios': vios_w.name,
|
|
'volume_id': volume_id, 'status': str(status)})
|
|
|
|
# Found a hdisk on this Virtual I/O Server. Add the action to
|
|
# map it to the VM when the tx_mgr is executed.
|
|
self._add_append_mapping(vios_w.uuid, device_name)
|
|
|
|
# Save the UDID for the disk in the connection info. It is
|
|
# used for the detach.
|
|
self._set_udid(vios_w.uuid, volume_id, udid)
|
|
LOG.debug('Device attached: %s', device_name)
|
|
|
|
# Valid attachment
|
|
return True
|
|
elif status == hdisk.LUAStatus.DEVICE_IN_USE:
|
|
LOG.warn(_LW('Discovered device %(dev)s for volume %(volume)s '
|
|
'on %(vios)s is in use. Error code: %(status)s.'),
|
|
{'dev': device_name, 'volume': volume_id,
|
|
'vios': vios_w.name, 'status': str(status)})
|
|
return False
|
|
|
|
def _disconnect_volume(self):
|
|
"""Disconnect the volume."""
|
|
volume_id = self.connection_info['data']['volume_id']
|
|
self._vioses_modified = []
|
|
try:
|
|
# Iterate through VIOS feed (from the transaction TaskFeed) to
|
|
# find hdisks to disconnect.
|
|
for vios_w in self.tx_mgr.feed:
|
|
if self._disconnect_volume_for_vio(vios_w, volume_id):
|
|
self._vioses_modified.append(vios_w.uuid)
|
|
|
|
if len(self._vioses_modified) == 0:
|
|
LOG.warn(_LW("Disconnect Volume: Failed to disconnect the "
|
|
"volume %(volume_id)s on ANY of the Virtual I/O "
|
|
"Servers for instance %(inst)s."),
|
|
{'inst': self.instance.name, 'volume_id': volume_id})
|
|
|
|
except Exception as e:
|
|
LOG.error(_LE('Cannot detach volumes from virtual machine: %s'),
|
|
self.vm_uuid)
|
|
LOG.exception(_LE('Error: %s'), e)
|
|
ex_args = {'volume_id': volume_id, 'reason': six.text_type(e),
|
|
'instance_name': self.instance.name}
|
|
raise p_exc.VolumeDetachFailed(**ex_args)
|
|
|
|
def _disconnect_volume_for_vio(self, vios_w, volume_id):
|
|
"""Removes the volume from a specific Virtual I/O Server.
|
|
|
|
:param vios_w: The VIOS wrapper.
|
|
:param volume_id: The volume identifier to remove.
|
|
:return: True if a remove action was done against this VIOS. False
|
|
otherwise.
|
|
"""
|
|
LOG.debug("vios uuid %s", vios_w.uuid)
|
|
volume_udid = None
|
|
try:
|
|
volume_udid = self._get_udid(vios_w.uuid, volume_id)
|
|
device_name = vios_w.hdisk_from_uuid(volume_udid)
|
|
|
|
if not device_name:
|
|
LOG.warn(_LW("Disconnect Volume: No mapped device found on "
|
|
"Virtual I/O Server %(vios)s for volume "
|
|
"%(volume_id)s. Volume UDID: %(volume_uid)s"),
|
|
{'volume_uid': volume_udid, 'volume_id': volume_id,
|
|
'vios': vios_w.name})
|
|
return False
|
|
|
|
except Exception as e:
|
|
LOG.warn(_LW("Disconnect Volume: Failed to find disk on Virtual "
|
|
"I/O Server %(vios_name)s for volume %(volume_id)s. "
|
|
"Volume UDID: %(volume_uid)s. Error: %(error)s"),
|
|
{'error': e, 'volume_uid': volume_udid,
|
|
'volume_id': volume_id, 'vios_name': vios_w.name})
|
|
return False
|
|
|
|
# We have found the device name
|
|
LOG.info(_LI("Disconnect Volume: Discovered the device %(hdisk)s on "
|
|
"Virtual I/O Server %(vios_name)s for volume "
|
|
"%(volume_id)s. Volume UDID: %(volume_uid)s."),
|
|
{'volume_uid': volume_udid, 'volume_id': volume_id,
|
|
'vios_name': vios_w.name, 'hdisk': device_name})
|
|
|
|
# Add the action to remove the mapping when the tx_mgr is run.
|
|
partition_id = vm.get_vm_id(self.adapter, self.vm_uuid)
|
|
self._add_remove_mapping(partition_id, vios_w.uuid,
|
|
device_name)
|
|
|
|
# Add a step after the mapping removal to also remove the
|
|
# hdisk.
|
|
self._add_remove_hdisk(vios_w, device_name)
|
|
|
|
# Found a valid element to remove
|
|
return True
|
|
|
|
def _add_remove_hdisk(self, vio_wrap, device_name):
|
|
"""Adds a post-mapping task to remove the hdisk from the VIOS.
|
|
|
|
This removal is only done after the mapping updates have completed.
|
|
|
|
:param vio_wrap: The Virtual I/O Server wrapper to remove the disk
|
|
from.
|
|
:param device_name: The hdisk name to remove.
|
|
"""
|
|
def rm_hdisk():
|
|
try:
|
|
# Attempt to remove the hDisk
|
|
hdisk.remove_hdisk(self.adapter, CONF.host, device_name,
|
|
vio_wrap.uuid)
|
|
except Exception as e:
|
|
# If there is a failure, log it, but don't stop the process
|
|
LOG.warn(_LW("There was an error removing the hdisk "
|
|
"%(disk)s from the Virtual I/O Server."),
|
|
{'disk': device_name})
|
|
LOG.warn(e)
|
|
name = 'rm_hdisk_%s_%s' % (vio_wrap.name, device_name)
|
|
self.tx_mgr.add_post_execute(task.FunctorTask(rm_hdisk, name=name))
|
|
|
|
def wwpns(self):
|
|
"""Builds the WWPNs of the adapters that will connect the ports.
|
|
|
|
:return: The list of WWPNs that need to be included in the zone set.
|
|
"""
|
|
if self._pfc_wwpns is None:
|
|
self._pfc_wwpns = vios.get_physical_wwpns(self.adapter,
|
|
self.host_uuid)
|
|
return self._pfc_wwpns
|
|
|
|
def host_name(self):
|
|
"""Derives the host name that should be used for the storage device.
|
|
|
|
:return: The host name.
|
|
"""
|
|
return CONF.host
|
|
|
|
def _add_remove_mapping(self, vm_uuid, vios_uuid, device_name):
|
|
"""Adds a transaction to remove the storage mapping.
|
|
|
|
:param vm_uuid: The UUID of the VM instance
|
|
:param vios_uuid: The UUID of the vios for the pypowervm adapter.
|
|
:param device_name: The The hdisk device name.
|
|
"""
|
|
def rm_func(vios_w):
|
|
LOG.info(_LI("Removing vSCSI mapping from Physical Volume %(dev)s "
|
|
"to VM %(vm)s") % {'dev': device_name, 'vm': vm_uuid})
|
|
return tsk_map.remove_maps(
|
|
vios_w, vm_uuid,
|
|
tsk_map.gen_match_func(pvm_stor.PV, names=[device_name]))
|
|
self.tx_mgr.wrapper_tasks[vios_uuid].add_functor_subtask(rm_func)
|
|
|
|
def _add_append_mapping(self, vios_uuid, device_name):
|
|
"""This method will update the tx_mgr to append the mapping to the VIOS
|
|
|
|
:param vios_uuid: The UUID of the vios for the pypowervm adapter.
|
|
:param device_name: The The hdisk device name.
|
|
"""
|
|
def add_func(vios_w):
|
|
LOG.info(_LI("Adding vSCSI mapping to Physical Volume %(dev)s "
|
|
"to VM %(vm)s") % {'dev': device_name,
|
|
'vm': self.vm_uuid})
|
|
pv = pvm_stor.PV.bld(self.adapter, device_name)
|
|
v_map = tsk_map.build_vscsi_mapping(self.host_uuid, vios_w,
|
|
self.vm_uuid, pv)
|
|
return tsk_map.add_map(vios_w, v_map)
|
|
self.tx_mgr.wrapper_tasks[vios_uuid].add_functor_subtask(add_func)
|
|
|
|
def _set_udid(self, vios_uuid, volume_id, udid):
|
|
"""This method will set the hdisk udid in the connection_info.
|
|
|
|
:param vios_uuid: The UUID of the vios for the pypowervm adapter.
|
|
:param volume_id: The lun volume id
|
|
:param udid: The hdisk target_udid to be stored in system_metadata
|
|
"""
|
|
udid_key = _build_udid_key(vios_uuid, volume_id)
|
|
self.connection_info['data'][udid_key] = udid
|
|
|
|
def _get_hdisk_itls(self, vios_w):
|
|
"""Returns the mapped ITLs for the hdisk for the given VIOS.
|
|
|
|
A PowerVM system may have multiple Virtual I/O Servers to virtualize
|
|
the I/O to the virtual machines. Each Virtual I/O server may have their
|
|
own set of initiator WWPNs, target WWPNs and Lun on which hdisk is
|
|
mapped.It will determine and return the ITLs for the given VIOS.
|
|
|
|
:param vios_w: A virtual I/O Server wrapper.
|
|
:return: List of the i_wwpns that are part of the vios_w,
|
|
:return: List of the t_wwpns that are part of the vios_w,
|
|
:return: Target lun id of the hdisk for the vios_w.
|
|
"""
|
|
it_map = self.connection_info['data']['initiator_target_map']
|
|
i_wwpns = it_map.keys()
|
|
|
|
active_wwpns = vios_w.get_active_pfc_wwpns()
|
|
vio_wwpns = [x for x in i_wwpns if x in active_wwpns]
|
|
|
|
t_wwpns = []
|
|
for it_key in vio_wwpns:
|
|
t_wwpns.extend(it_map[it_key])
|
|
lun = self.connection_info['data']['target_lun']
|
|
|
|
return vio_wwpns, t_wwpns, lun
|
|
|
|
def _get_udid(self, vios_uuid, volume_id):
|
|
"""This method will return the hdisk udid stored in connection_info.
|
|
|
|
:param vios_uuid: The UUID of the vios for the pypowervm adapter.
|
|
:param volume_id: The lun volume id
|
|
:return: The target_udid associated with the hdisk
|
|
"""
|
|
try:
|
|
udid_key = _build_udid_key(vios_uuid, volume_id)
|
|
return self.connection_info['data'][udid_key]
|
|
except (KeyError, ValueError):
|
|
LOG.warn(_LW(u'Failed to retrieve device_id key from BDM for '
|
|
'volume id %s'), volume_id)
|
|
return None
|