Hitachi: add an option for host group name format

This patch is to add an configuration option which specifies
hostgroup(or iSCSI target) name format.

Implements: blueprint hitachi-vsp-add-hostgroup-name-format-option
Change-Id: Icf3c8dc4ba2fd96cda01d778e3a49406fec3b9db
This commit is contained in:
Atsushi Kawai 2023-02-17 18:19:20 +00:00
parent 5a3664ae2e
commit 5363d21d57
11 changed files with 304 additions and 22 deletions

View File

@ -354,6 +354,7 @@ def list_opts():
FJ_ETERNUS_DX_OPT_opts,
cinder_volume_drivers_hitachi_hbsdcommon.COMMON_VOLUME_OPTS,
cinder_volume_drivers_hitachi_hbsdcommon.COMMON_PORT_OPTS,
cinder_volume_drivers_hitachi_hbsdcommon.COMMON_NAME_OPTS,
cinder_volume_drivers_hitachi_hbsdrest.REST_VOLUME_OPTS,
cinder_volume_drivers_hitachi_hbsdrestfc.FC_VOLUME_OPTS,
cinder_volume_drivers_hpe_hpe3parcommon.hpe3par_opts,

View File

@ -470,6 +470,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
self.configuration.hitachi_copy_check_interval = 3
self.configuration.hitachi_async_copy_check_interval = 10
self.configuration.hitachi_port_scheduler = False
self.configuration.hitachi_group_name_format = None
self.configuration.san_login = CONFIG_MAP['user_id']
self.configuration.san_password = CONFIG_MAP['user_pass']
@ -617,6 +618,54 @@ class HBSDRESTFCDriverTest(test.TestCase):
self.driver.common.client.keep_session_loop.stop()
self.driver.common.client.keep_session_loop.wait()
@mock.patch.object(requests.Session, "request")
@mock.patch.object(
volume_utils, 'brick_get_connector_properties',
side_effect=_brick_get_connector_properties)
def test_do_setup_create_hg_format(
self, brick_get_connector_properties, request):
drv = hbsd_fc.HBSDFCDriver(configuration=self.configuration)
self._setup_config()
self.configuration.hitachi_group_name_format = (
'HBSD-{wwn}-{host}-_:.@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@')
request.side_effect = [FakeResponse(200, POST_SESSIONS_RESULT),
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
drv.do_setup(None)
self.assertEqual(
{CONFIG_MAP['port_id']: CONFIG_MAP['target_wwn']},
drv.common.storage_info['wwns'])
self.assertEqual(1, brick_get_connector_properties.call_count)
self.assertEqual(8, request.call_count)
# stop the Loopingcall within the do_setup treatment
self.driver.common.client.keep_session_loop.stop()
self.driver.common.client.keep_session_loop.wait()
@mock.patch.object(requests.Session, "request")
@mock.patch.object(
volume_utils, 'brick_get_connector_properties',
side_effect=_brick_get_connector_properties)
def test_do_setup_create_hg_format_error(
self, brick_get_connector_properties, request):
drv = hbsd_fc.HBSDFCDriver(configuration=self.configuration)
self._setup_config()
self.configuration.hitachi_group_name_format = '{host}-{wwn}'
request.side_effect = [FakeResponse(200, POST_SESSIONS_RESULT),
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
self.assertRaises(exception.VolumeDriverException, drv.do_setup, None)
@mock.patch.object(requests.Session, "request")
@mock.patch.object(
volume_utils, 'brick_get_connector_properties',
@ -1286,6 +1335,7 @@ class HBSDRESTFCDriverTest(test.TestCase):
ret = self.driver.get_driver_options()
actual = (hbsd_common.COMMON_VOLUME_OPTS +
hbsd_common.COMMON_PORT_OPTS +
hbsd_common.COMMON_NAME_OPTS +
hbsd_rest.REST_VOLUME_OPTS +
hbsd_rest_fc.FC_VOLUME_OPTS)
self.assertEqual(actual, ret)

View File

@ -343,6 +343,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
self.configuration.hitachi_copy_check_interval = 3
self.configuration.hitachi_async_copy_check_interval = 10
self.configuration.hitachi_port_scheduler = False
self.configuration.hitachi_group_name_format = None
self.configuration.san_login = CONFIG_MAP['user_id']
self.configuration.san_password = CONFIG_MAP['user_pass']
@ -498,6 +499,56 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
self.driver.common.client.keep_session_loop.stop()
self.driver.common.client.keep_session_loop.wait()
@mock.patch.object(requests.Session, "request")
@mock.patch.object(
volume_utils, 'brick_get_connector_properties',
side_effect=_brick_get_connector_properties)
def test_do_setup_create_hg_format(
self, brick_get_connector_properties, request):
drv = hbsd_iscsi.HBSDISCSIDriver(configuration=self.configuration)
self._setup_config()
self.configuration.hitachi_group_name_format = 'HBSD-{ip}@{host}-_:.'
request.side_effect = [FakeResponse(200, POST_SESSIONS_RESULT),
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, GET_PORT_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
drv.do_setup(None)
self.assertEqual(
{CONFIG_MAP['port_id']:
'%(ip)s:%(port)s' % {
'ip': CONFIG_MAP['ipv4Address'],
'port': CONFIG_MAP['tcpPort']}},
drv.common.storage_info['portals'])
self.assertEqual(1, brick_get_connector_properties.call_count)
self.assertEqual(8, request.call_count)
# stop the Loopingcall within the do_setup treatment
self.driver.common.client.keep_session_loop.stop()
self.driver.common.client.keep_session_loop.wait()
@mock.patch.object(requests.Session, "request")
@mock.patch.object(
volume_utils, 'brick_get_connector_properties',
side_effect=_brick_get_connector_properties)
def test_do_setup_create_hg_format_error(
self, brick_get_connector_properties, request):
drv = hbsd_iscsi.HBSDISCSIDriver(configuration=self.configuration)
self._setup_config()
self.configuration.hitachi_group_name_format = (
'HBSD-{ip}@{host}ZZZZZZZZZZZ')
request.side_effect = [FakeResponse(200, POST_SESSIONS_RESULT),
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, GET_PORT_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
self.assertRaises(exception.VolumeDriverException, drv.do_setup, None)
@mock.patch.object(requests.Session, "request")
def test_extend_volume(self, request):
request.side_effect = [FakeResponse(200, GET_LDEV_RESULT),
@ -989,5 +1040,6 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
_get_oslo_driver_opts.return_value = []
ret = self.driver.get_driver_options()
actual = (hbsd_common.COMMON_VOLUME_OPTS +
hbsd_common.COMMON_NAME_OPTS +
hbsd_rest.REST_VOLUME_OPTS)
self.assertEqual(actual, ret)

View File

@ -28,6 +28,23 @@ from cinder.volume.drivers.hitachi import hbsd_utils as utils
from cinder.volume import volume_types
from cinder.volume import volume_utils
_GROUP_NAME_FORMAT_DEFAULT_FC = utils.TARGET_PREFIX + '{wwn}'
_GROUP_NAME_FORMAT_DEFAULT_ISCSI = utils.TARGET_PREFIX + '{ip}'
_GROUP_NAME_MAX_LEN_FC = 64
_GROUP_NAME_MAX_LEN_ISCSI = 32
GROUP_NAME_ALLOWED_CHARS = 'a-zA-Z0-9.@_:-'
GROUP_NAME_VAR_WWN = '{wwn}'
GROUP_NAME_VAR_IP = '{ip}'
GROUP_NAME_VAR_HOST = '{host}'
_GROUP_NAME_VAR_WWN_LEN = 16
_GROUP_NAME_VAR_IP_LEN = 15
_GROUP_NAME_VAR_HOST_LEN = 1
_GROUP_NAME_VAR_LEN = {GROUP_NAME_VAR_WWN: _GROUP_NAME_VAR_WWN_LEN,
GROUP_NAME_VAR_IP: _GROUP_NAME_VAR_IP_LEN,
GROUP_NAME_VAR_HOST: _GROUP_NAME_VAR_HOST_LEN}
_STR_VOLUME = 'volume'
_STR_SNAPSHOT = 'snapshot'
@ -114,9 +131,38 @@ COMMON_PORT_OPTS = [
'WWNs are registered to ports in a round-robin fashion.'),
]
COMMON_NAME_OPTS = [
cfg.StrOpt(
'hitachi_group_name_format',
default=None,
help='Format of host groups, iSCSI targets, and server objects.'),
]
_GROUP_NAME_FORMAT = {
'FC': {
'group_name_max_len': _GROUP_NAME_MAX_LEN_FC,
'group_name_var_cnt': {
GROUP_NAME_VAR_WWN: [1],
GROUP_NAME_VAR_IP: [0],
GROUP_NAME_VAR_HOST: [0, 1],
},
'group_name_format_default': _GROUP_NAME_FORMAT_DEFAULT_FC,
},
'iSCSI': {
'group_name_max_len': _GROUP_NAME_MAX_LEN_ISCSI,
'group_name_var_cnt': {
GROUP_NAME_VAR_WWN: [0],
GROUP_NAME_VAR_IP: [1],
GROUP_NAME_VAR_HOST: [0, 1],
},
'group_name_format_default': _GROUP_NAME_FORMAT_DEFAULT_ISCSI,
}
}
CONF = cfg.CONF
CONF.register_opts(COMMON_VOLUME_OPTS, group=configuration.SHARED_CONF_GROUP)
CONF.register_opts(COMMON_PORT_OPTS, group=configuration.SHARED_CONF_GROUP)
CONF.register_opts(COMMON_NAME_OPTS, group=configuration.SHARED_CONF_GROUP)
LOG = logging.getLogger(__name__)
MSG = utils.HBSDMsg
@ -159,6 +205,24 @@ class HBSDCommon():
'wwns': {},
'portals': {},
}
self.group_name_format = _GROUP_NAME_FORMAT[driverinfo['proto']]
self.format_info = {
'group_name_format': self.group_name_format[
'group_name_format_default'],
'group_name_format_without_var_len': (
len(re.sub('|'.join([GROUP_NAME_VAR_WWN,
GROUP_NAME_VAR_IP, GROUP_NAME_VAR_HOST]), '',
self.group_name_format['group_name_format_default']))),
'group_name_var_cnt': {
GROUP_NAME_VAR_WWN: self.group_name_format[
'group_name_format_default'].count(GROUP_NAME_VAR_WWN),
GROUP_NAME_VAR_IP: self.group_name_format[
'group_name_format_default'].count(GROUP_NAME_VAR_IP),
GROUP_NAME_VAR_HOST: self.group_name_format[
'group_name_format_default'].count(GROUP_NAME_VAR_HOST),
}
}
self._required_common_opts = [
self.driver_info['param_prefix'] + '_storage_id',
self.driver_info['param_prefix'] + '_pool',
@ -555,6 +619,10 @@ class HBSDCommon():
"""Check parameter values and consistency among them."""
utils.check_opt_value(self.conf, _INHERITED_VOLUME_OPTS)
self.check_opts(self.conf, COMMON_VOLUME_OPTS)
if hasattr(
self.conf,
self.driver_info['param_prefix'] + '_group_name_format'):
self.check_opts(self.conf, COMMON_NAME_OPTS)
if self.conf.hitachi_ldev_range:
self.storage_info['ldev_range'] = self._range2list(
self.driver_info['param_prefix'] + '_ldev_range')
@ -565,6 +633,7 @@ class HBSDCommon():
param=self.driver_info['param_prefix'] + '_target_ports or ' +
self.driver_info['param_prefix'] + '_compute_target_ports')
self.raise_error(msg)
self._check_param_group_name_format()
if (self.conf.hitachi_group_delete and
not self.conf.hitachi_group_create):
msg = utils.output_log(
@ -587,6 +656,50 @@ class HBSDCommon():
if self.storage_info['protocol'] == 'iSCSI':
self.check_param_iscsi()
def _check_param_group_name_format(self):
if not hasattr(
self.conf,
self.driver_info['param_prefix'] + '_group_name_format'):
return
if self.conf.hitachi_group_name_format is not None:
error_flag = False
if re.match(
utils.TARGET_PREFIX + '(' + GROUP_NAME_VAR_WWN + '|' +
GROUP_NAME_VAR_IP + '|' + GROUP_NAME_VAR_HOST + '|' + '[' +
GROUP_NAME_ALLOWED_CHARS + '])+$',
self.conf.hitachi_group_name_format) is None:
error_flag = True
if not error_flag:
for var in _GROUP_NAME_VAR_LEN:
self.format_info['group_name_var_cnt'][var] = (
self.conf.hitachi_group_name_format.count(var))
if (self.format_info[
'group_name_var_cnt'][var] not in
self.group_name_format['group_name_var_cnt'][var]):
error_flag = True
break
if not error_flag:
group_name_var_replaced = self.conf.hitachi_group_name_format
for var, length in _GROUP_NAME_VAR_LEN.items():
group_name_var_replaced = (
group_name_var_replaced.replace(var, '_' * length))
if len(group_name_var_replaced) > self.group_name_format[
'group_name_max_len']:
error_flag = True
if error_flag:
msg = utils.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] +
'_group_name_format')
raise self.raise_error(msg)
self.format_info['group_name_format'] = (
self.conf.hitachi_group_name_format)
self.format_info['group_name_format_without_var_len'] = (
len(re.sub('|'.join(
[GROUP_NAME_VAR_WWN, GROUP_NAME_VAR_IP,
GROUP_NAME_VAR_HOST]), '',
self.format_info['group_name_format'])))
def need_client_setup(self):
"""Check if the making of the communication client is necessary."""
raise NotImplementedError()

View File

@ -73,6 +73,7 @@ class HBSDFCDriver(driver.FibreChannelDriver):
2.2.3 - Add port scheduler.
2.3.0 - Support multi pool.
2.3.1 - Update retype and support storage assisted migration.
2.3.2 - Add specifies format of the names HostGroups/iSCSI Targets.
"""
@ -90,6 +91,7 @@ class HBSDFCDriver(driver.FibreChannelDriver):
self.configuration.append_config_values(common.COMMON_VOLUME_OPTS)
self.configuration.append_config_values(common.COMMON_PORT_OPTS)
self.configuration.append_config_values(common.COMMON_NAME_OPTS)
self.configuration.append_config_values(rest_fc.FC_VOLUME_OPTS)
os.environ['LANG'] = 'C'
self.common = self._init_common(self.configuration, kwargs.get('db'))
@ -106,6 +108,7 @@ class HBSDFCDriver(driver.FibreChannelDriver):
'san_api_port', ]))
return (common.COMMON_VOLUME_OPTS +
common.COMMON_PORT_OPTS +
common.COMMON_NAME_OPTS +
rest.REST_VOLUME_OPTS +
rest_fc.FC_VOLUME_OPTS +
additional_opts)

View File

@ -73,6 +73,7 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
2.2.3 - Add port scheduler.
2.3.0 - Support multi pool.
2.3.1 - Update retype and support storage assisted migration.
2.3.2 - Add specifies format of the names HostGroups/iSCSI Targets.
"""
@ -89,6 +90,7 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
super(HBSDISCSIDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(common.COMMON_VOLUME_OPTS)
self.configuration.append_config_values(common.COMMON_NAME_OPTS)
os.environ['LANG'] = 'C'
self.common = self._init_common(self.configuration, kwargs.get('db'))
@ -103,6 +105,7 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
['driver_ssl_cert_verify', 'driver_ssl_cert_path',
'san_api_port', ]))
return (common.COMMON_VOLUME_OPTS +
common.COMMON_NAME_OPTS +
rest.REST_VOLUME_OPTS +
additional_opts)

View File

@ -15,6 +15,7 @@
"""REST interface module for Hitachi HBSD Driver."""
from collections import defaultdict
import re
from oslo_config import cfg
from oslo_config import types
@ -34,6 +35,9 @@ from cinder.volume.drivers.hitachi import hbsd_utils as utils
from cinder.volume.drivers.san import san
from cinder.volume import volume_utils
_GROUP_NAME_PROHIBITED_CHAR_PATTERN = re.compile(
'[^' + common.GROUP_NAME_ALLOWED_CHARS + ']')
_LU_PATH_DEFINED = ('B958', '015A')
NORMAL_STS = 'NML'
_LUN_TIMEOUT = 50
@ -1312,3 +1316,21 @@ class HBSDREST(common.HBSDCommon):
msg = utils.output_log(
MSG.VOLUME_COPY_FAILED, pvol=pvol, svol=svol)
self.raise_error(msg)
def create_target_name(self, connector):
wwn = (min(self.get_hba_ids_from_connector(connector)) if
self.format_info['group_name_var_cnt'][
common.GROUP_NAME_VAR_WWN] else '')
ip = (connector['ip'] if self.format_info[
'group_name_var_cnt'][common.GROUP_NAME_VAR_IP] else '')
if not self.format_info['group_name_var_cnt'][
common.GROUP_NAME_VAR_HOST]:
return self.format_info['group_name_format'].format(wwn=wwn, ip=ip)
host = connector['host'] if 'host' in connector else ''
max_host_len = (self.group_name_format['group_name_max_len'] -
self.format_info['group_name_format_without_var_len'] -
len(wwn) - len(ip))
host = _GROUP_NAME_PROHIBITED_CHAR_PATTERN.sub(
'_', host[:max_host_len])
return self.format_info['group_name_format'].format(
host=host, wwn=wwn, ip=ip)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2020, 2021, Hitachi, Ltd.
# Copyright (C) 2020, 2023, Hitachi, Ltd.
#
# 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
@ -128,11 +128,7 @@ class HBSDRESTFC(rest.HBSDREST):
def create_target_to_storage(self, port, connector, hba_ids):
"""Create a host group on the specified port."""
wwpns = self.get_hba_ids_from_connector(connector)
target_name = '%(prefix)s-%(wwpns)s' % {
'prefix': self.driver_info['driver_prefix'],
'wwpns': min(wwpns),
}
target_name = self.create_target_name(connector)
try:
body = {'portId': port,
'hostGroupName': target_name}
@ -232,12 +228,7 @@ class HBSDRESTFC(rest.HBSDREST):
self, targets, connector, target_ports):
"""Find mapped ports, memorize them and return unmapped port count."""
wwpns = self.get_hba_ids_from_connector(connector)
target_names = [
'%(prefix)s-%(wwpns)s' % {
'prefix': self.driver_info['driver_prefix'],
'wwpns': min(wwpns),
}
]
target_names = [self.create_target_name(connector)]
if 'ip' in connector:
target_names.append(
'%(prefix)s-%(ip)s' % {

View File

@ -1,4 +1,4 @@
# Copyright (C) 2020, 2021, Hitachi, Ltd.
# Copyright (C) 2020, 2023, Hitachi, Ltd.
#
# 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
@ -90,10 +90,7 @@ class HBSDRESTISCSI(rest.HBSDREST):
def create_target_to_storage(self, port, connector, hba_ids):
"""Create an iSCSI target on the specified port."""
target_name = '%(prefix)s-%(ip)s' % {
'prefix': self.driver_info['driver_prefix'],
'ip': connector['ip'],
}
target_name = self.create_target_name(connector)
body = {'portId': port, 'hostGroupName': target_name}
if hba_ids:
body['iscsiName'] = '%(id)s%(suffix)s' % {
@ -183,10 +180,7 @@ class HBSDRESTISCSI(rest.HBSDREST):
for port in target_ports:
targets['info'][port] = False
if 'ip' in connector:
target_name = '%(prefix)s-%(ip)s' % {
'prefix': self.driver_info['driver_prefix'],
'ip': connector['ip'],
}
target_name = self.create_target_name(connector)
if self._set_target_info_by_name(
targets, port, target_name, iqn):
continue

View File

@ -25,7 +25,7 @@ from oslo_utils import units
from cinder import exception
VERSION = '2.3.1'
VERSION = '2.3.2'
CI_WIKI_NAME = 'Hitachi_VSP_CI'
PARAM_PREFIX = 'hitachi'
VENDOR_NAME = 'Hitachi'

View File

@ -0,0 +1,53 @@
---
features:
- |
Hitachi driver: Add a config option ``hitachi_group_name_format`` for
hostgroup name format.
When using this option, users can specify the name format of
host groups or iSCSI targets.
Rules of the format:
* Usable characters are alphanumerics, ".", "@", "_", ":", "-",
"{" and "}". "{" and "}" can be used only in variables.
* The specified value must start with ``HBSD-``.
* You can use the following variables:
* ``{wwn}``: FC driver only. This is replaced with the smallest
WWPN of the WWPNs of the connecting node.
* ``{ip}``: iSCSI driver only. This is replaced with the IP address of
the connecting node.
* ``{host}``: This is replaced with the host name of the connecting node.
* You can use each variable in the specified value no more than once.
* The specified value must include the following variables:
* FC driver: ``{wwn}``
* iSCSI driver: ``{ip}``
* The maximum length of a specified value is as follows:
* FC driver: 64
* iSCSI driver: 32
* In the length calculation, use the following values as the length of
each variable:
* ``{wwn}``: 16
* ``{ip}``: 15
* ``{host}``: 1
* If the specified value includes ``{host}``, the following rules apply:
* characters that are not permitted for this parameter, they are
replaced with ``_``.
* If the length of the name after variable replacement exceeds
the maximum length of host group (iSCSI target) names, the
host name is truncated so that the length of the host groups or
iSCSI targets do not exceed the maximum length.
If you specify this parameter, it is recommended that you specify ``True``
for the hitachi_group_create parameter to collect necessary
information automatically.
Examples:
* FC driver: ``HBSD-{host}-{wwn}``
* iSCSI driver: ``HBSD-{host}-{ip}``