Implementation of Cinder driver over FC for Inspur InStorage

This driver implements the features to support the use of Inspur InStorage
over FC. This driver is based on the iSCSI driver of Inspur InStorage.

Change-Id: I51c746f0aff566cfb99f9f3e65a509c1e545f194
Implement: blueprint inspur-instorage-fc-support
This commit is contained in:
wang yong 2018-01-29 10:07:29 +08:00
parent 8a59d07db3
commit a6e79968ed
7 changed files with 984 additions and 0 deletions

View File

@ -1080,3 +1080,16 @@ class VolumeUtilsTestCase(test.TestCase):
self.assertEqual(max_over_subscription_ratio, mosr)
else:
self.assertEqual(float(max_over_subscription_ratio), mosr)
def test_make_initiator_target_all2all_map(self):
initiator_wwpns = ['ff00000000000000', 'ff00000000000001']
target_wwpns = ['bb00000000000000', 'bb00000000000001']
expected = {
'ff00000000000000': ['bb00000000000000', 'bb00000000000001'],
'ff00000000000001': ['bb00000000000000', 'bb00000000000001']
}
ret = volume_utils.make_initiator_target_all2all_map(initiator_wwpns,
target_wwpns)
self.assertEqual(ret, expected)

View File

@ -27,6 +27,7 @@ from cinder import exception
from cinder import utils
from cinder.volume.drivers.inspur.instorage import instorage_const
from cinder.volume.drivers.inspur.instorage import instorage_fc
from cinder.volume.drivers.inspur.instorage import instorage_iscsi
MCS_POOLS = ['openstack', 'openstack1']
@ -39,6 +40,21 @@ def get_test_pool(get_all=False):
return MCS_POOLS[0]
class FakeInStorageMCSFcDriver(instorage_fc.InStorageMCSFCDriver):
def __init__(self, *args, **kwargs):
super(FakeInStorageMCSFcDriver, self).__init__(*args, **kwargs)
def set_fake_storage(self, fake):
self.fake_storage = fake
def _run_ssh(self, cmd, check_exit_code=True, attempts=1):
utils.check_ssh_injection(cmd)
ret = self.fake_storage.execute_command(cmd, check_exit_code)
return ret
class FakeInStorageMCSISCSIDriver(instorage_iscsi.InStorageMCSISCSIDriver):
def __init__(self, *args, **kwargs):

View File

@ -0,0 +1,596 @@
# 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.
#
"""
Tests for the Inspur InStorage volume driver.
"""
from eventlet import greenthread
import mock
from oslo_utils import importutils
from cinder import context
from cinder import exception
from cinder import test
from cinder.tests.unit import utils as testutils
from cinder.tests.unit.volume.drivers.inspur.instorage import fakes
from cinder.volume import configuration as conf
from cinder.volume.drivers.inspur.instorage import instorage_common
from cinder.volume.drivers.inspur.instorage import instorage_fc
from cinder.volume import volume_types
class InStorageMCSFcDriverTestCase(test.TestCase):
@mock.patch.object(greenthread, 'sleep')
def setUp(self, mock_sleep):
super(InStorageMCSFcDriverTestCase, self).setUp()
self.fc_driver = fakes.FakeInStorageMCSFcDriver(
configuration=conf.Configuration(None))
self._def_flags = {'san_ip': 'hostname',
'san_login': 'user',
'san_password': 'pass',
'instorage_mcs_volpool_name': ['openstack'],
'instorage_mcs_localcopy_timeout': 20,
'instorage_mcs_localcopy_rate': 49,
'instorage_mcs_allow_tenant_qos': True}
wwpns = ['1234567890123458', '6543210987654323']
initiator = 'test.initiator.%s' % 123458
self._connector = {'ip': '1.234.56.78',
'host': 'instorage-mcs-test',
'wwpns': wwpns,
'initiator': initiator}
self.sim = fakes.FakeInStorage(['openstack'])
self.fc_driver.set_fake_storage(self.sim)
self.ctxt = context.get_admin_context()
self._reset_flags()
self.ctxt = context.get_admin_context()
db_driver = self.fc_driver.configuration.db_driver
self.db = importutils.import_module(db_driver)
self.fc_driver.db = self.db
self.fc_driver.do_setup(None)
self.fc_driver.check_for_setup_error()
self.fc_driver._assistant.check_lcmapping_interval = 0
def _set_flag(self, flag, value):
group = self.fc_driver.configuration.config_group
self.fc_driver.configuration.set_override(flag, value, group)
def _reset_flags(self):
self.fc_driver.configuration.local_conf.reset()
for k, v in self._def_flags.items():
self._set_flag(k, v)
def _create_volume(self, **kwargs):
pool = fakes.get_test_pool()
prop = {'host': 'openstack@mcs#%s' % pool,
'size': 1}
for p in prop.keys():
if p not in kwargs:
kwargs[p] = prop[p]
vol = testutils.create_volume(self.ctxt, **kwargs)
self.fc_driver.create_volume(vol)
return vol
def _delete_volume(self, volume):
self.fc_driver.delete_volume(volume)
self.db.volume_destroy(self.ctxt, volume['id'])
def _generate_vol_info(self, vol_name, vol_id):
pool = fakes.get_test_pool()
prop = {'mdisk_grp_name': pool}
if vol_name:
prop.update(volume_name=vol_name,
volume_id=vol_id,
volume_size=10)
else:
prop.update(size=10,
volume_type_id=None,
mdisk_grp_name=pool,
host='openstack@mcs#%s' % pool)
vol = testutils.create_volume(self.ctxt, **prop)
return vol
def _assert_vol_exists(self, name, exists):
is_vol_defined = self.fc_driver._assistant.is_vdisk_defined(name)
self.assertEqual(exists, is_vol_defined)
def test_instorage_get_host_with_fc_connection(self):
# Create a FC host
del self._connector['initiator']
assistant = self.fc_driver._assistant
host_name = assistant.create_host(self._connector)
# Remove the first wwpn from connector, and then try get host
wwpns = self._connector['wwpns']
wwpns.remove(wwpns[0])
host_name = assistant.get_host_from_connector(self._connector)
self.assertIsNotNone(host_name)
def test_instorage_get_host_with_fc_connection_with_volume(self):
# create a FC volume
extra_spec = {'capabilities:storage_protocol': '<in> FC'}
vol_type_fc = volume_types.create(self.ctxt, 'FC', extra_spec)
volume_fc = self._generate_vol_info(None, None)
volume_fc['volume_type_id'] = vol_type_fc['id']
self.fc_driver.create_volume(volume_fc)
connector = {'host': 'instorage-mcs-host',
'wwnns': ['20000090fa17311e', '20000090fa17311f'],
'wwpns': ['ff00000000000000', 'ff00000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa'}
self.fc_driver.initialize_connection(volume_fc, connector)
# Create a FC host
assistant = self.fc_driver._assistant
# tell lsfabric to not return anything
self.sim.error_injection('lsfabric', 'no_hosts')
host_name = assistant.get_host_from_connector(
connector, volume_fc['name'])
self.assertIsNotNone(host_name)
def test_instorage_get_host_from_connector_with_lshost_failure2(self):
self._connector.pop('initiator')
self._connector['wwpns'] = [] # Clearing will skip over fast-path
assistant = self.fc_driver._assistant
# Add a host to the simulator. We don't need it to match the
# connector since we will force a bad failure for lshost.
self.sim._cmd_mkhost(name='DifferentHost', hbawwpn='123456')
# tell lshost to fail badly while called from
# get_host_from_connector
self.sim.error_injection('lshost', 'bigger_troubles')
self.assertRaises(exception.VolumeBackendAPIException,
assistant.get_host_from_connector, self._connector)
def test_instorage_get_host_from_connector_not_found(self):
self._connector.pop('initiator')
assistant = self.fc_driver._assistant
# Create some hosts. The first is not related to the connector and
# we use the simulator for that. The second is for the connector.
# We will force the missing_host error for the first host, but
# then tolerate and find the second host on the slow path normally.
self.sim._cmd_mkhost(name='instorage-mcs-test-3',
hbawwpn='1234567')
self.sim._cmd_mkhost(name='instorage-mcs-test-2',
hbawwpn='2345678')
self.sim._cmd_mkhost(name='instorage-mcs-test-1',
hbawwpn='3456789')
self.sim._cmd_mkhost(name='A-Different-host', hbawwpn='9345678')
self.sim._cmd_mkhost(name='B-Different-host', hbawwpn='8345678')
self.sim._cmd_mkhost(name='C-Different-host', hbawwpn='7345678')
# tell lsfabric to skip rows so that we skip past fast path
self.sim.error_injection('lsfabric', 'remove_rows')
# Run test
host_name = assistant.get_host_from_connector(self._connector)
self.assertIsNone(host_name)
def test_instorage_get_host_from_connector_fast_path(self):
self._connector.pop('initiator')
assistant = self.fc_driver._assistant
# Create two hosts. Our lshost will return the hosts in sorted
# Order. The extra host will be returned before the target
# host. If we get detailed lshost info on our host without
# gettting detailed info on the other host we used the fast path
self.sim._cmd_mkhost(name='A-DifferentHost', hbawwpn='123456')
assistant.create_host(self._connector)
# tell lshost to fail while called from get_host_from_connector
self.sim.error_injection('lshost', 'fail_fastpath')
# tell lsfabric to skip rows so that we skip past fast path
self.sim.error_injection('lsfabric', 'remove_rows')
# Run test
host_name = assistant.get_host_from_connector(self._connector)
self.assertIsNotNone(host_name)
# Need to assert that lshost was actually called. The way
# we do that is check that the next simulator error for lshost
# has not been reset.
self.assertEqual(self.sim._next_cmd_error['lshost'], 'fail_fastpath',
"lshost was not called in the simulator. The "
"queued error still remains.")
def test_instorage_initiator_multiple_wwpns_connected(self):
# Generate us a test volume
volume = self._create_volume()
# Fibre Channel volume type
extra_spec = {'capabilities:storage_protocol': '<in> FC'}
vol_type = volume_types.create(self.ctxt, 'FC', extra_spec)
volume['volume_type_id'] = vol_type['id']
# Make sure that the volumes have been created
self._assert_vol_exists(volume['name'], True)
# Set up one WWPN that won't match and one that will.
self.fc_driver._state['storage_nodes']['1']['WWPN'] = [
'123456789ABCDEF0', 'AABBCCDDEEFF0010']
wwpns = ['ff00000000000000', 'ff00000000000001']
connector = {'host': 'instorage-mcs-test', 'wwpns': wwpns}
with mock.patch.object(instorage_common.InStorageAssistant,
'get_conn_fc_wwpns') as get_mappings:
mapped_wwpns = ['AABBCCDDEEFF0001', 'AABBCCDDEEFF0002',
'AABBCCDDEEFF0010', 'AABBCCDDEEFF0012']
get_mappings.return_value = mapped_wwpns
# Initialize the connection
init_ret = self.fc_driver.initialize_connection(volume, connector)
# Make sure we return all wwpns which where mapped as part of the
# connection
self.assertEqual(mapped_wwpns,
init_ret['data']['target_wwn'])
def test_instorage_mcs_fc_validate_connector(self):
conn_neither = {'host': 'host'}
conn_iscsi = {'host': 'host', 'initiator': 'foo'}
conn_fc = {'host': 'host', 'wwpns': 'bar', 'wwnns': 'foo'}
conn_both = {'host': 'host', 'initiator': 'foo', 'wwpns': 'bar',
'wwnns': 'baz'}
self.fc_driver.validate_connector(conn_fc)
self.fc_driver.validate_connector(conn_both)
self.assertRaises(exception.InvalidConnectorException,
self.fc_driver.validate_connector, conn_iscsi)
self.assertRaises(exception.InvalidConnectorException,
self.fc_driver.validate_connector, conn_neither)
def test_instorage_terminate_fc_connection(self):
# create a FC volume
volume_fc = self._create_volume()
extra_spec = {'capabilities:storage_protocol': '<in> FC'}
vol_type_fc = volume_types.create(self.ctxt, 'FC', extra_spec)
volume_fc['volume_type_id'] = vol_type_fc['id']
connector = {'host': 'instorage-mcs-host',
'wwnns': ['20000090fa17311e', '20000090fa17311f'],
'wwpns': ['ff00000000000000', 'ff00000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa'}
self.fc_driver.initialize_connection(volume_fc, connector)
self.fc_driver.terminate_connection(volume_fc, connector)
@mock.patch.object(instorage_fc.InStorageMCSFCDriver,
'_do_terminate_connection')
def test_instorage_initialize_fc_connection_failure(self, term_conn):
# create a FC volume
volume_fc = self._create_volume()
extra_spec = {'capabilities:storage_protocol': '<in> FC'}
vol_type_fc = volume_types.create(self.ctxt, 'FC', extra_spec)
volume_fc['volume_type_id'] = vol_type_fc['id']
connector = {'host': 'instorage-mcs-host',
'wwnns': ['20000090fa17311e', '20000090fa17311f'],
'wwpns': ['ff00000000000000', 'ff00000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa'}
self.fc_driver._state['storage_nodes'] = {}
self.assertRaises(exception.VolumeBackendAPIException,
self.fc_driver.initialize_connection,
volume_fc, connector)
term_conn.assert_called_once_with(volume_fc, connector)
def test_instorage_terminate_fc_connection_multi_attach(self):
# create a FC volume
volume_fc = self._create_volume()
extra_spec = {'capabilities:storage_protocol': '<in> FC'}
vol_type_fc = volume_types.create(self.ctxt, 'FC', extra_spec)
volume_fc['volume_type_id'] = vol_type_fc['id']
connector = {'host': 'instorage-mcs-host',
'wwnns': ['20000090fa17311e', '20000090fa17311f'],
'wwpns': ['ff00000000000000', 'ff00000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1aaa'}
connector2 = {'host': 'INSTORAGE-MCS-HOST',
'wwnns': ['30000090fa17311e', '30000090fa17311f'],
'wwpns': ['ffff000000000000', 'ffff000000000001'],
'initiator': 'iqn.1993-08.org.debian:01:eac5ccc1bbb'}
# map and unmap the volume to two hosts normal case
self.fc_driver.initialize_connection(volume_fc, connector)
self.fc_driver.initialize_connection(volume_fc, connector2)
# validate that the host entries are created
for conn in [connector, connector2]:
host = self.fc_driver._assistant.get_host_from_connector(conn)
self.assertIsNotNone(host)
self.fc_driver.terminate_connection(volume_fc, connector)
self.fc_driver.terminate_connection(volume_fc, connector2)
# validate that the host entries are deleted
for conn in [connector, connector2]:
host = self.fc_driver._assistant.get_host_from_connector(conn)
self.assertIsNone(host)
# map and unmap the volume to two hosts with the mapping gone
self.fc_driver.initialize_connection(volume_fc, connector)
self.fc_driver.initialize_connection(volume_fc, connector2)
# Test multiple attachments case
host_name = self.fc_driver._assistant.get_host_from_connector(
connector2)
self.fc_driver._assistant.unmap_vol_from_host(
volume_fc['name'], host_name)
host_name = self.fc_driver._assistant.get_host_from_connector(
connector2)
self.assertIsNotNone(host_name)
with mock.patch.object(instorage_common.InStorageSSH,
'rmvdiskhostmap') as rmmap:
rmmap.side_effect = Exception('boom')
self.fc_driver.terminate_connection(volume_fc, connector2)
host_name = self.fc_driver._assistant.get_host_from_connector(
connector2)
self.assertIsNone(host_name)
# Test single attachment case
self.fc_driver._assistant.unmap_vol_from_host(
volume_fc['name'], host_name)
with mock.patch.object(instorage_common.InStorageSSH,
'rmvdiskhostmap') as rmmap:
rmmap.side_effect = Exception('boom')
self.fc_driver.terminate_connection(volume_fc, connector)
# validate that the host entries are deleted
for conn in [connector, connector2]:
host = self.fc_driver._assistant.get_host_from_connector(conn)
self.assertIsNone(host)
def test_instorage_initiator_target_map(self):
# Generate us a test volume
volume = self._create_volume()
# FIbre Channel volume type
extra_spec = {'capabilities:storage_protocol': '<in> FC'}
vol_type = volume_types.create(self.ctxt, 'FC', extra_spec)
volume['volume_type_id'] = vol_type['id']
# Make sure that the volumes have been created
self._assert_vol_exists(volume['name'], True)
wwpns = ['ff00000000000000', 'ff00000000000001']
connector = {'host': 'instorage-mcs-test', 'wwpns': wwpns}
# Initialise the connection
init_ret = self.fc_driver.initialize_connection(volume, connector)
# Check that the initiator_target_map is as expected
init_data = {'driver_volume_type': 'fibre_channel',
'data': {'initiator_target_map':
{'ff00000000000000': ['AABBCCDDEEFF0011'],
'ff00000000000001': ['AABBCCDDEEFF0011']},
'target_discovered': False,
'target_lun': 0,
'target_wwn': ['AABBCCDDEEFF0011'],
'volume_id': volume['id']
}
}
self.assertEqual(init_data, init_ret)
# Terminate connection
term_ret = self.fc_driver.terminate_connection(volume, connector)
# Check that the initiator_target_map is as expected
term_data = {'driver_volume_type': 'fibre_channel',
'data': {'initiator_target_map':
{'ff00000000000000': ['5005076802432ADE',
'5005076802332ADE',
'5005076802532ADE',
'5005076802232ADE',
'5005076802132ADE',
'5005086802132ADE',
'5005086802332ADE',
'5005086802532ADE',
'5005086802232ADE',
'5005086802432ADE'],
'ff00000000000001': ['5005076802432ADE',
'5005076802332ADE',
'5005076802532ADE',
'5005076802232ADE',
'5005076802132ADE',
'5005086802132ADE',
'5005086802332ADE',
'5005086802532ADE',
'5005086802232ADE',
'5005086802432ADE']}
}
}
self.assertItemsEqual(term_data, term_ret)
def test_instorage_mcs_fc_host_maps(self):
# Create two volumes to be used in mappings
ctxt = context.get_admin_context()
volume1 = self._generate_vol_info(None, None)
self.fc_driver.create_volume(volume1)
volume2 = self._generate_vol_info(None, None)
self.fc_driver.create_volume(volume2)
# FIbre Channel volume type
extra_spec = {'capabilities:storage_protocol': '<in> FC'}
vol_type = volume_types.create(self.ctxt, 'FC', extra_spec)
expected = {'driver_volume_type': 'fibre_channel',
'data': {'target_lun': 0,
'target_wwn': ['AABBCCDDEEFF0011'],
'target_discovered': False}}
volume1['volume_type_id'] = vol_type['id']
volume2['volume_type_id'] = vol_type['id']
ret = self.fc_driver._assistant.get_host_from_connector(
self._connector)
self.assertIsNone(ret)
# Make sure that the volumes have been created
self._assert_vol_exists(volume1['name'], True)
self._assert_vol_exists(volume2['name'], True)
# Initialize connection from the first volume to a host
ret = self.fc_driver.initialize_connection(
volume1, self._connector)
self.assertEqual(expected['driver_volume_type'],
ret['driver_volume_type'])
for k, v in expected['data'].items():
self.assertEqual(v, ret['data'][k])
# Initialize again, should notice it and do nothing
ret = self.fc_driver.initialize_connection(
volume1, self._connector)
self.assertEqual(expected['driver_volume_type'],
ret['driver_volume_type'])
for k, v in expected['data'].items():
self.assertEqual(v, ret['data'][k])
# Try to delete the 1st volume (should fail because it is mapped)
self.assertRaises(exception.VolumeBackendAPIException,
self.fc_driver.delete_volume,
volume1)
# Check bad output from lsfabric for the 2nd volume
for error in ['remove_field', 'header_mismatch']:
self.sim.error_injection('lsfabric', error)
self.assertRaises(exception.VolumeBackendAPIException,
self.fc_driver.initialize_connection,
volume2, self._connector)
with mock.patch.object(instorage_common.InStorageAssistant,
'get_conn_fc_wwpns') as conn_fc_wwpns:
conn_fc_wwpns.return_value = []
ret = self.fc_driver.initialize_connection(volume2,
self._connector)
ret = self.fc_driver.terminate_connection(volume1, self._connector)
# For the first volume detach, ret['data'] should be empty
# only ret['driver_volume_type'] returned
self.assertEqual({}, ret['data'])
self.assertEqual('fibre_channel', ret['driver_volume_type'])
ret = self.fc_driver.terminate_connection(volume2,
self._connector)
self.assertEqual('fibre_channel', ret['driver_volume_type'])
# wwpn is randomly created
self.assertNotEqual({}, ret['data'])
ret = self.fc_driver._assistant.get_host_from_connector(
self._connector)
self.assertIsNone(ret)
# Test no preferred node
self.sim.error_injection('lsvdisk', 'no_pref_node')
self.assertRaises(exception.VolumeBackendAPIException,
self.fc_driver.initialize_connection,
volume1, self._connector)
# Initialize connection from the second volume to the host with no
# preferred node set if in simulation mode, otherwise, just
# another initialize connection.
self.sim.error_injection('lsvdisk', 'blank_pref_node')
self.fc_driver.initialize_connection(volume2, self._connector)
# Try to remove connection from host that doesn't exist (should fail)
conn_no_exist = self._connector.copy()
conn_no_exist['initiator'] = 'i_dont_exist'
conn_no_exist['wwpns'] = ['0000000000000000']
self.assertRaises(exception.VolumeDriverException,
self.fc_driver.terminate_connection,
volume1,
conn_no_exist)
# Try to remove connection from volume that isn't mapped (should print
# message but NOT fail)
unmapped_vol = self._generate_vol_info(None, None)
self.fc_driver.create_volume(unmapped_vol)
self.fc_driver.terminate_connection(unmapped_vol, self._connector)
self.fc_driver.delete_volume(unmapped_vol)
# Remove the mapping from the 1st volume and delete it
self.fc_driver.terminate_connection(volume1, self._connector)
self.fc_driver.delete_volume(volume1)
self._assert_vol_exists(volume1['name'], False)
# Make sure our host still exists
host_name = self.fc_driver._assistant.get_host_from_connector(
self._connector)
self.assertIsNotNone(host_name)
# Remove the mapping from the 2nd volume. The host should
# be automatically removed because there are no more mappings.
self.fc_driver.terminate_connection(volume2, self._connector)
# Check if we successfully terminate connections when the host is not
# specified
fake_conn = {'ip': '127.0.0.1', 'initiator': 'iqn.fake'}
self.fc_driver.initialize_connection(volume2, self._connector)
host_name = self.fc_driver._assistant.get_host_from_connector(
self._connector)
self.assertIsNotNone(host_name)
self.fc_driver.terminate_connection(volume2, fake_conn)
host_name = self.fc_driver._assistant.get_host_from_connector(
self._connector)
self.assertIsNone(host_name)
self.fc_driver.delete_volume(volume2)
self._assert_vol_exists(volume2['name'], False)
# Delete volume types that we created
volume_types.destroy(ctxt, vol_type['id'])
ret = (self.fc_driver._assistant.get_host_from_connector(
self._connector))
self.assertIsNone(ret)
def test_instorage_mcs_fc_multi_host_maps(self):
# Create a volume to be used in mappings
ctxt = context.get_admin_context()
volume = self._generate_vol_info(None, None)
self.fc_driver.create_volume(volume)
# Create volume types for protocols
types = {}
for protocol in ['FC']:
opts = {'storage_protocol': '<in> ' + protocol}
types[protocol] = volume_types.create(ctxt, protocol, opts)
# Create a connector for the second 'host'
wwpns = ['1234567890123459', '6543210987654324']
initiator = 'test.initiator.%s' % 123459
conn2 = {'ip': '1.234.56.79',
'host': 'instorage-mcs-test2',
'wwpns': wwpns,
'initiator': initiator}
# Check protocols for FC
volume['volume_type_id'] = types[protocol]['id']
# Make sure that the volume has been created
self._assert_vol_exists(volume['name'], True)
self.fc_driver.initialize_connection(volume, self._connector)
self.fc_driver.initialize_connection(volume, conn2)
self.fc_driver.terminate_connection(volume, conn2)
self.fc_driver.terminate_connection(volume, self._connector)
def test_add_vdisk_copy_fc(self):
# Ensure only FC is available
self.fc_driver._state['enabled_protocols'] = set(['FC'])
volume = self._generate_vol_info(None, None)
self.fc_driver.create_volume(volume)
self.fc_driver.add_vdisk_copy(volume['name'], 'fake-pool', None)

View File

@ -0,0 +1,233 @@
# 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
@fczm_utils.add_fc_zone
@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})
return {'driver_volume_type': 'fibre_channel', 'data': properties, }
@fczm_utils.remove_fc_zone
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}
# No volume mapped to the host, delete host from array
self._assistant.delete_host(host_name)
return info

View File

@ -1011,3 +1011,14 @@ def get_max_over_subscription_ratio(str_value, supports_auto=False):
LOG.error(msg)
raise exception.InvalidParameterValue(message=msg)
return mosr
def make_initiator_target_all2all_map(initiator_wwpns, target_wwpns):
"""Build a simplistic all-to-all mapping."""
i_t_map = {}
for i_wwpn in initiator_wwpns:
i_t_map[str(i_wwpn)] = []
for t_wwpn in target_wwpns:
i_t_map[i_wwpn].append(t_wwpn)
return i_t_map

View File

@ -0,0 +1,111 @@
=====================================
Inspur InStorage family volume driver
=====================================
Inspur InStorage family volume driver provides OpenStack Compute instances
with access to Inspur Instorage family storage system.
Inspur InStorage storage system can be used with FC or iSCSI connection.
This documentation explains how to configure and connect the block storage
nodes to Inspur InStorage family storage system.
Supported operations
~~~~~~~~~~~~~~~~~~~~
- Create, list, delete, attach (map), and detach (unmap) volumes.
- Create, list and delete volume snapshots.
- Create a volume from a snapshot.
- Copy an image to a volume.
- Copy a volume to an image.
- Clone a volume.
- Extend a volume.
- Retype a volume.
- Manage and unmanage a volume.
- Create, list, and delete consistency group.
- Create, list, and delete consistency group snapshot.
- Modify consistency group (add or remove volumes).
- Create consistency group from source.
- Failover and Failback support.
Configure Inspur InStorage iSCSI/FC backend
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This section details the steps required to configure the Inspur InStorage
Cinder Driver for single FC or iSCSI backend.
#. In the ``cinder.conf`` configuration file under the ``[DEFAULT]``
section, set the enabled_backends parameter
with the iSCSI or FC back-end group
- For Fibre Channel:
.. code-block:: ini
[DEFAULT]
enabled_backends = instorage-fc-1
- For iSCSI:
.. code-block:: ini
[DEFAULT]
enabled_backends = instorage-iscsi-1
#. Add a back-end group section for back-end group specified
in the enabled_backends parameter
#. In the newly created back-end group section, set the
following configuration options:
- For Fibre Channel:
.. code-block:: ini
[instorage-fc-1]
# Management IP of Inspur InStorage storage array
san_ip = 10.0.0.10
# Management Port of Inspur InStorage storage array, by default set to 22
san_ssh_port = 22
# Management username of Inspur InStorage storage array
san_login = username
# Management password of Inspur InStorage storage array
san_password = password
# Private key for Inspur InStorage storage array
san_private_key = path/to/the/private/key
# The Pool used to allocated volumes
instorage_mcs_volpool_name = Pool0
# The driver path
volume_driver = cinder.volume.drivers.inspur.instorage.instorage_fc.InStorageMCSFCDriver
# Backend name
volume_backend_name = instorage_fc
- For iSCSI:
.. code-block:: ini
[instorage-iscsi-1]
# Management IP of Inspur InStorage storage array
san_ip = 10.0.0.10
# Management Port of Inspur InStorage storage array, by default set to 22
san_ssh_port = 22
# Management username of Inspur InStorage storage array
san_login = username
# Management password of Inspur InStorage storage array
san_password = password
# Private key for Inspur InStorage storage array
san_private_key = path/to/the/private/key
# The Pool used to allocated volumes
instorage_mcs_volpool_name = Pool0
# The driver path
volume_driver = cinder.volume.drivers.inspur.instorage.instorage_iscsi.InStorageMCSISCSIDriver
# Backend name
volume_backend_name = instorage_iscsi
.. note::
When both ``san_password`` and ``san_private_key`` are provide, the driver will use private key prefer to password.
#. Save the changes to the ``/etc/cinder/cinder.conf`` file and
restart the ``cinder-volume`` service.

View File

@ -0,0 +1,4 @@
---
features:
- |
New FC Cinder volume driver for Inspur Instorage.