cinder/cinder/volume/drivers/violin/v7000_fcp.py

389 lines
14 KiB
Python

# Copyright 2015 Violin Memory, Inc.
# 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.
"""
Violin 7000 Series All-Flash Array Volume Driver
Provides fibre channel specific LUN services for V7000 series flash
arrays.
This driver requires Concerto v7.0.0 or newer software on the array.
You will need to install the Violin Memory REST client library:
sudo pip install vmemclient
Set the following in the cinder.conf file to enable the VMEM V7000
Fibre Channel Driver along with the required flags:
volume_driver=cinder.volume.drivers.violin.v7000_fcp.V7000FCDriver
NOTE: this driver file requires the use of synchronization points for
certain types of backend operations, and as a result may not work
properly in an active-active HA configuration. See OpenStack Cinder
driver documentation for more information.
"""
from oslo_log import log as logging
from cinder import exception
from cinder.i18n import _, _LE, _LI
from cinder import interface
from cinder import utils
from cinder.volume import driver
from cinder.volume.drivers.san import san
from cinder.volume.drivers.violin import v7000_common
from cinder.zonemanager import utils as fczm_utils
import socket
LOG = logging.getLogger(__name__)
@interface.volumedriver
class V7000FCPDriver(driver.FibreChannelDriver):
"""Executes commands relating to fibre channel based Violin Memory arrays.
Version history:
1.0 - Initial driver
"""
VERSION = '1.0'
def __init__(self, *args, **kwargs):
super(V7000FCPDriver, self).__init__(*args, **kwargs)
self.gateway_fc_wwns = []
self.stats = {}
self.configuration.append_config_values(v7000_common.violin_opts)
self.configuration.append_config_values(san.san_opts)
self.common = v7000_common.V7000Common(self.configuration)
self.lookup_service = fczm_utils.create_lookup_service()
LOG.info(_LI("Initialized driver %(name)s version: %(vers)s"),
{'name': self.__class__.__name__, 'vers': self.VERSION})
def do_setup(self, context):
"""Any initialization the driver does while starting."""
super(V7000FCPDriver, self).do_setup(context)
self.common.do_setup(context)
self.gateway_fc_wwns = self._get_active_fc_targets()
# Register the client with the storage array
fc_version = self.VERSION + "-FCP"
self.common.vmem_mg.utility.set_managed_by_openstack_version(
fc_version)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self.common.check_for_setup_error()
if len(self.gateway_fc_wwns) == 0:
raise exception.ViolinInvalidBackendConfig(
reason=_('No FCP targets found'))
def create_volume(self, volume):
"""Creates a volume."""
self.common._create_lun(volume)
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
self.common._create_volume_from_snapshot(snapshot, volume)
def create_cloned_volume(self, volume, src_vref):
"""Creates a clone of the specified volume."""
self.common._create_lun_from_lun(src_vref, volume)
def delete_volume(self, volume):
"""Deletes a volume."""
self.common._delete_lun(volume)
def extend_volume(self, volume, new_size):
"""Extend an existing volume's size."""
self.common._extend_lun(volume, new_size)
def create_snapshot(self, snapshot):
"""Creates a snapshot."""
self.common._create_lun_snapshot(snapshot)
def delete_snapshot(self, snapshot):
"""Deletes a snapshot."""
self.common._delete_lun_snapshot(snapshot)
def ensure_export(self, context, volume):
"""Synchronously checks and re-exports volumes at cinder start time."""
pass
def create_export(self, context, volume, connector):
"""Exports the volume."""
pass
def remove_export(self, context, volume):
"""Removes an export for a logical volume."""
pass
@fczm_utils.AddFCZone
def initialize_connection(self, volume, connector):
"""Allow connection to connector and return connection info."""
LOG.debug("Initialize_connection: initiator - %(initiator)s host - "
"%(host)s wwpns - %(wwpns)s",
{'initiator': connector['initiator'],
'host': connector['host'],
'wwpns': connector['wwpns']})
self.common.vmem_mg.client.create_client(
name=connector['host'], proto='FC', fc_wwns=connector['wwpns'])
lun_id = self._export_lun(volume, connector)
target_wwns, init_targ_map = self._build_initiator_target_map(
connector)
properties = {}
properties['target_discovered'] = True
properties['target_wwn'] = target_wwns
properties['target_lun'] = lun_id
properties['initiator_target_map'] = init_targ_map
LOG.debug("Return FC data for zone addition: %(properties)s.",
{'properties': properties})
return {'driver_volume_type': 'fibre_channel', 'data': properties}
@fczm_utils.RemoveFCZone
def terminate_connection(self, volume, connector, **kwargs):
"""Terminates the connection (target<-->initiator)."""
self._unexport_lun(volume, connector)
properties = {}
if not self._is_initiator_connected_to_array(connector):
target_wwns, init_targ_map = self._build_initiator_target_map(
connector)
properties['target_wwn'] = target_wwns
properties['initiator_target_map'] = init_targ_map
LOG.debug("Return FC data for zone deletion: %(properties)s.",
{'properties': properties})
return {'driver_volume_type': 'fibre_channel', 'data': properties}
def get_volume_stats(self, refresh=False):
"""Get volume stats.
If 'refresh' is True, update the stats first.
"""
if refresh or not self.stats:
self._update_volume_stats()
return self.stats
@utils.synchronized('vmem-export')
def _export_lun(self, volume, connector=None):
"""Generates the export configuration for the given volume.
:param volume: volume object provided by the Manager
:param connector: connector object provided by the Manager
:returns: the LUN ID assigned by the backend
"""
lun_id = ''
v = self.common.vmem_mg
if not connector:
raise exception.ViolinInvalidBackendConfig(
reason=_('No initiators found, cannot proceed'))
LOG.debug("Exporting lun %(vol_id)s - initiator wwpns %(i_wwpns)s "
"- target wwpns %(t_wwpns)s.",
{'vol_id': volume['id'], 'i_wwpns': connector['wwpns'],
't_wwpns': self.gateway_fc_wwns})
try:
lun_id = self.common._send_cmd_and_verify(
v.lun.assign_lun_to_client,
self._is_lun_id_ready,
"Assign SAN client successfully",
[volume['id'], connector['host'],
"ReadWrite"],
[volume['id'], connector['host']])
except exception.ViolinBackendErr:
LOG.exception(_LE("Backend returned err for lun export."))
raise
except Exception:
raise exception.ViolinInvalidBackendConfig(
reason=_('LUN export failed!'))
lun_id = self._get_lun_id(volume['id'], connector['host'])
LOG.info(_LI("Exported lun %(vol_id)s on lun_id %(lun_id)s."),
{'vol_id': volume['id'], 'lun_id': lun_id})
return lun_id
@utils.synchronized('vmem-export')
def _unexport_lun(self, volume, connector=None):
"""Removes the export configuration for the given volume.
:param volume: volume object provided by the Manager
"""
v = self.common.vmem_mg
LOG.info(_LI("Unexporting lun %s."), volume['id'])
try:
self.common._send_cmd(v.lun.unassign_client_lun,
"Unassign SAN client successfully",
volume['id'], connector['host'], True)
except exception.ViolinBackendErr:
LOG.exception(_LE("Backend returned err for lun export."))
raise
except Exception:
LOG.exception(_LE("LUN unexport failed!"))
raise
def _update_volume_stats(self):
"""Gathers array stats and converts them to GB values."""
data = {}
total_gb = 0
free_gb = 0
v = self.common.vmem_mg.basic
array_name_triple = socket.gethostbyaddr(self.configuration.san_ip)
array_name = array_name_triple[0]
phy_devices = v.get("/batch/physicalresource/physicaldevice")
all_devices = [x for x in phy_devices['data']['physical_devices']]
for x in all_devices:
if socket.getfqdn(x['owner']) == array_name:
total_gb += x['size_mb'] // 1024
free_gb += x['availsize_mb'] // 1024
backend_name = self.configuration.volume_backend_name
data['volume_backend_name'] = backend_name or self.__class__.__name__
data['vendor_name'] = 'Violin Memory, Inc.'
data['driver_version'] = self.VERSION
data['storage_protocol'] = 'fibre_channel'
data['reserved_percentage'] = 0
data['QoS_support'] = False
data['total_capacity_gb'] = total_gb
data['free_capacity_gb'] = free_gb
for i in data:
LOG.debug("stat update: %(name)s=%(data)s",
{'name': i, 'data': data[i]})
self.stats = data
def _get_active_fc_targets(self):
"""Get a list of gateway WWNs that can be used as FCP targets.
:param mg_conn: active XG connection to one of the gateways
:returns: list of WWNs in openstack format
"""
v = self.common.vmem_mg
active_gw_fcp_wwns = []
fc_info = v.adapter.get_fc_info()
for x in fc_info.values():
active_gw_fcp_wwns.append(x[0])
return active_gw_fcp_wwns
def _get_lun_id(self, volume_name, client_name):
"""Get the lun ID for an exported volume.
If the lun is successfully assigned (exported) to a client, the
client info has the lun_id.
:param volume_name: name of volume to query for lun ID
:param client_name: name of client associated with the volume
:returns: integer value of lun ID
"""
v = self.common.vmem_mg
lun_id = None
client_info = v.client.get_client_info(client_name)
for x in client_info['FibreChannelDevices']:
if volume_name == x['name']:
lun_id = x['lun']
break
if lun_id:
lun_id = int(lun_id)
return lun_id
def _is_lun_id_ready(self, volume_name, client_name):
"""Get the lun ID for an exported volume.
If the lun is successfully assigned (exported) to a client, the
client info has the lun_id.
:param volume_name: name of volume to query for lun ID
:param client_name: name of client associated with the volume
:returns: Returns True if lun is ready, False otherwise
"""
lun_id = -1
lun_id = self._get_lun_id(volume_name, client_name)
if lun_id is None:
return False
else:
return True
def _build_initiator_target_map(self, connector):
"""Build the target_wwns and the initiator target map."""
target_wwns = []
init_targ_map = {}
if self.lookup_service:
dev_map = self.lookup_service.get_device_mapping_from_network(
connector['wwpns'], self.gateway_fc_wwns)
for fabric_name in dev_map:
fabric = dev_map[fabric_name]
target_wwns += fabric['target_port_wwn_list']
for initiator in fabric['initiator_port_wwn_list']:
if initiator not in init_targ_map:
init_targ_map[initiator] = []
init_targ_map[initiator] += fabric['target_port_wwn_list']
init_targ_map[initiator] = list(
set(init_targ_map[initiator]))
target_wwns = list(set(target_wwns))
else:
initiator_wwns = connector['wwpns']
target_wwns = self.gateway_fc_wwns
for initiator in initiator_wwns:
init_targ_map[initiator] = target_wwns
return target_wwns, init_targ_map
def _is_initiator_connected_to_array(self, connector):
"""Check if any initiator wwns still have active sessions."""
v = self.common.vmem_mg
client = v.client.get_client_info(connector['host'])
if len(client['FibreChannelDevices']):
# each entry in the FibreChannelDevices array is a dict
# describing an active lun assignment
return True
return False