cinder/cinder/volume/drivers/inspur/instorage/instorage_fc.py

236 lines
9.5 KiB
Python

# Copyright 2017 Inspur 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.
#
"""
FC volume driver for Inspur InStorage family storage systems.
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from cinder import coordination
from cinder import exception
from cinder.i18n import _
from cinder import interface
from cinder import utils as cinder_utils
from cinder.volume import driver
from cinder.volume.drivers.inspur.instorage import instorage_common
from cinder.volume import utils
from cinder.zonemanager import utils as fczm_utils
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
@interface.volumedriver
class InStorageMCSFCDriver(instorage_common.InStorageMCSCommonDriver,
driver.FibreChannelDriver):
"""INSPUR InStorage MCS FC volume driver.
Version history:
.. code-block:: none
1.0 - Initial driver
"""
VERSION = "1.0.0"
# ThirdPartySystems wiki page
CI_WIKI_NAME = "INSPUR_CI"
def __init__(self, *args, **kwargs):
super(InStorageMCSFCDriver, self).__init__(*args, **kwargs)
self.protocol = 'FC'
@cinder_utils.trace
@coordination.synchronized('instorage-host'
'{self._state[system_id]}'
'{connector[host]}')
def initialize_connection(self, volume, connector):
"""Perform necessary work to make a FC connection.
To be able to create an FC connection from a given host to a
volume, we must:
1. Translate the given WWNN to a host name
2. Create new host on the storage system if it does not yet exist
3. Map the volume to the host if it is not already done
4. Return the connection information for relevant nodes (in the
proper I/O group)
"""
volume_name = self._get_target_vol(volume)
# Check if a host object is defined for this host name
host_name = self._assistant.get_host_from_connector(connector)
if host_name is None:
# Host does not exist - add a new host to InStorage/MCS
host_name = self._assistant.create_host(connector)
volume_attributes = self._assistant.get_vdisk_attributes(volume_name)
if volume_attributes is None:
msg = (_('initialize_connection: Failed to get attributes'
' for volume %s.') % volume_name)
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
lun_id = self._assistant.map_vol_to_host(volume_name,
host_name,
True)
try:
preferred_node = volume_attributes['preferred_node_id']
IO_group = volume_attributes['IO_group_id']
except KeyError as e:
LOG.error('Did not find expected column name in '
'lsvdisk: %s.', e)
raise exception.VolumeBackendAPIException(
data=_('initialize_connection: Missing volume attribute for '
'volume %s.') % volume_name)
try:
# Get preferred node and other nodes in I/O group
preferred_node_entry = None
io_group_nodes = []
for node in self._state['storage_nodes'].values():
if node['id'] == preferred_node:
preferred_node_entry = node
if node['IO_group'] == IO_group:
io_group_nodes.append(node)
if not len(io_group_nodes):
msg = (_('initialize_connection: No node found in '
'I/O group %(gid)s for volume %(vol)s.') %
{'gid': IO_group, 'vol': volume_name})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if not preferred_node_entry:
# Get 1st node in I/O group
preferred_node_entry = io_group_nodes[0]
LOG.warning('initialize_connection: Did not find a '
'preferred node for volume %s.', volume_name)
properties = {}
properties['target_discovered'] = False
properties['target_lun'] = lun_id
properties['volume_id'] = volume.id
conn_wwpns = self._assistant.get_conn_fc_wwpns(host_name)
# If conn_wwpns is empty, then that means that there were
# no target ports with visibility to any of the initiators
# so we return all target ports.
if len(conn_wwpns) == 0:
for node in self._state['storage_nodes'].values():
conn_wwpns.extend(node['WWPN'])
properties['target_wwn'] = conn_wwpns
i_t_map = utils.make_initiator_target_all2all_map(
connector['wwpns'], conn_wwpns)
properties['initiator_target_map'] = i_t_map
except Exception:
with excutils.save_and_reraise_exception():
self._do_terminate_connection(volume, connector)
LOG.error('initialize_connection: Failed '
'to collect return '
'properties for volume %(vol)s and connector '
'%(conn)s.\n', {'vol': volume,
'conn': connector})
info = {'driver_volume_type': 'fibre_channel', 'data': properties, }
fczm_utils.add_fc_zone(info)
return info
def terminate_connection(self, volume, connector, **kwargs):
"""Cleanup after an FC connection has been terminated."""
# If a fake connector is generated by nova when the host
# is down, then the connector will not have a host property,
# In this case construct the lock without the host property
# so that all the fake connectors to an MCS are serialized
host = ""
if connector is not None and 'host' in connector:
host = connector['host']
@coordination.synchronized('instorage-host' +
self._state['system_id'] + host)
def _do_terminate_connection_locked():
return self._do_terminate_connection(volume, connector,
**kwargs)
return _do_terminate_connection_locked()
@cinder_utils.trace
def _do_terminate_connection(self, volume, connector, **kwargs):
"""Cleanup after an FC connection has been terminated.
When we clean up a terminated connection between a given connector
and volume, we:
1. Translate the given connector to a host name
2. Remove the volume-to-host mapping if it exists
3. Delete the host if it has no more mappings (hosts are created
automatically by this driver when mappings are created)
"""
vol_name = self._get_target_vol(volume)
info = {}
if connector is not None and 'host' in connector:
# get host according to FC protocol
connector = connector.copy()
connector.pop('initiator', None)
info = {'driver_volume_type': 'fibre_channel',
'data': {}}
host_name = self._assistant.get_host_from_connector(
connector, volume_name=vol_name)
if host_name is None:
msg = (_('terminate_connection: Failed to get host name from'
' connector.'))
LOG.error(msg)
raise exception.VolumeDriverException(message=msg)
else:
host_name = None
# Unmap volumes, if hostname is None, need to get value from vdiskmap
host_name = self._assistant.unmap_vol_from_host(vol_name, host_name)
# Host_name could be none
if host_name:
resp = self._assistant.check_host_mapped_vols(host_name)
if not len(resp):
LOG.info("Need to remove FC Zone, building initiator "
"target map.")
# Build info data structure for zone removing
if connector is not None and 'wwpns' in connector:
target_wwpns = []
# Returning all target_wwpns in storage_nodes, since
# we cannot determine which wwpns are logged in during
# a VM deletion.
for node in self._state['storage_nodes'].values():
target_wwpns.extend(node['WWPN'])
init_targ_map = (utils.make_initiator_target_all2all_map
(connector['wwpns'],
target_wwpns))
info['data'] = {'initiator_target_map': init_targ_map}
# Only remove the zone if it's the last volume removed
fczm_utils.remove_fc_zone(info)
# No volume mapped to the host, delete host from array
self._assistant.delete_host(host_name)
return info