Hitachi: Add Target Port Assignment for VSP Driver

Defining particular ports in extra spec "hbsd:target_ports"
determines which of the ports specified by the hitachi_target_ports
or the hitachi_compute_target_ports parameters are used to create LUN
paths during volume attach operations for each volume type.

Implements: blueprint hitachi-vsp-tgt-port-asgn
Change-Id: I2e8d80261fa797c8f3c83c078c29cf1173d46c3c
This commit is contained in:
Atsushi Kawai 2022-03-08 03:59:37 +00:00
parent b1fd778cc8
commit 1678260539
9 changed files with 109 additions and 14 deletions

View File

@ -88,6 +88,7 @@ for i in range(4):
volume = {}
volume['id'] = '00000000-0000-0000-0000-{0:012d}'.format(i)
volume['name'] = 'test-volume{0:d}'.format(i)
volume['volume_type_id'] = '00000000-0000-0000-0000-{0:012d}'.format(i)
if i == 3:
volume['provider_location'] = None
else:
@ -98,6 +99,7 @@ for i in range(4):
else:
volume['status'] = 'available'
volume = fake_volume.fake_volume_obj(CTXT, **volume)
volume.volume_type = fake_volume.fake_volume_type_obj(CTXT)
TEST_VOLUME.append(volume)
@ -785,9 +787,13 @@ class HBSDRESTFCDriverTest(test.TestCase):
@mock.patch.object(fczm_utils, "add_fc_zone")
@mock.patch.object(requests.Session, "request")
def test_initialize_connection(self, request, add_fc_zone):
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
def test_initialize_connection(
self, get_volume_type_extra_specs, request, add_fc_zone):
self.driver.common.conf.hitachi_zoning_request = True
self.driver.common._lookup_service = FakeLookupService()
extra_specs = {"hbsd:target_ports": "CL1-A"}
get_volume_type_extra_specs.return_value = extra_specs
request.side_effect = [FakeResponse(200, GET_HOST_WWNS_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
ret = self.driver.initialize_connection(
@ -795,15 +801,20 @@ class HBSDRESTFCDriverTest(test.TestCase):
self.assertEqual('fibre_channel', ret['driver_volume_type'])
self.assertEqual([CONFIG_MAP['target_wwn']], ret['data']['target_wwn'])
self.assertEqual(1, ret['data']['target_lun'])
self.assertEqual(1, get_volume_type_extra_specs.call_count)
self.assertEqual(2, request.call_count)
self.assertEqual(1, add_fc_zone.call_count)
@mock.patch.object(fczm_utils, "add_fc_zone")
@mock.patch.object(requests.Session, "request")
def test_initialize_connection_already_mapped(self, request, add_fc_zone):
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
def test_initialize_connection_already_mapped(
self, get_volume_type_extra_specs, request, add_fc_zone):
"""Normal case: ldev have already mapped."""
self.driver.common.conf.hitachi_zoning_request = True
self.driver.common._lookup_service = FakeLookupService()
extra_specs = {"hbsd:target_ports": "CL1-A"}
get_volume_type_extra_specs.return_value = extra_specs
request.side_effect = [
FakeResponse(200, GET_HOST_WWNS_RESULT),
FakeResponse(202, COMPLETED_FAILED_RESULT_LU_DEFINED),
@ -814,15 +825,20 @@ class HBSDRESTFCDriverTest(test.TestCase):
self.assertEqual('fibre_channel', ret['driver_volume_type'])
self.assertEqual([CONFIG_MAP['target_wwn']], ret['data']['target_wwn'])
self.assertEqual(1, ret['data']['target_lun'])
self.assertEqual(1, get_volume_type_extra_specs.call_count)
self.assertEqual(3, request.call_count)
self.assertEqual(1, add_fc_zone.call_count)
@mock.patch.object(fczm_utils, "add_fc_zone")
@mock.patch.object(requests.Session, "request")
def test_initialize_connection_shared_target(self, request, add_fc_zone):
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
def test_initialize_connection_shared_target(
self, get_volume_type_extra_specs, request, add_fc_zone):
"""Normal case: A target shared with other systems."""
self.driver.common.conf.hitachi_zoning_request = True
self.driver.common._lookup_service = FakeLookupService()
extra_specs = {"hbsd:target_ports": "CL1-A"}
get_volume_type_extra_specs.return_value = extra_specs
request.side_effect = [FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT),
@ -833,6 +849,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
self.assertEqual('fibre_channel', ret['driver_volume_type'])
self.assertEqual([CONFIG_MAP['target_wwn']], ret['data']['target_wwn'])
self.assertEqual(1, ret['data']['target_lun'])
self.assertEqual(1, get_volume_type_extra_specs.call_count)
self.assertEqual(5, request.call_count)
self.assertEqual(1, add_fc_zone.call_count)

View File

@ -79,6 +79,7 @@ for i in range(4):
volume = {}
volume['id'] = '00000000-0000-0000-0000-{0:012d}'.format(i)
volume['name'] = 'test-volume{0:d}'.format(i)
volume['volume_type_id'] = '00000000-0000-0000-0000-{0:012d}'.format(i)
if i == 3:
volume['provider_location'] = None
else:
@ -89,6 +90,7 @@ for i in range(4):
else:
volume['status'] = 'available'
volume = fake_volume.fake_volume_obj(CTXT, **volume)
volume.volume_type = fake_volume.fake_volume_type_obj(CTXT)
TEST_VOLUME.append(volume)
@ -616,7 +618,11 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
self.assertEqual(5, request.call_count)
@mock.patch.object(requests.Session, "request")
def test_initialize_connection(self, request):
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
def test_initialize_connection(
self, get_volume_type_extra_specs, request):
extra_specs = {"hbsd:target_ports": "CL1-A"}
get_volume_type_extra_specs.return_value = extra_specs
request.side_effect = [FakeResponse(200, GET_HOST_ISCSIS_RESULT),
FakeResponse(200, GET_HOST_GROUP_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
@ -636,11 +642,16 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
self.assertEqual(
CONFIG_MAP['auth_password'], ret['data']['auth_password'])
self.assertEqual(1, ret['data']['target_lun'])
self.assertEqual(1, get_volume_type_extra_specs.call_count)
self.assertEqual(3, request.call_count)
@mock.patch.object(requests.Session, "request")
def test_initialize_connection_shared_target(self, request):
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
def test_initialize_connection_shared_target(
self, get_volume_type_extra_specs, request):
"""Normal case: A target shared with other systems."""
extra_specs = {"hbsd:target_ports": "CL1-A"}
get_volume_type_extra_specs.return_value = extra_specs
request.side_effect = [FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT),
FakeResponse(200, GET_HOST_ISCSIS_RESULT),
@ -661,6 +672,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
self.assertEqual(
CONFIG_MAP['auth_password'], ret['data']['auth_password'])
self.assertEqual(1, ret['data']['target_lun'])
self.assertEqual(1, get_volume_type_extra_specs.call_count)
self.assertEqual(4, request.call_count)
@mock.patch.object(requests.Session, "request")

View File

@ -25,6 +25,7 @@ from cinder import exception
from cinder.i18n import _
from cinder.volume import configuration
from cinder.volume.drivers.hitachi import hbsd_utils as utils
from cinder.volume import volume_types
from cinder.volume import volume_utils
_STR_VOLUME = 'volume'
@ -645,7 +646,7 @@ class HBSDCommon():
object='compute target port list',
value=self.storage_info['compute_ports'])
def attach_ldev(self, volume, ldev, connector, targets):
def attach_ldev(self, volume, ldev, connector, is_snapshot, targets):
"""Initialize connection between the server and the volume."""
raise NotImplementedError()
@ -703,7 +704,7 @@ class HBSDCommon():
@coordination.synchronized(
'{self.driver_info[driver_file_prefix]}-host-'
'{self.conf.hitachi_storage_id}-{connector[host]}')
def initialize_connection(self, volume, connector):
def initialize_connection(self, volume, connector, is_snapshot=False):
"""Initialize connection between the server and the volume."""
targets = {
'info': {},
@ -718,7 +719,8 @@ class HBSDCommon():
volume_id=volume['id'])
self.raise_error(msg)
target_lun = self.attach_ldev(volume, ldev, connector, targets)
target_lun = self.attach_ldev(
volume, ldev, connector, is_snapshot, targets)
return {
'driver_volume_type': self.driver_info['volume_type'],
@ -781,6 +783,41 @@ class HBSDCommon():
'data': {'target_wwn': target_wwn}}
return inner(self, volume, connector)
def get_volume_extra_specs(self, volume):
if volume is None:
return {}
type_id = volume.get('volume_type_id')
if type_id is None:
return {}
return volume_types.get_volume_type_extra_specs(type_id)
def filter_target_ports(self, target_ports, volume, is_snapshot=False):
specs = self.get_volume_extra_specs(volume) if volume else None
if not specs:
return target_ports
if self.driver_info.get('driver_dir_name'):
tps_name = self.driver_info['driver_dir_name'] + ':target_ports'
else:
return target_ports
tps = specs.get(tps_name)
if tps is None:
return target_ports
tpsset = set([s.strip() for s in tps.split(',')])
filtered_tps = list(tpsset.intersection(target_ports))
if is_snapshot:
volume = volume['volume']
for port in tpsset:
if port not in target_ports:
utils.output_log(
MSG.INVALID_EXTRA_SPEC_KEY_PORT,
port=port, target_ports_param=tps_name,
volume_type=volume['volume_type']['name'])
return filtered_tps
def unmanage_snapshot(self, snapshot):
"""Output error message and raise NotImplementedError."""
utils.output_log(

View File

@ -42,6 +42,7 @@ _DRIVER_INFO = {
'volume_type': 'fibre_channel',
'param_prefix': utils.PARAM_PREFIX,
'vendor_name': utils.VENDOR_NAME,
'driver_dir_name': utils.DRIVER_DIR_NAME,
'driver_prefix': utils.DRIVER_PREFIX,
'driver_file_prefix': utils.DRIVER_FILE_PREFIX,
'target_prefix': utils.TARGET_PREFIX,
@ -68,6 +69,7 @@ class HBSDFCDriver(driver.FibreChannelDriver):
2.1.0 - Add Cinder generic volume groups.
2.2.0 - Add maintenance parameters.
2.2.1 - Make the parameters name variable for supporting OEM storages.
2.2.2 - Add Target Port Assignment.
"""
@ -223,7 +225,8 @@ class HBSDFCDriver(driver.FibreChannelDriver):
@volume_utils.trace
def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
"""Initialize connection between the server and the snapshot."""
return self.common.initialize_connection(snapshot, connector)
return self.common.initialize_connection(
snapshot, connector, is_snapshot=True)
@volume_utils.trace
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):

View File

@ -42,6 +42,7 @@ _DRIVER_INFO = {
'volume_type': 'iscsi',
'param_prefix': utils.PARAM_PREFIX,
'vendor_name': utils.VENDOR_NAME,
'driver_dir_name': utils.DRIVER_DIR_NAME,
'driver_prefix': utils.DRIVER_PREFIX,
'driver_file_prefix': utils.DRIVER_FILE_PREFIX,
'target_prefix': utils.TARGET_PREFIX,
@ -68,6 +69,7 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
2.1.0 - Add Cinder generic volume groups.
2.2.0 - Add maintenance parameters.
2.2.1 - Make the parameters name variable for supporting OEM storages.
2.2.2 - Add Target Port Assignment.
"""
@ -221,7 +223,8 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
@volume_utils.trace
def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
"""Initialize connection between the server and the snapshot."""
return self.common.initialize_connection(snapshot, connector)
return self.common.initialize_connection(
snapshot, connector, is_snapshot=True)
@volume_utils.trace
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):

View File

@ -560,9 +560,11 @@ class HBSDREST(common.HBSDCommon):
port=port, id=gid, lun=lun)
return lun
def attach_ldev(self, volume, ldev, connector, targets):
def attach_ldev(self, volume, ldev, connector, is_snapshot, targets):
"""Initialize connection between the server and the volume."""
target_ports = self.get_target_ports(connector)
target_ports = self.filter_target_ports(target_ports, volume,
is_snapshot)
if (self.find_targets_from_storage(
targets, connector, target_ports) and
self.conf.hitachi_group_create):

View File

@ -242,10 +242,10 @@ class HBSDRESTFC(rest.HBSDREST):
not_found_count += 1
return not_found_count
def initialize_connection(self, volume, connector):
def initialize_connection(self, volume, connector, is_snapshot=False):
"""Initialize connection between the server and the volume."""
conn_info = super(HBSDRESTFC, self).initialize_connection(
volume, connector)
volume, connector, is_snapshot)
if self.conf.hitachi_zoning_request:
init_targ_map = utils.build_initiator_target_map(
connector, conn_info['data']['target_wwn'],

View File

@ -25,10 +25,11 @@ from oslo_utils import units
from cinder import exception
VERSION = '2.2.1'
VERSION = '2.2.2'
CI_WIKI_NAME = 'Hitachi_VSP_CI'
PARAM_PREFIX = 'hitachi'
VENDOR_NAME = 'Hitachi'
DRIVER_DIR_NAME = 'hbsd'
DRIVER_PREFIX = 'HBSD'
DRIVER_FILE_PREFIX = 'hbsd'
TARGET_PREFIX = 'HBSD-'
@ -172,6 +173,17 @@ class HBSDMsg(enum.Enum):
'reason: %(reason)s)',
'suffix': WARNING_SUFFIX,
}
INVALID_EXTRA_SPEC_KEY_PORT = {
'msg_id': 330,
'loglevel': base_logging.WARNING,
'msg': 'The port name specified for the extra spec key '
'"%(target_ports_param)s" '
'of the volume type is not specified for the '
'target_ports or compute_target_ports '
'parameter in cinder.conf. (port: %(port)s, volume type: '
'%(volume_type)s)',
'suffix': WARNING_SUFFIX,
}
INVALID_PORT = {
'msg_id': 339,
'loglevel': base_logging.WARNING,

View File

@ -0,0 +1,9 @@
---
features:
- |
Hitachi driver: Add target port assignment. Defining particular
ports in extra spec ``hbsd:target_ports`` determines which of
the ports specified by the ``hitachi_target_ports`` or the
``hitachi_compute_target_ports`` parameters are used to create LUN
paths during volume attach operations for each volume type.