Hitachi: add GAD volume support

This patch adds Global-Active-Device("GAD")(*) volume support for
Hitachi VSP driver.
New properties will be added in configuration:
hbsd:topology sets to "active_active_mirror_volume" would specify a GAD
volume.
hitachi_mirror_xxx parameters would specify a secondary storage for GAD
volume.

(*) GAD is one of Hitachi storage product.
It can use volume replication to provide a HA environment for hosts
across systems and sites.

Implements: blueprint hitachi-gad-support
Change-Id: I4543cd036897b4db8b04011b808dd5af34439153
This commit is contained in:
Atsushi Kawai 2023-02-20 05:56:27 +00:00
parent e6637b94fa
commit a92aa06e46
14 changed files with 3606 additions and 311 deletions

View File

@ -106,6 +106,8 @@ from cinder.volume.drivers.fusionstorage import dsware as \
cinder_volume_drivers_fusionstorage_dsware
from cinder.volume.drivers.hitachi import hbsd_common as \
cinder_volume_drivers_hitachi_hbsdcommon
from cinder.volume.drivers.hitachi import hbsd_replication as \
cinder_volume_drivers_hitachi_hbsdreplication
from cinder.volume.drivers.hitachi import hbsd_rest as \
cinder_volume_drivers_hitachi_hbsdrest
from cinder.volume.drivers.hitachi import hbsd_rest_fc as \
@ -291,6 +293,17 @@ def list_opts():
cinder_volume_drivers_datera_dateraiscsi.d_opts,
cinder_volume_drivers_fungible_driver.fungible_opts,
cinder_volume_drivers_fusionstorage_dsware.volume_opts,
cinder_volume_drivers_hitachi_hbsdreplication._REP_OPTS,
cinder_volume_drivers_hitachi_hbsdreplication.
COMMON_MIRROR_OPTS,
cinder_volume_drivers_hitachi_hbsdreplication.
ISCSI_MIRROR_OPTS,
cinder_volume_drivers_hitachi_hbsdreplication.
REST_MIRROR_OPTS,
cinder_volume_drivers_hitachi_hbsdreplication.
REST_MIRROR_API_OPTS,
cinder_volume_drivers_hitachi_hbsdreplication.
REST_MIRROR_SSL_OPTS,
cinder_volume_drivers_infortrend_raidcmd_cli_commoncli.
infortrend_opts,
cinder_volume_drivers_inspur_as13000_as13000driver.
@ -356,8 +369,10 @@ 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_PAIR_OPTS,
cinder_volume_drivers_hitachi_hbsdcommon.COMMON_NAME_OPTS,
cinder_volume_drivers_hitachi_hbsdrest.REST_VOLUME_OPTS,
cinder_volume_drivers_hitachi_hbsdrest.REST_PAIR_OPTS,
cinder_volume_drivers_hitachi_hbsdrestfc.FC_VOLUME_OPTS,
cinder_volume_drivers_hpe_hpe3parcommon.hpe3par_opts,
cinder_volume_drivers_hpe_nimble.nimble_opts,

File diff suppressed because it is too large Load Diff

View File

@ -36,6 +36,7 @@ from cinder.volume import configuration as conf
from cinder.volume import driver
from cinder.volume.drivers.hitachi import hbsd_common
from cinder.volume.drivers.hitachi import hbsd_fc
from cinder.volume.drivers.hitachi import hbsd_replication
from cinder.volume.drivers.hitachi import hbsd_rest
from cinder.volume.drivers.hitachi import hbsd_rest_api
from cinder.volume.drivers.hitachi import hbsd_rest_fc
@ -182,6 +183,16 @@ GET_HOST_WWNS_RESULT = {
],
}
GET_HOST_GROUPS_RESULT_TEST = {
"data": [
{
"hostGroupNumber": 0,
"portId": CONFIG_MAP['port_id'],
"hostGroupName": CONFIG_MAP['host_grp_name'],
},
],
}
COMPLETED_SUCCEEDED_RESULT = {
"status": "Completed",
"state": "Succeeded",
@ -307,6 +318,16 @@ GET_HOST_GROUPS_RESULT = {
],
}
GET_HOST_GROUPS_RESULT_PAIR = {
"data": [
{
"hostGroupNumber": 1,
"portId": CONFIG_MAP['port_id'],
"hostGroupName": "HBSD-pair00",
},
],
}
GET_LDEVS_RESULT = {
"data": [
{
@ -481,7 +502,6 @@ class HBSDRESTFCDriverTest(test.TestCase):
self.configuration.hitachi_rest_disable_io_wait = True
self.configuration.hitachi_rest_tcp_keepalive = True
self.configuration.hitachi_discard_zero_page = True
self.configuration.hitachi_rest_number = "0"
self.configuration.hitachi_lun_timeout = hbsd_rest._LUN_TIMEOUT
self.configuration.hitachi_lun_retry_interval = (
hbsd_rest._LUN_RETRY_INTERVAL)
@ -529,6 +549,21 @@ class HBSDRESTFCDriverTest(test.TestCase):
self.configuration.chap_username = CONFIG_MAP['auth_user']
self.configuration.chap_password = CONFIG_MAP['auth_password']
self.configuration.hitachi_replication_number = 0
self.configuration.hitachi_pair_target_number = 0
self.configuration.hitachi_rest_pair_target_ports = []
self.configuration.hitachi_quorum_disk_id = ''
self.configuration.hitachi_mirror_copy_speed = ''
self.configuration.hitachi_mirror_storage_id = ''
self.configuration.hitachi_mirror_pool = ''
self.configuration.hitachi_mirror_ldev_range = ''
self.configuration.hitachi_mirror_target_ports = ''
self.configuration.hitachi_mirror_rest_user = ''
self.configuration.hitachi_mirror_rest_password = ''
self.configuration.hitachi_mirror_rest_api_ip = ''
self.configuration.hitachi_set_mirror_reserve_attribute = ''
self.configuration.hitachi_path_group_id = ''
self.configuration.safe_get = self._fake_safe_get
CONF = cfg.CONF
@ -553,7 +588,8 @@ class HBSDRESTFCDriverTest(test.TestCase):
configuration=self.configuration)
request.side_effect = [FakeResponse(200, POST_SESSIONS_RESULT),
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, GET_HOST_WWNS_RESULT)]
FakeResponse(200, GET_HOST_WWNS_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
self.driver.do_setup(None)
self.driver.check_for_setup_error()
self.driver.local_path(None)
@ -580,13 +616,14 @@ class HBSDRESTFCDriverTest(test.TestCase):
self._setup_config()
request.side_effect = [FakeResponse(200, POST_SESSIONS_RESULT),
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, GET_HOST_WWNS_RESULT)]
FakeResponse(200, GET_HOST_WWNS_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
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(3, request.call_count)
self.assertEqual(4, 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()
@ -607,13 +644,14 @@ class HBSDRESTFCDriverTest(test.TestCase):
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
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)
self.assertEqual(9, 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()
@ -635,13 +673,14 @@ class HBSDRESTFCDriverTest(test.TestCase):
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
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)
self.assertEqual(9, 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()
@ -687,13 +726,14 @@ class HBSDRESTFCDriverTest(test.TestCase):
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
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(9, request.call_count)
self.assertEqual(10, 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()
@ -712,13 +752,14 @@ class HBSDRESTFCDriverTest(test.TestCase):
request.side_effect = [FakeResponse(200, POST_SESSIONS_RESULT),
FakeResponse(200, GET_POOLS_RESULT),
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, GET_HOST_WWNS_RESULT)]
FakeResponse(200, GET_HOST_WWNS_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
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(4, request.call_count)
self.assertEqual(5, request.call_count)
self.configuration.hitachi_pool = tmp_pool
# stop the Loopingcall within the do_setup treatment
self.driver.common.client.keep_session_loop.stop()
@ -835,9 +876,13 @@ class HBSDRESTFCDriverTest(test.TestCase):
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
self.driver.delete_snapshot(TEST_SNAPSHOT[0])
self.assertEqual(10, request.call_count)
self.assertEqual(14, request.call_count)
@mock.patch.object(requests.Session, "request")
def test_delete_snapshot_no_pair(self, request):
@ -946,6 +991,32 @@ class HBSDRESTFCDriverTest(test.TestCase):
self.assertEqual(5, 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")
@mock.patch.object(volume_types, 'get_volume_type_extra_specs')
def test_create_target_to_storage_return(
self, get_volume_type_extra_specs, request, add_fc_zone):
self.configuration.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),
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(400, GET_HOST_GROUPS_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_TEST),
FakeResponse(200, GET_HOST_GROUPS_RESULT_TEST),
]
self.assertRaises(exception.VolumeDriverException,
self.driver.initialize_connection,
TEST_VOLUME[1],
DEFAULT_CONNECTOR)
self.assertEqual(1, get_volume_type_extra_specs.call_count)
self.assertEqual(10, request.call_count)
self.assertEqual(0, add_fc_zone.call_count)
@mock.patch.object(fczm_utils, "remove_fc_zone")
@mock.patch.object(requests.Session, "request")
def test_terminate_connection(self, request, remove_fc_zone):
@ -1319,10 +1390,14 @@ class HBSDRESTFCDriverTest(test.TestCase):
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
ret = self.driver.delete_group_snapshot(
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]])
self.assertEqual(10, request.call_count)
self.assertEqual(14, request.call_count)
actual = (
{'status': TEST_GROUP_SNAP[0]['status']},
[{'id': TEST_SNAPSHOT[0]['id'], 'status': 'deleted'}]
@ -1335,7 +1410,15 @@ class HBSDRESTFCDriverTest(test.TestCase):
ret = self.driver.get_driver_options()
actual = (hbsd_common.COMMON_VOLUME_OPTS +
hbsd_common.COMMON_PORT_OPTS +
hbsd_common.COMMON_PAIR_OPTS +
hbsd_common.COMMON_NAME_OPTS +
hbsd_rest.REST_VOLUME_OPTS +
hbsd_rest_fc.FC_VOLUME_OPTS)
hbsd_rest.REST_PAIR_OPTS +
hbsd_rest_fc.FC_VOLUME_OPTS +
hbsd_replication._REP_OPTS +
hbsd_replication.COMMON_MIRROR_OPTS +
hbsd_replication.ISCSI_MIRROR_OPTS +
hbsd_replication.REST_MIRROR_OPTS +
hbsd_replication.REST_MIRROR_API_OPTS +
hbsd_replication.REST_MIRROR_SSL_OPTS)
self.assertEqual(actual, ret)

View File

@ -33,6 +33,7 @@ from cinder.volume import configuration as conf
from cinder.volume import driver
from cinder.volume.drivers.hitachi import hbsd_common
from cinder.volume.drivers.hitachi import hbsd_iscsi
from cinder.volume.drivers.hitachi import hbsd_replication
from cinder.volume.drivers.hitachi import hbsd_rest
from cinder.volume.drivers.hitachi import hbsd_rest_api
from cinder.volume import volume_types
@ -252,6 +253,16 @@ GET_SNAPSHOTS_RESULT_PAIR = {
],
}
GET_HOST_GROUPS_RESULT_PAIR = {
"data": [
{
"hostGroupNumber": 1,
"portId": CONFIG_MAP['port_id'],
"hostGroupName": "HBSD-pair00",
},
],
}
GET_LDEVS_RESULT = {
"data": [
{
@ -354,7 +365,6 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
self.configuration.hitachi_rest_disable_io_wait = True
self.configuration.hitachi_rest_tcp_keepalive = True
self.configuration.hitachi_discard_zero_page = True
self.configuration.hitachi_rest_number = "0"
self.configuration.hitachi_lun_timeout = hbsd_rest._LUN_TIMEOUT
self.configuration.hitachi_lun_retry_interval = (
hbsd_rest._LUN_RETRY_INTERVAL)
@ -400,6 +410,21 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
self.configuration.ssh_min_pool_conn = '1'
self.configuration.ssh_max_pool_conn = '5'
self.configuration.hitachi_replication_number = 0
self.configuration.hitachi_pair_target_number = 0
self.configuration.hitachi_rest_pair_target_ports = []
self.configuration.hitachi_quorum_disk_id = ''
self.configuration.hitachi_mirror_copy_speed = ''
self.configuration.hitachi_mirror_storage_id = ''
self.configuration.hitachi_mirror_pool = ''
self.configuration.hitachi_mirror_ldev_range = ''
self.configuration.hitachi_mirror_target_ports = ''
self.configuration.hitachi_mirror_rest_user = ''
self.configuration.hitachi_mirror_rest_password = ''
self.configuration.hitachi_mirror_rest_api_ip = ''
self.configuration.hitachi_set_mirror_reserve_attribute = ''
self.configuration.hitachi_path_group_id = ''
self.configuration.safe_get = self._fake_safe_get
CONF = cfg.CONF
@ -426,7 +451,8 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, GET_PORT_RESULT),
FakeResponse(200, GET_HOST_ISCSIS_RESULT),
FakeResponse(200, GET_HOST_GROUP_RESULT)]
FakeResponse(200, GET_HOST_GROUP_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
self.driver.do_setup(None)
self.driver.check_for_setup_error()
self.driver.local_path(None)
@ -455,7 +481,8 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
FakeResponse(200, GET_PORTS_RESULT),
FakeResponse(200, GET_PORT_RESULT),
FakeResponse(200, GET_HOST_ISCSIS_RESULT),
FakeResponse(200, GET_HOST_GROUP_RESULT)]
FakeResponse(200, GET_HOST_GROUP_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
drv.do_setup(None)
self.assertEqual(
{CONFIG_MAP['port_id']:
@ -464,7 +491,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
'port': CONFIG_MAP['tcpPort']}},
drv.common.storage_info['portals'])
self.assertEqual(1, brick_get_connector_properties.call_count)
self.assertEqual(5, request.call_count)
self.assertEqual(6, 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()
@ -485,7 +512,8 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
drv.do_setup(None)
self.assertEqual(
{CONFIG_MAP['port_id']:
@ -494,7 +522,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
'port': CONFIG_MAP['tcpPort']}},
drv.common.storage_info['portals'])
self.assertEqual(1, brick_get_connector_properties.call_count)
self.assertEqual(8, request.call_count)
self.assertEqual(9, 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()
@ -515,7 +543,8 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
FakeResponse(200, NOTFOUND_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT),
FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR)]
drv.do_setup(None)
self.assertEqual(
{CONFIG_MAP['port_id']:
@ -524,7 +553,7 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
'port': CONFIG_MAP['tcpPort']}},
drv.common.storage_info['portals'])
self.assertEqual(1, brick_get_connector_properties.call_count)
self.assertEqual(8, request.call_count)
self.assertEqual(9, 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()
@ -1025,10 +1054,14 @@ class HBSDRESTISCSIDriverTest(test.TestCase):
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(200, GET_LDEV_RESULT),
FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)]
ret = self.driver.delete_group_snapshot(
self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]])
self.assertEqual(10, request.call_count)
self.assertEqual(14, request.call_count)
actual = (
{'status': TEST_GROUP_SNAP[0]['status']},
[{'id': TEST_SNAPSHOT[0]['id'], 'status': 'deleted'}]
@ -1040,6 +1073,14 @@ 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_PAIR_OPTS +
hbsd_common.COMMON_NAME_OPTS +
hbsd_rest.REST_VOLUME_OPTS)
hbsd_rest.REST_VOLUME_OPTS +
hbsd_rest.REST_PAIR_OPTS +
hbsd_replication._REP_OPTS +
hbsd_replication.COMMON_MIRROR_OPTS +
hbsd_replication.ISCSI_MIRROR_OPTS +
hbsd_replication.REST_MIRROR_OPTS +
hbsd_replication.REST_MIRROR_API_OPTS +
hbsd_replication.REST_MIRROR_SSL_OPTS)
self.assertEqual(actual, ret)

View File

@ -14,6 +14,7 @@
#
"""Common module for Hitachi HBSD Driver."""
import json
import re
from oslo_config import cfg
@ -45,8 +46,8 @@ _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'
STR_VOLUME = 'volume'
STR_SNAPSHOT = 'snapshot'
_INHERITED_VOLUME_OPTS = [
'volume_backend_name',
@ -131,6 +132,13 @@ COMMON_PORT_OPTS = [
'WWNs are registered to ports in a round-robin fashion.'),
]
COMMON_PAIR_OPTS = [
cfg.IntOpt(
'hitachi_pair_target_number',
default=0, min=0, max=99,
help='Pair target name of the host group or iSCSI target'),
]
COMMON_NAME_OPTS = [
cfg.StrOpt(
'hitachi_group_name_format',
@ -162,13 +170,14 @@ _GROUP_NAME_FORMAT = {
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_PAIR_OPTS, group=configuration.SHARED_CONF_GROUP)
CONF.register_opts(COMMON_NAME_OPTS, group=configuration.SHARED_CONF_GROUP)
LOG = logging.getLogger(__name__)
MSG = utils.HBSDMsg
def _str2int(num):
def str2int(num):
"""Convert a string into an integer."""
if not num:
return None
@ -202,9 +211,11 @@ class HBSDCommon():
'ldev_range': [],
'controller_ports': [],
'compute_ports': [],
'pair_ports': [],
'wwns': {},
'portals': {},
}
self.storage_id = None
self.group_name_format = _GROUP_NAME_FORMAT[driverinfo['proto']]
self.format_info = {
'group_name_format': self.group_name_format[
@ -255,7 +266,7 @@ class HBSDCommon():
ldev = self.create_ldev(volume['size'], pool_id, ldev_range)
except Exception:
with excutils.save_and_reraise_exception():
utils.output_log(MSG.CREATE_LDEV_FAILED)
self.output_log(MSG.CREATE_LDEV_FAILED)
self.modify_ldev_name(ldev, volume['id'].replace("-", ""))
return {
'provider_location': str(ldev),
@ -276,33 +287,33 @@ class HBSDCommon():
def copy_on_storage(
self, pvol, size, pool_id, snap_pool_id, ldev_range,
is_snapshot=False, sync=False):
is_snapshot=False, sync=False, is_rep=False):
"""Create a copy of the specified LDEV on the storage."""
ldev_info = self.get_ldev_info(['status', 'attributes'], pvol)
if ldev_info['status'] != 'NML':
msg = utils.output_log(MSG.INVALID_LDEV_STATUS_FOR_COPY, ldev=pvol)
msg = self.output_log(MSG.INVALID_LDEV_STATUS_FOR_COPY, ldev=pvol)
self.raise_error(msg)
svol = self.create_ldev(size, pool_id, ldev_range)
try:
self.create_pair_on_storage(
pvol, svol, snap_pool_id, is_snapshot=is_snapshot)
if sync:
if sync or is_rep:
self.wait_copy_completion(pvol, svol)
except Exception:
with excutils.save_and_reraise_exception():
try:
self.delete_ldev(svol)
except exception.VolumeDriverException:
utils.output_log(MSG.DELETE_LDEV_FAILED, ldev=svol)
self.output_log(MSG.DELETE_LDEV_FAILED, ldev=svol)
return svol
def create_volume_from_src(self, volume, src, src_type):
def create_volume_from_src(self, volume, src, src_type, is_rep=False):
"""Create a volume from a volume or snapshot and return its properties.
"""
ldev = utils.get_ldev(src)
ldev = self.get_ldev(src)
if ldev is None:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY, type=src_type, id=src['id'])
self.raise_error(msg)
@ -311,8 +322,10 @@ class HBSDCommon():
snap_pool_id = self.storage_info['snap_pool_id']
ldev_range = self.storage_info['ldev_range']
new_ldev = self.copy_on_storage(
ldev, size, pool_id, snap_pool_id, ldev_range)
ldev, size, pool_id, snap_pool_id, ldev_range, is_rep=is_rep)
self.modify_ldev_name(new_ldev, volume['id'].replace("-", ""))
if is_rep:
self.delete_pair(new_ldev)
return {
'provider_location': str(new_ldev),
@ -320,11 +333,11 @@ class HBSDCommon():
def create_cloned_volume(self, volume, src_vref):
"""Create a clone of the specified volume and return its properties."""
return self.create_volume_from_src(volume, src_vref, _STR_VOLUME)
return self.create_volume_from_src(volume, src_vref, STR_VOLUME)
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot and return its properties."""
return self.create_volume_from_src(volume, snapshot, _STR_SNAPSHOT)
return self.create_volume_from_src(volume, snapshot, STR_SNAPSHOT)
def delete_pair_based_on_svol(self, pvol, svol_info):
"""Disconnect all volume pairs to which the specified S-VOL belongs."""
@ -340,7 +353,7 @@ class HBSDCommon():
if not pair_info:
return
if pair_info['pvol'] == ldev:
utils.output_log(
self.output_log(
MSG.UNABLE_TO_DELETE_PAIR, pvol=pair_info['pvol'])
self.raise_busy()
else:
@ -375,9 +388,9 @@ class HBSDCommon():
def delete_volume(self, volume):
"""Delete the specified volume."""
ldev = utils.get_ldev(volume)
ldev = self.get_ldev(volume)
if ldev is None:
utils.output_log(
self.output_log(
MSG.INVALID_LDEV_FOR_DELETION,
method='delete_volume', id=volume['id'])
return
@ -392,9 +405,9 @@ class HBSDCommon():
def create_snapshot(self, snapshot):
"""Create a snapshot from a volume and return its properties."""
src_vref = snapshot.volume
ldev = utils.get_ldev(src_vref)
ldev = self.get_ldev(src_vref)
if ldev is None:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY,
type='volume', id=src_vref['id'])
self.raise_error(msg)
@ -410,9 +423,9 @@ class HBSDCommon():
def delete_snapshot(self, snapshot):
"""Delete the specified snapshot."""
ldev = utils.get_ldev(snapshot)
ldev = self.get_ldev(snapshot)
if ldev is None:
utils.output_log(
self.output_log(
MSG.INVALID_LDEV_FOR_DELETION, method='delete_snapshot',
id=snapshot['id'])
return
@ -453,7 +466,7 @@ class HBSDCommon():
single_pool.update(dict(
provisioned_capacity_gb=0,
backend_state='down'))
utils.output_log(MSG.POOL_INFO_RETRIEVAL_FAILED, pool=pool_name)
self.output_log(MSG.POOL_INFO_RETRIEVAL_FAILED, pool=pool_name)
return single_pool
total_capacity, free_capacity, provisioned_capacity = cap_data
single_pool.update(dict(
@ -506,14 +519,14 @@ class HBSDCommon():
def extend_volume(self, volume, new_size):
"""Extend the specified volume to the specified size."""
ldev = utils.get_ldev(volume)
ldev = self.get_ldev(volume)
if ldev is None:
msg = utils.output_log(MSG.INVALID_LDEV_FOR_EXTENSION,
volume_id=volume['id'])
msg = self.output_log(MSG.INVALID_LDEV_FOR_EXTENSION,
volume_id=volume['id'])
self.raise_error(msg)
if self.check_pair_svol(ldev):
msg = utils.output_log(MSG.INVALID_VOLUME_TYPE_FOR_EXTEND,
volume_id=volume['id'])
msg = self.output_log(MSG.INVALID_VOLUME_TYPE_FOR_EXTEND,
volume_id=volume['id'])
self.raise_error(msg)
self.delete_pair(ldev)
self.extend_ldev(ldev, volume['size'], new_size)
@ -532,7 +545,7 @@ class HBSDCommon():
ldev = self.get_ldev_by_name(
existing_ref.get('source-name').replace('-', ''))
elif 'source-id' in existing_ref:
ldev = _str2int(existing_ref.get('source-id'))
ldev = str2int(existing_ref.get('source-id'))
self.check_ldev_manageability(ldev, existing_ref)
self.modify_ldev_name(ldev, volume['id'].replace("-", ""))
return {
@ -543,29 +556,29 @@ class HBSDCommon():
"""Return the size[GB] of the specified LDEV."""
raise NotImplementedError()
def manage_existing_get_size(self, existing_ref):
def manage_existing_get_size(self, volume, existing_ref):
"""Return the size[GB] of the specified volume."""
ldev = None
if 'source-name' in existing_ref:
ldev = self.get_ldev_by_name(
existing_ref.get('source-name').replace("-", ""))
elif 'source-id' in existing_ref:
ldev = _str2int(existing_ref.get('source-id'))
ldev = str2int(existing_ref.get('source-id'))
if ldev is None:
msg = utils.output_log(MSG.INVALID_LDEV_FOR_MANAGE)
msg = self.output_log(MSG.INVALID_LDEV_FOR_MANAGE)
raise exception.ManageExistingInvalidReference(
existing_ref=existing_ref, reason=msg)
return self.get_ldev_size_in_gigabyte(ldev, existing_ref)
def unmanage(self, volume):
"""Prepare the volume for removing it from Cinder management."""
ldev = utils.get_ldev(volume)
ldev = self.get_ldev(volume)
if ldev is None:
utils.output_log(MSG.INVALID_LDEV_FOR_DELETION, method='unmanage',
id=volume['id'])
self.output_log(MSG.INVALID_LDEV_FOR_DELETION, method='unmanage',
id=volume['id'])
return
if self.check_pair_svol(ldev):
utils.output_log(
self.output_log(
MSG.INVALID_LDEV_TYPE_FOR_UNMANAGE, volume_id=volume['id'],
volume_type=utils.NORMAL_LDEV_TYPE)
raise exception.VolumeIsBusy(volume_name=volume['name'])
@ -579,10 +592,10 @@ class HBSDCommon():
def _range2list(self, param):
"""Analyze a 'xxx-xxx' string and return a list of two integers."""
values = [_str2int(value) for value in
values = [str2int(value) for value in
self.conf.safe_get(param).split('-')]
if len(values) != 2 or None in values or values[0] > values[1]:
msg = utils.output_log(MSG.INVALID_PARAMETER, param=param)
msg = self.output_log(MSG.INVALID_PARAMETER, param=param)
self.raise_error(msg)
return values
@ -594,31 +607,35 @@ class HBSDCommon():
self.check_opts(self.conf, COMMON_PORT_OPTS)
if (self.conf.hitachi_port_scheduler and
not self.conf.hitachi_group_create):
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] + '_port_scheduler')
self.raise_error(msg)
if (self._lookup_service is None and
self.conf.hitachi_port_scheduler):
msg = utils.output_log(MSG.ZONE_MANAGER_IS_NOT_AVAILABLE)
msg = self.output_log(MSG.ZONE_MANAGER_IS_NOT_AVAILABLE)
self.raise_error(msg)
def check_param_iscsi(self):
"""Check iSCSI-related parameter values and consistency among them."""
if self.conf.use_chap_auth:
if not self.conf.chap_username:
msg = utils.output_log(MSG.INVALID_PARAMETER,
param='chap_username')
msg = self.output_log(MSG.INVALID_PARAMETER,
param='chap_username')
self.raise_error(msg)
if not self.conf.chap_password:
msg = utils.output_log(MSG.INVALID_PARAMETER,
param='chap_password')
msg = self.output_log(MSG.INVALID_PARAMETER,
param='chap_password')
self.raise_error(msg)
def check_param(self):
"""Check parameter values and consistency among them."""
utils.check_opt_value(self.conf, _INHERITED_VOLUME_OPTS)
self.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'] + '_pair_target_number'):
self.check_opts(self.conf, COMMON_PAIR_OPTS)
if hasattr(
self.conf,
self.driver_info['param_prefix'] + '_group_name_format'):
@ -628,7 +645,7 @@ class HBSDCommon():
self.driver_info['param_prefix'] + '_ldev_range')
if (not self.conf.hitachi_target_ports and
not self.conf.hitachi_compute_target_ports):
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] + '_target_ports or ' +
self.driver_info['param_prefix'] + '_compute_target_ports')
@ -636,18 +653,18 @@ class HBSDCommon():
self._check_param_group_name_format()
if (self.conf.hitachi_group_delete and
not self.conf.hitachi_group_create):
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] + '_group_delete or '
+ self.driver_info['param_prefix'] + '_group_create')
self.raise_error(msg)
for opt in self._required_common_opts:
if not self.conf.safe_get(opt):
msg = utils.output_log(MSG.INVALID_PARAMETER, param=opt)
msg = self.output_log(MSG.INVALID_PARAMETER, param=opt)
self.raise_error(msg)
for pool in self.conf.hitachi_pool:
if len(pool) == 0:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] + '_pool')
self.raise_error(msg)
@ -687,7 +704,7 @@ class HBSDCommon():
'group_name_max_len']:
error_flag = True
if error_flag:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] +
'_group_name_format')
@ -719,8 +736,8 @@ class HBSDCommon():
def connect_storage(self):
"""Prepare for using the storage."""
self.check_pool_id()
utils.output_log(MSG.SET_CONFIG_VALUE, object='DP Pool ID',
value=self.storage_info['pool_id'])
self.output_log(MSG.SET_CONFIG_VALUE, object='DP Pool ID',
value=self.storage_info['pool_id'])
self.storage_info['controller_ports'] = []
self.storage_info['compute_ports'] = []
@ -732,8 +749,8 @@ class HBSDCommon():
"""Return the HBA ID stored in the connector."""
if self.driver_info['hba_id'] in connector:
return connector[self.driver_info['hba_id']]
msg = utils.output_log(MSG.RESOURCE_NOT_FOUND,
resource=self.driver_info['hba_id_type'])
msg = self.output_log(MSG.RESOURCE_NOT_FOUND,
resource=self.driver_info['hba_id_type'])
self.raise_error(msg)
def set_device_map(self, targets, hba_ids, volume):
@ -759,7 +776,7 @@ class HBSDCommon():
for target_port, target_gid in targets['list']:
if target_port == port:
return target_gid
msg = utils.output_log(MSG.NO_CONNECTED_TARGET)
msg = self.output_log(MSG.NO_CONNECTED_TARGET)
self.raise_error(msg)
def set_target_mode(self, port, gid):
@ -782,7 +799,7 @@ class HBSDCommon():
if port not in targets['info'] or not targets['info'][port]:
target_name, gid = self.create_target_to_storage(
port, connector, hba_ids)
utils.output_log(
self.output_log(
MSG.OBJECT_CREATED,
object='a target',
details='port: %(port)s, gid: %(gid)s, target_name: '
@ -821,14 +838,14 @@ class HBSDCommon():
self.create_target(
targets, port, connector, active_hba_ids)
except exception.VolumeDriverException:
utils.output_log(
self.output_log(
self.driver_info['msg_id']['target'], port=port)
# When other threads created a host group at same time, need to
# re-find targets.
if not targets['list']:
self.find_targets_from_storage(
targets, connector, targets['info'].keys())
targets, connector, list(targets['info'].keys()))
def get_port_index_to_be_used(self, ports, network_name):
backend_name = self.conf.safe_get('volume_backend_name')
@ -880,21 +897,22 @@ class HBSDCommon():
"""Check if available storage ports exist."""
if (self.conf.hitachi_target_ports and
not self.storage_info['controller_ports']):
msg = utils.output_log(MSG.RESOURCE_NOT_FOUND,
resource="Target ports")
msg = self.output_log(MSG.RESOURCE_NOT_FOUND,
resource="Target ports")
self.raise_error(msg)
if (self.conf.hitachi_compute_target_ports and
not self.storage_info['compute_ports']):
msg = utils.output_log(MSG.RESOURCE_NOT_FOUND,
resource="Compute target ports")
msg = self.output_log(MSG.RESOURCE_NOT_FOUND,
resource="Compute target ports")
self.raise_error(msg)
utils.output_log(MSG.SET_CONFIG_VALUE, object='target port list',
value=self.storage_info['controller_ports'])
utils.output_log(MSG.SET_CONFIG_VALUE,
object='compute target port list',
value=self.storage_info['compute_ports'])
self.output_log(MSG.SET_CONFIG_VALUE, object='target port list',
value=self.storage_info['controller_ports'])
self.output_log(MSG.SET_CONFIG_VALUE,
object='compute target port list',
value=self.storage_info['compute_ports'])
def attach_ldev(self, volume, ldev, connector, is_snapshot, targets):
def attach_ldev(
self, volume, ldev, connector, is_snapshot, targets, lun=None):
"""Initialize connection between the server and the volume."""
raise NotImplementedError()
@ -952,7 +970,8 @@ class HBSDCommon():
@coordination.synchronized(
'{self.driver_info[driver_file_prefix]}-host-'
'{self.conf.hitachi_storage_id}-{connector[host]}')
def initialize_connection(self, volume, connector, is_snapshot=False):
def initialize_connection(
self, volume, connector, is_snapshot=False, lun=None):
"""Initialize connection between the server and the volume."""
targets = {
'info': {},
@ -961,14 +980,14 @@ class HBSDCommon():
'iqns': {},
'target_map': {},
}
ldev = utils.get_ldev(volume)
ldev = self.get_ldev(volume)
if ldev is None:
msg = utils.output_log(MSG.INVALID_LDEV_FOR_CONNECTION,
volume_id=volume['id'])
msg = self.output_log(MSG.INVALID_LDEV_FOR_CONNECTION,
volume_id=volume['id'])
self.raise_error(msg)
target_lun = self.attach_ldev(
volume, ldev, connector, is_snapshot, targets)
volume, ldev, connector, is_snapshot, targets, lun)
return {
'driver_volume_type': self.driver_info['volume_type'],
@ -996,10 +1015,10 @@ class HBSDCommon():
def terminate_connection(self, volume, connector):
"""Terminate connection between the server and the volume."""
ldev = utils.get_ldev(volume)
ldev = self.get_ldev(volume)
if ldev is None:
utils.output_log(MSG.INVALID_LDEV_FOR_UNMAPPING,
volume_id=volume['id'])
self.output_log(MSG.INVALID_LDEV_FOR_UNMAPPING,
volume_id=volume['id'])
return
# If a fake connector is generated by nova when the host
# is down, then the connector will not have a host property,
@ -1008,7 +1027,7 @@ class HBSDCommon():
if 'host' not in connector:
port_hostgroup_map = self.get_port_hostgroup_map(ldev)
if not port_hostgroup_map:
utils.output_log(MSG.NO_LUN, ldev=ldev)
self.output_log(MSG.NO_LUN, ldev=ldev)
return
self.set_terminate_target(connector, port_hostgroup_map)
@ -1031,21 +1050,17 @@ 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'
if getattr(self, 'is_secondary', False):
tps_name = self.driver_info[
'driver_dir_name'] + ':remote_target_ports'
else:
tps_name = self.driver_info[
'driver_dir_name'] + ':target_ports'
else:
return target_ports
@ -1059,7 +1074,7 @@ class HBSDCommon():
volume = volume['volume']
for port in tpsset:
if port not in target_ports:
utils.output_log(
self.output_log(
MSG.INVALID_EXTRA_SPEC_KEY_PORT,
port=port, target_ports_param=tps_name,
volume_type=volume['volume_type']['name'])
@ -1071,7 +1086,7 @@ class HBSDCommon():
def unmanage_snapshot(self, snapshot):
"""Output error message and raise NotImplementedError."""
utils.output_log(
self.output_log(
MSG.SNAPSHOT_UNMANAGE_FAILED, snapshot_id=snapshot['id'])
raise NotImplementedError()
@ -1093,8 +1108,8 @@ class HBSDCommon():
def revert_to_snapshot(self, volume, snapshot):
"""Rollback the specified snapshot."""
pvol = utils.get_ldev(volume)
svol = utils.get_ldev(snapshot)
pvol = self.get_ldev(volume)
svol = self.get_ldev(snapshot)
if (pvol is not None and
svol is not None and
self.has_snap_pair(pvol, svol)):
@ -1121,20 +1136,65 @@ class HBSDCommon():
def delete_group_snapshot(self, group_snapshot, snapshots):
raise NotImplementedError()
def output_log(self, msg_enum, **kwargs):
if self.storage_id is not None:
return utils.output_log(
msg_enum, storage_id=self.storage_id, **kwargs)
else:
return utils.output_log(msg_enum, **kwargs)
def get_ldev(self, obj, both=False):
if not obj:
return None
provider_location = obj.get('provider_location')
if not provider_location:
return None
if provider_location.isdigit():
return int(provider_location)
if provider_location.startswith('{'):
loc = json.loads(provider_location)
if isinstance(loc, dict):
if getattr(self, 'is_primary', False) or (
hasattr(self, 'primary_storage_id') and not both):
return None if 'pldev' not in loc else int(loc['pldev'])
elif getattr(self, 'is_secondary', False):
return None if 'sldev' not in loc else int(loc['sldev'])
if hasattr(self, 'primary_storage_id'):
return {key: loc.get(key) for key in ['pldev', 'sldev']}
return None
def check_opt_value(self, conf, names):
"""Check if the parameter names and values are valid."""
for name in names:
try:
getattr(conf, name)
except (cfg.NoSuchOptError, cfg.ConfigFileValueError):
with excutils.save_and_reraise_exception():
self.output_log(MSG.INVALID_PARAMETER, param=name)
def check_opts(self, conf, opts):
"""Check if the specified configuration is valid."""
names = []
for opt in opts:
if opt.required and not conf.safe_get(opt.name):
msg = utils.output_log(MSG.INVALID_PARAMETER, param=opt.name)
msg = self.output_log(MSG.INVALID_PARAMETER, param=opt.name)
self.raise_error(msg)
names.append(opt.name)
utils.check_opt_value(conf, names)
self.check_opt_value(conf, names)
def get_volume_extra_specs(self, volume):
if volume is None:
return {}
type_id = volume.get('volume_type_id', None)
if type_id is None:
return {}
return volume_types.get_volume_type_extra_specs(type_id)
def require_target_existed(self, targets):
"""Check if the target list includes one or more members."""
if not targets['list']:
msg = utils.output_log(MSG.NO_CONNECTED_TARGET)
msg = self.output_log(MSG.NO_CONNECTED_TARGET)
self.raise_error(msg)
def raise_error(self, msg):

View File

@ -21,6 +21,7 @@ from oslo_utils import excutils
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.hitachi import hbsd_common as common
from cinder.volume.drivers.hitachi import hbsd_replication as replication
from cinder.volume.drivers.hitachi import hbsd_rest as rest
from cinder.volume.drivers.hitachi import hbsd_rest_fc as rest_fc
from cinder.volume.drivers.hitachi import hbsd_utils as utils
@ -51,6 +52,8 @@ _DRIVER_INFO = {
'nvol_ldev_type': utils.NVOL_LDEV_TYPE,
'target_iqn_suffix': utils.TARGET_IQN_SUFFIX,
'pair_attr': utils.PAIR_ATTR,
'mirror_attr': utils.MIRROR_ATTR,
'driver_impl_class': rest_fc.HBSDRESTFC,
}
@ -72,8 +75,9 @@ class HBSDFCDriver(driver.FibreChannelDriver):
2.2.2 - Add Target Port Assignment.
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.
2.3.1 - Add specifies format of the names HostGroups/iSCSI Targets.
2.3.2 - Add GAD volume support.
2.3.3 - Update retype and support storage assisted migration.
"""
@ -82,6 +86,8 @@ class HBSDFCDriver(driver.FibreChannelDriver):
# ThirdPartySystems wiki page
CI_WIKI_NAME = utils.CI_WIKI_NAME
driver_info = dict(_DRIVER_INFO)
def __init__(self, *args, **kwargs):
"""Initialize instance variables."""
utils.output_log(MSG.DRIVER_INITIALIZATION_START,
@ -90,14 +96,25 @@ class HBSDFCDriver(driver.FibreChannelDriver):
super(HBSDFCDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(common.COMMON_VOLUME_OPTS)
self.configuration.append_config_values(common.COMMON_PAIR_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)
self.configuration.append_config_values(
replication.COMMON_MIRROR_OPTS)
os.environ['LANG'] = 'C'
self.common = self._init_common(self.configuration, kwargs.get('db'))
def _init_common(self, conf, db):
return rest_fc.HBSDRESTFC(conf, _DRIVER_INFO, db)
kwargs.setdefault('driver_info', _DRIVER_INFO)
self.driver_info = dict(kwargs['driver_info'])
self.driver_info['driver_class'] = self.__class__
if self.configuration.safe_get('hitachi_mirror_storage_id'):
self.common = replication.HBSDREPLICATION(
self.configuration, self.driver_info, kwargs.get('db'))
elif not hasattr(self, '_init_common'):
self.common = self.driver_info['driver_impl_class'](
self.configuration, self.driver_info, kwargs.get('db'))
else:
self.common = self._init_common(
self.configuration, kwargs.get('db'))
@staticmethod
def get_driver_options():
@ -108,9 +125,17 @@ class HBSDFCDriver(driver.FibreChannelDriver):
'san_api_port', ]))
return (common.COMMON_VOLUME_OPTS +
common.COMMON_PORT_OPTS +
common.COMMON_PAIR_OPTS +
common.COMMON_NAME_OPTS +
rest.REST_VOLUME_OPTS +
rest.REST_PAIR_OPTS +
rest_fc.FC_VOLUME_OPTS +
replication._REP_OPTS +
replication.COMMON_MIRROR_OPTS +
replication.ISCSI_MIRROR_OPTS +
replication.REST_MIRROR_OPTS +
replication.REST_MIRROR_API_OPTS +
replication.REST_MIRROR_SSL_OPTS +
additional_opts)
def check_for_setup_error(self):
@ -187,7 +212,7 @@ class HBSDFCDriver(driver.FibreChannelDriver):
@volume_utils.trace
def manage_existing_get_size(self, volume, existing_ref):
"""Return the size[GB] of the specified volume."""
return self.common.manage_existing_get_size(existing_ref)
return self.common.manage_existing_get_size(volume, existing_ref)
@volume_utils.trace
def unmanage(self, volume):

View File

@ -21,6 +21,7 @@ from oslo_utils import excutils
from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.hitachi import hbsd_common as common
from cinder.volume.drivers.hitachi import hbsd_replication as replication
from cinder.volume.drivers.hitachi import hbsd_rest as rest
from cinder.volume.drivers.hitachi import hbsd_rest_iscsi as rest_iscsi
from cinder.volume.drivers.hitachi import hbsd_utils as utils
@ -51,6 +52,8 @@ _DRIVER_INFO = {
'nvol_ldev_type': utils.NVOL_LDEV_TYPE,
'target_iqn_suffix': utils.TARGET_IQN_SUFFIX,
'pair_attr': utils.PAIR_ATTR,
'mirror_attr': utils.MIRROR_ATTR,
'driver_impl_class': rest_iscsi.HBSDRESTISCSI,
}
@ -72,8 +75,9 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
2.2.2 - Add Target Port Assignment.
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.
2.3.1 - Add specifies format of the names HostGroups/iSCSI Targets.
2.3.2 - Add GAD volume support.
2.3.3 - Update retype and support storage assisted migration.
"""
@ -82,6 +86,8 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
# ThirdPartySystems wiki page
CI_WIKI_NAME = utils.CI_WIKI_NAME
driver_info = dict(_DRIVER_INFO)
def __init__(self, *args, **kwargs):
"""Initialize instance variables."""
utils.output_log(MSG.DRIVER_INITIALIZATION_START,
@ -90,12 +96,23 @@ 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_PAIR_OPTS)
self.configuration.append_config_values(common.COMMON_NAME_OPTS)
self.configuration.append_config_values(
replication.COMMON_MIRROR_OPTS)
os.environ['LANG'] = 'C'
self.common = self._init_common(self.configuration, kwargs.get('db'))
def _init_common(self, conf, db):
return rest_iscsi.HBSDRESTISCSI(conf, _DRIVER_INFO, db)
kwargs.setdefault('driver_info', _DRIVER_INFO)
self.driver_info = dict(kwargs['driver_info'])
self.driver_info['driver_class'] = self.__class__
if self.configuration.safe_get('hitachi_mirror_storage_id'):
self.common = replication.HBSDREPLICATION(
self.configuration, self.driver_info, kwargs.get('db'))
elif not hasattr(self, '_init_common'):
self.common = self.driver_info['driver_impl_class'](
self.configuration, self.driver_info, kwargs.get('db'))
else:
self.common = self._init_common(
self.configuration, kwargs.get('db'))
@staticmethod
def get_driver_options():
@ -105,8 +122,16 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
['driver_ssl_cert_verify', 'driver_ssl_cert_path',
'san_api_port', ]))
return (common.COMMON_VOLUME_OPTS +
common.COMMON_PAIR_OPTS +
common.COMMON_NAME_OPTS +
rest.REST_VOLUME_OPTS +
rest.REST_PAIR_OPTS +
replication._REP_OPTS +
replication.COMMON_MIRROR_OPTS +
replication.ISCSI_MIRROR_OPTS +
replication.REST_MIRROR_OPTS +
replication.REST_MIRROR_API_OPTS +
replication.REST_MIRROR_SSL_OPTS +
additional_opts)
def check_for_setup_error(self):
@ -183,7 +208,7 @@ class HBSDISCSIDriver(driver.ISCSIDriver):
@volume_utils.trace
def manage_existing_get_size(self, volume, existing_ref):
"""Return the size[GB] of the specified volume."""
return self.common.manage_existing_get_size(existing_ref)
return self.common.manage_existing_get_size(volume, existing_ref)
@volume_utils.trace
def unmanage(self, volume):

View File

@ -0,0 +1,972 @@
# Copyright (C) 2022, 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
# 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.
#
"""replication module for Hitachi HBSD Driver."""
import json
from eventlet import greenthread
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import timeutils
from cinder import exception
from cinder.volume.drivers.hitachi import hbsd_common as common
from cinder.volume.drivers.hitachi import hbsd_rest as rest
from cinder.volume.drivers.hitachi import hbsd_utils as utils
from cinder.zonemanager import utils as fczm_utils
_REP_STATUS_CHECK_SHORT_INTERVAL = 5
_REP_STATUS_CHECK_LONG_INTERVAL = 10 * 60
_REP_STATUS_CHECK_TIMEOUT = 24 * 60 * 60
_WAIT_PAIR = 1
_WAIT_PSUS = 2
_REP_OPTS = [
cfg.IntOpt(
'hitachi_replication_status_check_short_interval',
default=_REP_STATUS_CHECK_SHORT_INTERVAL,
help='Initial interval at which remote replication pair status is '
'checked'),
cfg.IntOpt(
'hitachi_replication_status_check_long_interval',
default=_REP_STATUS_CHECK_LONG_INTERVAL,
help='Interval at which remote replication pair status is checked. '
'This parameter is applied if the status has not changed to the '
'expected status after the time indicated by this parameter has '
'elapsed.'),
cfg.IntOpt(
'hitachi_replication_status_check_timeout',
default=_REP_STATUS_CHECK_TIMEOUT,
help='Maximum wait time before the remote replication pair status '
'changes to the expected status'),
cfg.IntOpt(
'hitachi_path_group_id',
default=0, min=0, max=255,
help='Path group ID assigned to the remote connection for remote '
'replication'),
cfg.IntOpt(
'hitachi_quorum_disk_id',
min=0, max=31,
help='ID of the Quorum disk used for global-active device'),
cfg.IntOpt(
'hitachi_replication_copy_speed',
min=1, max=15, default=3,
help='Remote copy speed of storage system. 1 or 2 indicates '
'low speed, 3 indicates middle speed, and a value between 4 and '
'15 indicates high speed.'),
cfg.BoolOpt(
'hitachi_set_mirror_reserve_attribute',
default=True,
help='Whether or not to set the mirror reserve attribute'),
cfg.IntOpt(
'hitachi_replication_number',
default=0, min=0, max=255,
help='Instance number for REST API'),
]
COMMON_MIRROR_OPTS = [
cfg.StrOpt(
'hitachi_mirror_storage_id',
default=None,
help='ID of secondary storage system'),
cfg.StrOpt(
'hitachi_mirror_pool',
default=None,
help='Pool of secondary storage system'),
cfg.StrOpt(
'hitachi_mirror_snap_pool',
default=None,
help='Thin pool of secondary storage system'),
cfg.StrOpt(
'hitachi_mirror_ldev_range',
default=None,
help='Logical device range of secondary storage system'),
cfg.ListOpt(
'hitachi_mirror_target_ports',
default=[],
help='Target port names for host group or iSCSI target'),
cfg.ListOpt(
'hitachi_mirror_compute_target_ports',
default=[],
help=(
'Target port names of compute node '
'for host group or iSCSI target')),
cfg.IntOpt(
'hitachi_mirror_pair_target_number',
min=0, max=99, default=0,
help='Pair target name of the host group or iSCSI target'),
]
ISCSI_MIRROR_OPTS = [
cfg.BoolOpt(
'hitachi_mirror_use_chap_auth',
default=False,
help='Whether or not to use iSCSI authentication'),
cfg.StrOpt(
'hitachi_mirror_auth_user',
default=None,
help='iSCSI authentication username'),
cfg.StrOpt(
'hitachi_mirror_auth_password',
default=None,
secret=True,
help='iSCSI authentication password'),
]
REST_MIRROR_OPTS = [
cfg.ListOpt(
'hitachi_mirror_rest_pair_target_ports',
default=[],
help='Target port names for pair of the host group or iSCSI target'),
]
REST_MIRROR_API_OPTS = [
cfg.StrOpt(
'hitachi_mirror_rest_user',
default=None,
help='Username of secondary storage system for REST API'),
cfg.StrOpt(
'hitachi_mirror_rest_password',
default=None,
secret=True,
help='Password of secondary storage system for REST API'),
cfg.StrOpt(
'hitachi_mirror_rest_api_ip',
default=None,
help='IP address of REST API server'),
cfg.PortOpt(
'hitachi_mirror_rest_api_port',
default=443,
help='Port number of REST API server'),
]
REST_MIRROR_SSL_OPTS = [
cfg.BoolOpt('hitachi_mirror_ssl_cert_verify',
default=False,
help='If set to True the http client will validate the SSL '
'certificate of the backend endpoint.'),
cfg.StrOpt('hitachi_mirror_ssl_cert_path',
help='Can be used to specify a non default path to a '
'CA_BUNDLE file or directory with certificates of '
'trusted CAs, which will be used to validate the backend'),
]
CONF = cfg.CONF
CONF.register_opts(_REP_OPTS)
CONF.register_opts(COMMON_MIRROR_OPTS)
CONF.register_opts(ISCSI_MIRROR_OPTS)
CONF.register_opts(REST_MIRROR_OPTS)
CONF.register_opts(REST_MIRROR_API_OPTS)
CONF.register_opts(REST_MIRROR_SSL_OPTS)
LOG = logging.getLogger(__name__)
MSG = utils.HBSDMsg
def _pack_rep_provider_location(pldev=None, sldev=None, rep_type=None):
provider_location = {}
if pldev is not None:
provider_location['pldev'] = pldev
if sldev is not None:
provider_location['sldev'] = sldev
if rep_type is not None:
provider_location['remote-copy'] = rep_type
return json.dumps(provider_location)
def _delays(short_interval, long_interval, timeout):
start_time = timeutils.utcnow()
watch = timeutils.StopWatch()
i = 0
while True:
watch.restart()
yield i
if utils.timed_out(start_time, timeout):
raise StopIteration()
watch.stop()
interval = long_interval if utils.timed_out(
start_time, long_interval) else short_interval
idle = max(interval - watch.elapsed(), 0)
greenthread.sleep(idle)
i += 1
class HBSDREPLICATION(rest.HBSDREST):
def __init__(self, conf, driverinfo, db):
super(HBSDREPLICATION, self).__init__(conf, driverinfo, db)
conf.append_config_values(_REP_OPTS)
if driverinfo['proto'] == 'iSCSI':
conf.append_config_values(ISCSI_MIRROR_OPTS)
conf.append_config_values(REST_MIRROR_OPTS)
conf.append_config_values(REST_MIRROR_API_OPTS)
conf.append_config_values(REST_MIRROR_SSL_OPTS)
driver_impl_class = self.driver_info['driver_impl_class']
self.primary = driver_impl_class(conf, driverinfo, db)
self.rep_primary = self.primary
self.rep_primary.is_primary = True
self.rep_primary.storage_id = conf.safe_get(
self.driver_info['param_prefix'] + '_storage_id') or ''
self.primary_storage_id = self.rep_primary.storage_id
self.secondary = driver_impl_class(conf, driverinfo, db)
self.rep_secondary = self.secondary
self.rep_secondary.is_secondary = True
self.rep_secondary.storage_id = (
conf.safe_get(
self.driver_info['param_prefix'] + '_mirror_storage_id') or '')
self.secondary_storage_id = self.rep_secondary.storage_id
self.instances = self.rep_primary, self.rep_secondary
self._LDEV_NAME = self.driver_info['driver_prefix'] + '-LDEV-%d-%d'
def update_mirror_conf(self, conf, opts):
for opt in opts:
name = opt.name.replace('hitachi_mirror_', 'hitachi_')
try:
setattr(conf, name, getattr(conf, opt.name))
except Exception:
with excutils.save_and_reraise_exception():
self.rep_secondary.output_log(
MSG.INVALID_PARAMETER, param=opt.name)
def _replace_with_mirror_conf(self):
conf = self.conf
new_conf = utils.Config(conf)
self.rep_secondary.conf = new_conf
self.update_mirror_conf(new_conf, COMMON_MIRROR_OPTS)
self.update_mirror_conf(new_conf, REST_MIRROR_OPTS)
if self.rep_secondary.driver_info['volume_type'] == 'iscsi':
self.update_mirror_conf(new_conf, ISCSI_MIRROR_OPTS)
new_conf.san_login = (
conf.safe_get(self.driver_info['param_prefix'] +
'_mirror_rest_user'))
new_conf.san_password = (
conf.safe_get(self.driver_info['param_prefix'] +
'_mirror_rest_password'))
new_conf.san_ip = (
conf.safe_get(self.driver_info['param_prefix'] +
'_mirror_rest_api_ip'))
new_conf.san_api_port = (
conf.safe_get(self.driver_info['param_prefix'] +
'_mirror_rest_api_port'))
new_conf.driver_ssl_cert_verify = (
conf.safe_get(self.driver_info['param_prefix'] +
'_mirror_ssl_cert_verify'))
new_conf.driver_ssl_cert_path = (
conf.safe_get(self.driver_info['param_prefix'] +
'_mirror_ssl_cert_path'))
def do_setup(self, context):
"""Prepare for the startup of the driver."""
self.rep_primary = self.primary
self.rep_secondary = self.secondary
self.ctxt = context
try:
self.rep_primary.do_setup(context)
self.client = self.rep_primary.client
except Exception:
self.rep_primary.output_log(
MSG.SITE_INITIALIZATION_FAILED, site='primary')
self.rep_primary = None
try:
self._replace_with_mirror_conf()
self.rep_secondary.do_setup(context)
except Exception:
self.rep_secondary.output_log(
MSG.SITE_INITIALIZATION_FAILED, site='secondary')
if not self.rep_primary:
raise
self.rep_secondary = None
def update_volume_stats(self):
"""Update properties, capabilities and current states of the driver."""
if self.rep_primary:
data = self.rep_primary.update_volume_stats()
else:
data = self.rep_secondary.update_volume_stats()
return data
def _require_rep_primary(self):
if not self.rep_primary:
msg = utils.output_log(
MSG.SITE_NOT_INITIALIZED, storage_id=self.primary_storage_id,
site='primary')
self.raise_error(msg)
def _require_rep_secondary(self):
if not self.rep_secondary:
msg = utils.output_log(
MSG.SITE_NOT_INITIALIZED, storage_id=self.secondary_storage_id,
site='secondary')
self.raise_error(msg)
def _is_mirror_spec(self, extra_specs):
if not extra_specs:
return False
topology = extra_specs.get(
self.driver_info['driver_dir_name'] + ':topology')
if topology is None:
return False
elif topology == 'active_active_mirror_volume':
return True
else:
msg = self.rep_primary.output_log(
MSG.INVALID_EXTRA_SPEC_KEY,
key=self.driver_info['driver_dir_name'] + ':topology',
value=topology)
self.raise_error(msg)
def _create_rep_ldev(self, volume, rep_type, pvol=None):
"""Create a primary volume and a secondary volume."""
pool_id = self.rep_secondary.storage_info['pool_id'][0]
ldev_range = self.rep_secondary.storage_info['ldev_range']
thread = greenthread.spawn(
self.rep_secondary.create_ldev, volume.size, pool_id, ldev_range)
if pvol is None:
try:
pool_id = self.rep_primary.get_pool_id_of_volume(volume)
ldev_range = self.rep_primary.storage_info['ldev_range']
pvol = self.rep_primary.create_ldev(volume.size,
pool_id, ldev_range)
except exception.VolumeDriverException:
self.rep_primary.output_log(MSG.CREATE_LDEV_FAILED)
try:
svol = thread.wait()
except Exception:
self.rep_secondary.output_log(MSG.CREATE_LDEV_FAILED)
svol = None
if pvol is None or svol is None:
for vol, type_, instance in zip((pvol, svol), ('P-VOL', 'S-VOL'),
self.instances):
if vol is None:
msg = instance.output_log(
MSG.CREATE_REPLICATION_VOLUME_FAILED,
type=type_, rep_type=rep_type,
volume_id=volume.id,
volume_type=volume.volume_type.name, size=volume.size)
else:
instance.delete_ldev(vol)
self.raise_error(msg)
thread = greenthread.spawn(
self.rep_secondary.modify_ldev_name,
svol, volume['id'].replace("-", ""))
try:
self.rep_primary.modify_ldev_name(
pvol, volume['id'].replace("-", ""))
finally:
thread.wait()
return pvol, svol
def _create_rep_copy_group_name(self, ldev):
return self.driver_info['target_prefix'] + '%s%02XU%02d' % (
CONF.my_ip, self.conf.hitachi_replication_number, ldev >> 10)
def _get_rep_copy_speed(self):
rep_copy_speed = self.rep_primary.conf.safe_get(
self.driver_info['param_prefix'] + '_replication_copy_speed')
if rep_copy_speed:
return rep_copy_speed
else:
return self.rep_primary.conf.hitachi_copy_speed
def _get_wait_pair_status_change_params(self, wait_type):
"""Get a replication pair status information."""
_wait_pair_status_change_params = {
_WAIT_PAIR: {
'instance': self.rep_primary,
'remote_client': self.rep_secondary.client,
'is_secondary': False,
'transitional_status': ['COPY'],
'expected_status': ['PAIR', 'PFUL'],
'msgid': MSG.CREATE_REPLICATION_PAIR_FAILED,
'status_keys': ['pvolStatus', 'svolStatus'],
},
_WAIT_PSUS: {
'instance': self.rep_primary,
'remote_client': self.rep_secondary.client,
'is_secondary': False,
'transitional_status': ['PAIR', 'PFUL'],
'expected_status': ['PSUS', 'SSUS'],
'msgid': MSG.SPLIT_REPLICATION_PAIR_FAILED,
'status_keys': ['pvolStatus', 'svolStatus'],
}
}
return _wait_pair_status_change_params[wait_type]
def _wait_pair_status_change(self, copy_group_name, pvol, svol,
rep_type, wait_type):
"""Wait until the replication pair status changes to the specified
status.
"""
for _ in _delays(
self.conf.hitachi_replication_status_check_short_interval,
self.conf.hitachi_replication_status_check_long_interval,
self.conf.hitachi_replication_status_check_timeout):
params = self._get_wait_pair_status_change_params(wait_type)
status = params['instance'].client.get_remote_copypair(
params['remote_client'], copy_group_name, pvol, svol,
is_secondary=params['is_secondary'])
statuses = [status.get(status_key) for status_key in
params['status_keys']]
unexpected_status_set = (set(statuses) -
set(params['expected_status']))
if not unexpected_status_set:
break
if unexpected_status_set.issubset(
set(params['transitional_status'])):
continue
msg = params['instance'].output_log(
params['msgid'], rep_type=rep_type, pvol=pvol, svol=svol,
copy_group=copy_group_name, status='/'.join(statuses))
self.raise_error(msg)
else:
status = params['instance'].client.get_remote_copypair(
params['remote_client'], copy_group_name, pvol, svol,
is_secondary=params['is_secondary'])
msg = params['instance'].output_log(
MSG.PAIR_CHANGE_TIMEOUT,
rep_type=rep_type, pvol=pvol, svol=svol,
copy_group=copy_group_name, current_status='/'.join(statuses),
expected_status=str(params['expected_status']),
timeout=self.conf.hitachi_replication_status_check_timeout)
self.raise_error(msg)
def _create_rep_pair(self, volume, pvol, svol, rep_type,
do_initialcopy=True):
"""Create a replication pair."""
copy_group_name = self._create_rep_copy_group_name(pvol)
@utils.synchronized_on_copy_group()
def inner(self, remote_client, copy_group_name, secondary_storage_id,
conf, copyPace, parent):
is_new_copy_grp = True
result = self.get_remote_copy_grps(remote_client)
if result:
for data in result:
if copy_group_name == data['copyGroupName']:
is_new_copy_grp = False
break
body = {
'copyGroupName': copy_group_name,
'copyPairName': parent._LDEV_NAME % (pvol, svol),
'replicationType': rep_type,
'remoteStorageDeviceId': secondary_storage_id,
'pvolLdevId': pvol,
'svolLdevId': svol,
'pathGroupId': conf.hitachi_path_group_id,
'localDeviceGroupName': copy_group_name + 'P',
'remoteDeviceGroupName': copy_group_name + 'S',
'isNewGroupCreation': is_new_copy_grp,
'doInitialCopy': do_initialcopy,
'isDataReductionForceCopy': False
}
if rep_type == parent.driver_info['mirror_attr']:
body['quorumDiskId'] = conf.hitachi_quorum_disk_id
body['copyPace'] = copyPace
if is_new_copy_grp:
body['muNumber'] = 0
self.add_remote_copypair(remote_client, body)
inner(
self.rep_primary.client, self.rep_secondary.client,
copy_group_name, self.rep_secondary.storage_id,
self.rep_secondary.conf, self._get_rep_copy_speed(),
self)
self._wait_pair_status_change(
copy_group_name, pvol, svol, rep_type, _WAIT_PAIR)
def _create_rep_ldev_and_pair(
self, volume, rep_type, pvol=None):
"""Create volume and Replication pair."""
svol = None
pvol, svol = self._create_rep_ldev(volume, rep_type, pvol)
try:
thread = greenthread.spawn(
self.rep_secondary.initialize_pair_connection, svol)
try:
self.rep_primary.initialize_pair_connection(pvol)
finally:
thread.wait()
if self.rep_primary.conf.\
hitachi_set_mirror_reserve_attribute:
self.rep_secondary.client.assign_virtual_ldevid(svol)
self._create_rep_pair(volume, pvol, svol, rep_type)
except Exception:
with excutils.save_and_reraise_exception():
if svol is not None:
self.rep_secondary.terminate_pair_connection(svol)
if self.rep_primary.conf.\
hitachi_set_mirror_reserve_attribute:
self.rep_secondary.client.unassign_virtual_ldevid(
svol)
self.rep_secondary.delete_ldev(svol)
if pvol is not None:
self.rep_primary.terminate_pair_connection(pvol)
self.rep_primary.delete_ldev(pvol)
return pvol, svol
def create_volume(self, volume):
"""Create a volume from a volume or snapshot and return its properties.
"""
self._require_rep_primary()
extra_specs = self.rep_primary.get_volume_extra_specs(volume)
if self._is_mirror_spec(extra_specs):
self._require_rep_secondary()
rep_type = self.driver_info['mirror_attr']
pldev, sldev = self._create_rep_ldev_and_pair(
volume, rep_type)
provider_location = _pack_rep_provider_location(
pldev, sldev, rep_type)
return {
'provider_location': provider_location
}
return self.rep_primary.create_volume(volume)
def _has_rep_pair(self, ldev):
ldev_info = self.rep_primary.get_ldev_info(
['status', 'attributes'], ldev)
return (ldev_info['status'] == rest.NORMAL_STS and
self.driver_info['mirror_attr'] in ldev_info['attributes'])
def _get_rep_pair_info(self, pldev):
"""Return replication pair info."""
pair_info = {}
if not self._has_rep_pair(pldev):
return pair_info
self._require_rep_secondary()
copy_group_name = self._create_rep_copy_group_name(pldev)
pairs = self.rep_primary.client.get_remote_copy_grp(
self.rep_secondary.client,
copy_group_name).get('copyPairs', [])
for pair in pairs:
if (pair.get('replicationType') in
[self.driver_info['mirror_attr']] and
pair['pvolLdevId'] == pldev):
break
else:
return pair_info
pair_info['pvol'] = pldev
pair_info['svol_info'] = [{
'ldev': pair.get('svolLdevId'),
'rep_type': pair.get('replicationType'),
'is_psus': pair.get('svolStatus') in ['SSUS', 'PFUS'],
'pvol_status': pair.get('pvolStatus'),
'svol_status': pair.get('svolStatus')}]
return pair_info
def _split_rep_pair(self, pvol, svol):
copy_group_name = self._create_rep_copy_group_name(pvol)
rep_type = self.driver_info['mirror_attr']
self.rep_primary.client.split_remote_copypair(
self.rep_secondary.client, copy_group_name, pvol, svol, rep_type)
self._wait_pair_status_change(
copy_group_name, pvol, svol, rep_type, _WAIT_PSUS)
def _delete_rep_pair(self, pvol, svol):
"""Delete a replication pair."""
copy_group_name = self._create_rep_copy_group_name(pvol)
self._split_rep_pair(pvol, svol)
self.rep_primary.client.delete_remote_copypair(
self.rep_secondary.client, copy_group_name, pvol, svol)
def delete_volume(self, volume):
"""Delete the specified volume."""
self._require_rep_primary()
ldev = self.rep_primary.get_ldev(volume)
if ldev is None:
self.rep_primary.output_log(
MSG.INVALID_LDEV_FOR_DELETION, method='delete_volume',
id=volume.id)
return
pair_info = self._get_rep_pair_info(ldev)
if pair_info:
self._delete_rep_pair(
pair_info['pvol'], pair_info['svol_info'][0]['ldev'])
thread = greenthread.spawn(
self.rep_secondary.delete_volume, volume)
try:
self.rep_primary.delete_volume(volume)
finally:
thread.wait()
else:
self.rep_primary.delete_volume(volume)
def delete_ldev(self, ldev):
self._require_rep_primary()
pair_info = self._get_rep_pair_info(ldev)
if pair_info:
self._delete_rep_pair(ldev, pair_info['svol_info'][0]['ldev'])
th = greenthread.spawn(self.rep_secondary.delete_ldev,
pair_info['svol_info'][0]['ldev'])
try:
self.rep_primary.delete_ldev(ldev)
finally:
th.wait()
else:
self.rep_primary.delete_ldev(ldev)
def _create_rep_volume_from_src(self, volume, src, src_type, operation):
"""Create a replication volume from a volume or snapshot and return
its properties.
"""
rep_type = self.driver_info['mirror_attr']
data = self.rep_primary.create_volume_from_src(
volume, src, src_type, is_rep=True)
new_ldev = self.rep_primary.get_ldev(data)
sldev = self._create_rep_ldev_and_pair(
volume, rep_type, new_ldev)[1]
provider_location = _pack_rep_provider_location(
new_ldev, sldev, rep_type)
return {
'provider_location': provider_location,
}
def _create_volume_from_src(self, volume, src, src_type):
"""Create a volume from a volume or snapshot and return its properties.
"""
self._require_rep_primary()
operation = ('create a volume from a %s' % src_type)
extra_specs = self.rep_primary.get_volume_extra_specs(volume)
if self._is_mirror_spec(extra_specs):
self._require_rep_secondary()
return self._create_rep_volume_from_src(
volume, src, src_type, operation)
return self.rep_primary.create_volume_from_src(volume, src, src_type)
def create_cloned_volume(self, volume, src_vref):
"""Create a clone of the specified volume and return its properties."""
return self._create_volume_from_src(
volume, src_vref, common.STR_VOLUME)
def create_volume_from_snapshot(self, volume, snapshot):
"""Create a volume from a snapshot and return its properties."""
return self._create_volume_from_src(
volume, snapshot, common.STR_SNAPSHOT)
def create_snapshot(self, snapshot):
"""Create a snapshot from a volume and return its properties."""
self._require_rep_primary()
return self.rep_primary.create_snapshot(snapshot)
def delete_snapshot(self, snapshot):
"""Delete the specified snapshot."""
self._require_rep_primary()
self.rep_primary.delete_snapshot(snapshot)
def _get_remote_copy_mode(self, vol):
provider_location = vol.get('provider_location')
if not provider_location:
return None
if provider_location.startswith('{'):
loc = json.loads(provider_location)
if isinstance(loc, dict):
return loc.get('remote-copy')
return None
def _merge_properties(self, prop1, prop2):
if prop1 is None:
if prop2 is None:
return []
return prop2
elif prop2 is None:
return prop1
d = dict(prop1)
for key in ('target_luns', 'target_wwn', 'target_portals',
'target_iqns'):
if key in d:
d[key] = d[key] + prop2[key]
if 'initiator_target_map' in d:
for key2 in d['initiator_target_map']:
d['initiator_target_map'][key2] = (
d['initiator_target_map'][key2]
+ prop2['initiator_target_map'][key2])
return d
def initialize_connection_mirror(self, volume, connector):
lun = None
prop1 = None
prop2 = None
if self.rep_primary:
try:
conn_info1 = (
self.rep_primary.initialize_connection(
volume, connector, is_mirror=True))
except Exception as ex:
self.rep_primary.output_log(
MSG.REPLICATION_VOLUME_OPERATION_FAILED,
operation='attach', type='P-VOL',
volume_id=volume.id, reason=str(ex))
else:
prop1 = conn_info1['data']
if self.driver_info['volume_type'] == 'fibre_channel':
if 'target_lun' in prop1:
lun = prop1['target_lun']
else:
lun = prop1['target_luns'][0]
if self.rep_secondary:
try:
conn_info2 = (
self.rep_secondary.initialize_connection(
volume, connector, lun=lun, is_mirror=True))
except Exception as ex:
self.rep_secondary.output_log(
MSG.REPLICATION_VOLUME_OPERATION_FAILED,
operation='attach', type='S-VOL',
volume_id=volume.id, reason=str(ex))
if prop1 is None:
raise ex
else:
prop2 = conn_info2['data']
conn_info = {
'driver_volume_type': self.driver_info['volume_type'],
'data': self._merge_properties(prop1, prop2),
}
return conn_info
def initialize_connection(self, volume, connector, is_snapshot=False):
"""Initialize connection between the server and the volume."""
if (self._get_remote_copy_mode(volume) ==
self.driver_info['mirror_attr']):
conn_info = self.initialize_connection_mirror(volume, connector)
if self.driver_info['volume_type'] == 'fibre_channel':
fczm_utils.add_fc_zone(conn_info)
return conn_info
else:
self._require_rep_primary()
return self.rep_primary.initialize_connection(
volume, connector, is_snapshot)
def terminate_connection_mirror(self, volume, connector):
prop1 = None
prop2 = None
if self.rep_primary:
try:
conn_info1 = self.rep_primary.terminate_connection(
volume, connector, is_mirror=True)
except Exception as ex:
self.rep_primary.output_log(
MSG.REPLICATION_VOLUME_OPERATION_FAILED,
operation='detach', type='P-VOL',
volume_id=volume.id, reason=str(ex))
raise ex
else:
if conn_info1:
prop1 = conn_info1['data']
if self.rep_secondary:
try:
conn_info2 = self.rep_secondary.terminate_connection(
volume, connector, is_mirror=True)
except Exception as ex:
self.rep_secondary.output_log(
MSG.REPLICATION_VOLUME_OPERATION_FAILED,
operation='detach', type='S-VOL',
volume_id=volume.id, reason=str(ex))
raise ex
else:
if conn_info2:
prop2 = conn_info2['data']
conn_info = {
'driver_volume_type': self.driver_info['volume_type'],
'data': self._merge_properties(prop1, prop2),
}
return conn_info
def terminate_connection(self, volume, connector):
"""Terminate connection between the server and the volume."""
if (self._get_remote_copy_mode(volume) ==
self.driver_info['mirror_attr']):
conn_info = self.terminate_connection_mirror(volume, connector)
if self.driver_info['volume_type'] == 'fibre_channel':
fczm_utils.remove_fc_zone(conn_info)
return conn_info
else:
self._require_rep_primary()
return self.rep_primary.terminate_connection(volume, connector)
def _extend_pair_volume(self, volume, new_size, ldev, pair_info):
"""Extend the specified replication volume to the specified size."""
rep_type = self.driver_info['mirror_attr']
pvol_info = self.rep_primary.get_ldev_info(
['numOfPorts'], pair_info['pvol'])
if pvol_info['numOfPorts'] > 1:
msg = self.rep_primary.output_log(
MSG.EXTEND_REPLICATION_VOLUME_ERROR,
rep_type=rep_type, volume_id=volume.id, ldev=ldev,
source_size=volume.size, destination_size=new_size,
pvol=pair_info['pvol'], svol='',
pvol_num_of_ports=pvol_info['numOfPorts'],
svol_num_of_ports='')
self.raise_error(msg)
self._delete_rep_pair(
ldev, pair_info['svol_info'][0]['ldev'])
thread = greenthread.spawn(
self.rep_secondary.extend_volume, volume, new_size)
try:
self.rep_primary.extend_volume(volume, new_size)
finally:
thread.wait()
self._create_rep_pair(
volume, pair_info['pvol'], pair_info['svol_info'][0]['ldev'],
rep_type, do_initialcopy=False)
def extend_volume(self, volume, new_size):
"""Extend the specified volume to the specified size."""
self._require_rep_primary()
ldev = self.rep_primary.get_ldev(volume)
if ldev is None:
msg = self.rep_primary.output_log(
MSG.INVALID_LDEV_FOR_EXTENSION, volume_id=volume.id)
self.raise_error(msg)
pair_info = self._get_rep_pair_info(ldev)
if pair_info:
self._extend_pair_volume(volume, new_size, ldev, pair_info)
else:
self.rep_primary.extend_volume(volume, new_size)
def manage_existing(self, volume, existing_ref):
"""Return volume properties which Cinder needs to manage the volume."""
self._require_rep_primary()
return self.rep_primary.manage_existing(volume, existing_ref)
def manage_existing_get_size(self, volume, existing_ref):
"""Return the size[GB] of the specified volume."""
self._require_rep_primary()
return self.rep_primary.manage_existing_get_size(volume, existing_ref)
def unmanage(self, volume):
"""Prepare the volume for removing it from Cinder management."""
self._require_rep_primary()
ldev = self.rep_primary.get_ldev(volume)
if ldev is None:
self.rep_primary.output_log(
MSG.INVALID_LDEV_FOR_DELETION,
method='unmanage', id=volume.id)
return
if self._has_rep_pair(ldev):
msg = self.rep_primary.output_log(
MSG.REPLICATION_PAIR_ERROR,
operation='unmanage a volume', volume=volume.id,
snapshot_info='', ldev=ldev)
self.raise_error(msg)
self.rep_primary.unmanage(volume)
def discard_zero_page(self, volume):
self._require_rep_primary()
ldev = self.rep_primary.get_ldev(volume)
if self._has_rep_pair(ldev):
self._require_rep_secondary()
th = greenthread.spawn(
self.rep_secondary.discard_zero_page, volume)
try:
self.rep_primary.discard_zero_page(volume)
finally:
th.wait()
else:
self.rep_primary.discard_zero_page(volume)
def unmanage_snapshot(self, snapshot):
if not self.rep_primary:
return self.rep_secondary.unmanage_snapshot(snapshot)
else:
return self.rep_primary.unmanage_snapshot(snapshot)
def retype(self, ctxt, volume, new_type, diff, host):
self._require_rep_primary()
ldev = self.rep_primary.get_ldev(volume)
if ldev is None:
msg = self.rep_primary.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY,
type='volume', id=volume.id)
self.raise_error(msg)
if (self._has_rep_pair(ldev) or
self._is_mirror_spec(new_type['extra_specs'])):
return False
return self.rep_primary.retype(
ctxt, volume, new_type, diff, host)
def migrate_volume(self, volume, host):
self._require_rep_primary()
ldev = self.rep_primary.get_ldev(volume)
if ldev is None:
msg = self.rep_primary.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY,
type='volume', id=volume.id)
self.raise_error(msg)
if self._get_rep_pair_info(ldev):
return False, None
else:
return self.rep_primary.migrate_volume(volume, host)
def _resync_rep_pair(self, pvol, svol):
copy_group_name = self._create_rep_copy_group_name(pvol)
rep_type = self.driver_info['mirror_attr']
self.rep_primary.client.resync_remote_copypair(
self.rep_secondary.client, copy_group_name, pvol, svol,
rep_type, copy_speed=self._get_rep_copy_speed())
self._wait_pair_status_change(
copy_group_name, pvol, svol, rep_type, _WAIT_PAIR)
def revert_to_snapshot(self, volume, snapshot):
"""Rollback the specified snapshot."""
self._require_rep_primary()
ldev = self.rep_primary.get_ldev(volume)
svol = self.rep_primary.get_ldev(snapshot)
if None in (ldev, svol):
raise NotImplementedError()
pair_info = self._get_rep_pair_info(ldev)
is_snap = self.rep_primary.has_snap_pair(ldev, svol)
if pair_info and is_snap:
self._split_rep_pair(pair_info['pvol'],
pair_info['svol_info'][0]['ldev'])
try:
self.rep_primary.revert_to_snapshot(volume, snapshot)
finally:
if pair_info and is_snap:
self._resync_rep_pair(pair_info['pvol'],
pair_info['svol_info'][0]['ldev'])
def create_group(self):
self._require_rep_primary()
return self.rep_primary.create_group()
def delete_group(self, group, volumes):
self._require_rep_primary()
return super(HBSDREPLICATION, self).delete_group(group, volumes)
def create_group_from_src(
self, context, group, volumes, snapshots=None, source_vols=None):
self._require_rep_primary()
return super(HBSDREPLICATION, self).create_group_from_src(
context, group, volumes, snapshots, source_vols)
def update_group(self, group, add_volumes=None):
self._require_rep_primary()
return self.rep_primary.update_group(group, add_volumes)
def create_group_snapshot(self, context, group_snapshot, snapshots):
self._require_rep_primary()
return self.rep_primary.create_group_snapshot(
context, group_snapshot, snapshots)
def delete_group_snapshot(self, group_snapshot, snapshots):
self._require_rep_primary()
return self.rep_primary.delete_group_snapshot(
group_snapshot, snapshots)

View File

@ -91,6 +91,8 @@ _MAX_COPY_GROUP_NAME = 29
_MAX_CTG_COUNT_EXCEEDED_ADD_SNAPSHOT = ('2E10', '2302')
_MAX_PAIR_COUNT_IN_CTG_EXCEEDED_ADD_SNAPSHOT = ('2E13', '9900')
_PAIR_TARGET_NAME_BODY_DEFAULT = 'pair00'
REST_VOLUME_OPTS = [
cfg.BoolOpt(
'hitachi_rest_disable_io_wait',
@ -190,6 +192,13 @@ REST_VOLUME_OPTS = [
help='Host mode option for host group or iSCSI target.'),
]
REST_PAIR_OPTS = [
cfg.ListOpt(
'hitachi_rest_pair_target_ports',
default=[],
help='Target port names for pair of the host group or iSCSI target'),
]
_REQUIRED_REST_OPTS = [
'san_login',
'san_password',
@ -198,21 +207,26 @@ _REQUIRED_REST_OPTS = [
CONF = cfg.CONF
CONF.register_opts(REST_VOLUME_OPTS, group=configuration.SHARED_CONF_GROUP)
CONF.register_opts(REST_PAIR_OPTS, group=configuration.SHARED_CONF_GROUP)
LOG = logging.getLogger(__name__)
MSG = utils.HBSDMsg
def _is_valid_target(self, target, target_name, target_ports):
def _is_valid_target(self, target, target_name, target_ports, is_pair):
"""Check if the specified target is valid."""
if is_pair:
return (target[:utils.PORT_ID_LENGTH] in target_ports and
target_name == self._PAIR_TARGET_NAME)
return (target[:utils.PORT_ID_LENGTH] in target_ports and
target_name.startswith(self.driver_info['target_prefix']))
target_name.startswith(self.driver_info['target_prefix']) and
target_name != self._PAIR_TARGET_NAME)
def _check_ldev_manageability(self, ldev_info, ldev, existing_ref):
"""Check if the LDEV meets the criteria for being managed."""
if ldev_info['status'] != NORMAL_STS:
msg = utils.output_log(MSG.INVALID_LDEV_FOR_MANAGE)
msg = self.output_log(MSG.INVALID_LDEV_FOR_MANAGE)
raise exception.ManageExistingInvalidReference(
existing_ref=existing_ref, reason=msg)
attributes = set(ldev_info['attributes'])
@ -221,20 +235,20 @@ def _check_ldev_manageability(self, ldev_info, ldev, existing_ref):
not attributes.issubset(
set(['CVS', self.driver_info['hdp_vol_attr'],
self.driver_info['hdt_vol_attr']]))):
msg = utils.output_log(MSG.INVALID_LDEV_ATTR_FOR_MANAGE, ldev=ldev,
ldevtype=self.driver_info['nvol_ldev_type'])
msg = self.output_log(MSG.INVALID_LDEV_ATTR_FOR_MANAGE, ldev=ldev,
ldevtype=self.driver_info['nvol_ldev_type'])
raise exception.ManageExistingInvalidReference(
existing_ref=existing_ref, reason=msg)
if ldev_info['numOfPorts']:
msg = utils.output_log(MSG.INVALID_LDEV_PORT_FOR_MANAGE, ldev=ldev)
msg = self.output_log(MSG.INVALID_LDEV_PORT_FOR_MANAGE, ldev=ldev)
raise exception.ManageExistingInvalidReference(
existing_ref=existing_ref, reason=msg)
def _check_ldev_size(ldev_info, ldev, existing_ref):
def _check_ldev_size(self, ldev_info, ldev, existing_ref):
"""Hitachi storage calculates volume sizes in a block unit, 512 bytes."""
if ldev_info['blockCapacity'] % utils.GIGABYTE_PER_BLOCK_SIZE:
msg = utils.output_log(MSG.INVALID_LDEV_SIZE_FOR_MANAGE, ldev=ldev)
msg = self.output_log(MSG.INVALID_LDEV_SIZE_FOR_MANAGE, ldev=ldev)
raise exception.ManageExistingInvalidReference(
existing_ref=existing_ref, reason=msg)
@ -246,10 +260,24 @@ class HBSDREST(common.HBSDCommon):
"""Initialize instance variables."""
super(HBSDREST, self).__init__(conf, storage_protocol, db)
self.conf.append_config_values(REST_VOLUME_OPTS)
self.conf.append_config_values(REST_PAIR_OPTS)
self.conf.append_config_values(san.san_opts)
self.client = None
def do_setup(self, context):
if hasattr(
self.conf,
self.driver_info['param_prefix'] + '_pair_target_number'):
self._PAIR_TARGET_NAME_BODY = 'pair%02d' % (
self.conf.safe_get(self.driver_info['param_prefix'] +
'_pair_target_number'))
else:
self._PAIR_TARGET_NAME_BODY = _PAIR_TARGET_NAME_BODY_DEFAULT
self._PAIR_TARGET_NAME = (self.driver_info['target_prefix'] +
self._PAIR_TARGET_NAME_BODY)
super(HBSDREST, self).do_setup(context)
def setup_client(self):
"""Initialize RestApiClient."""
verify = self.conf.driver_ssl_cert_verify
@ -258,6 +286,9 @@ class HBSDREST(common.HBSDCommon):
if verify_path:
verify = verify_path
self.verify = verify
is_rep = False
if self.storage_id is not None:
is_rep = True
self.client = rest_api.RestApiClient(
self.conf,
self.conf.san_ip,
@ -267,7 +298,8 @@ class HBSDREST(common.HBSDCommon):
self.conf.san_password,
self.driver_info['driver_prefix'],
tcp_keepalive=self.conf.hitachi_rest_tcp_keepalive,
verify=verify)
verify=verify,
is_rep=is_rep)
self.client.login()
def need_client_setup(self):
@ -307,7 +339,7 @@ class HBSDREST(common.HBSDCommon):
"""Delete the specified LDEV from the storage."""
result = self.client.get_ldev(ldev)
if result['emulationType'] == 'NOT DEFINED':
utils.output_log(MSG.LDEV_NOT_EXIST, ldev=ldev)
self.output_log(MSG.LDEV_NOT_EXIST, ldev=ldev)
return
self.client.delete_ldev(
ldev,
@ -352,7 +384,7 @@ class HBSDREST(common.HBSDCommon):
_wait_for_copy_pair_status, timeutils.utcnow(),
ldev, status, timeout)
if not loop.start(interval=interval).wait():
msg = utils.output_log(
msg = self.output_log(
MSG.PAIR_STATUS_WAIT_TIMEOUT, svol=ldev)
self.raise_error(msg)
@ -375,7 +407,7 @@ class HBSDREST(common.HBSDCommon):
if (utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
rest_api.INVALID_SNAPSHOT_POOL and
not self.conf.hitachi_snap_pool):
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] + '_snap_pool')
self.raise_error(msg)
@ -388,7 +420,7 @@ class HBSDREST(common.HBSDCommon):
try:
self._delete_pair_from_storage(pvol, svol)
except exception.VolumeDriverException:
utils.output_log(
self.output_log(
MSG.DELETE_PAIR_FAILED, pvol=pvol, svol=svol)
def _create_clone_pair(self, pvol, svol, snap_pool_id):
@ -417,7 +449,7 @@ class HBSDREST(common.HBSDCommon):
if (utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
rest_api.INVALID_SNAPSHOT_POOL and
not self.conf.hitachi_snap_pool):
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] + '_snap_pool')
self.raise_error(msg)
@ -430,7 +462,7 @@ class HBSDREST(common.HBSDCommon):
try:
self._delete_pair_from_storage(pvol, svol)
except exception.VolumeDriverException:
utils.output_log(
self.output_log(
MSG.DELETE_PAIR_FAILED, pvol=pvol, svol=svol)
def create_pair_on_storage(
@ -468,7 +500,7 @@ class HBSDREST(common.HBSDCommon):
loop = loopingcall.FixedIntervalLoopingCall(
_wait_for_copy_pair_smpl, timeutils.utcnow(), ldev)
if not loop.start(interval=interval).wait():
msg = utils.output_log(
msg = self.output_log(
MSG.PAIR_STATUS_WAIT_TIMEOUT, svol=ldev)
self.raise_error(msg)
@ -489,27 +521,65 @@ class HBSDREST(common.HBSDCommon):
pvol, mun, ignore_return_code=ignore_return_code)
self._wait_copy_pair_deleting(svol)
def _get_pair_ports(self):
return (self.storage_info['pair_ports'] or
self.storage_info['controller_ports'])
def terminate_pair_connection(self, ldev):
targets = {
'list': [],
}
ldev_info = self.get_ldev_info(['status', 'attributes'], ldev)
if (ldev_info['status'] == NORMAL_STS and
self.driver_info['mirror_attr'] in ldev_info['attributes']):
LOG.debug(
'The specified LDEV has replication pair. '
'Therefore, unmapping operation was skipped. '
'(LDEV: %(ldev)s, vol_attr: %(info)s)',
{'ldev': ldev, 'info': ldev_info['attributes']})
return
self._find_mapped_targets_from_storage(
targets, ldev, self._get_pair_ports(), is_pair=True)
self.unmap_ldev(targets, ldev)
def delete_pair_based_on_svol(self, pvol, svol_info):
"""Disconnect all volume pairs to which the specified S-VOL belongs."""
# If the pair status does not satisfy the execution condition,
if not (svol_info['is_psus'] or
_STATUS_TABLE.get(svol_info['status']) == SMPP):
utils.output_log(
self.output_log(
MSG.UNABLE_TO_DELETE_PAIR, pvol=pvol, svol=svol_info['ldev'])
self.raise_busy()
self._delete_pair_from_storage(pvol, svol_info['ldev'])
if hasattr(
self.conf,
self.driver_info['param_prefix'] + '_rest_pair_target_ports'):
self.terminate_pair_connection(svol_info['ldev'])
self.terminate_pair_connection(pvol)
def check_param(self):
"""Check parameter values and consistency among them."""
super(HBSDREST, self).check_param()
self.check_opts(self.conf, REST_VOLUME_OPTS)
self.check_opts(self.conf, san.san_opts)
if hasattr(
self.conf,
self.driver_info['param_prefix'] + '_rest_pair_target_ports'):
self.check_opts(self.conf, REST_PAIR_OPTS)
if (not self.conf.hitachi_target_ports and
not self.conf.hitachi_rest_pair_target_ports):
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] +
'_target_ports or ' + self.driver_info['param_prefix'] +
'_rest_pair_target_ports')
self.raise_error(msg)
LOG.debug(
'Setting ldev_range: %s', self.storage_info['ldev_range'])
for opt in _REQUIRED_REST_OPTS:
if not self.conf.safe_get(opt):
msg = utils.output_log(MSG.INVALID_PARAMETER, param=opt)
msg = self.output_log(MSG.INVALID_PARAMETER, param=opt)
self.raise_error(msg)
if not self.conf.safe_get('san_api_port'):
self.conf.san_api_port = _REST_DEFAULT_PORT
@ -544,8 +614,8 @@ class HBSDREST(common.HBSDCommon):
else:
lun = assigned_lun
elif err_code == rest_api.ANOTHER_LDEV_MAPPED:
utils.output_log(MSG.MAP_LDEV_FAILED,
ldev=ldev, port=port, id=gid, lun=lun)
self.output_log(MSG.MAP_LDEV_FAILED,
ldev=ldev, port=port, id=gid, lun=lun)
return None
LOG.debug(
'Created logical unit path to the specified logical device. '
@ -554,12 +624,18 @@ class HBSDREST(common.HBSDCommon):
{'ldev': ldev, 'port': port, 'gid': gid, 'lun': lun})
return lun
def map_ldev(self, targets, ldev):
def map_ldev(self, targets, ldev, lun=None):
"""Create the path between the server and the LDEV and return LUN."""
port, gid = targets['list'][0]
lun = self._run_add_lun(ldev, port, gid)
targets['lun'][port] = True
for port, gid in targets['list'][1:]:
raise_err = False
if lun is not None:
head = 0
raise_err = True
else:
head = 1
port, gid = targets['list'][0]
lun = self._run_add_lun(ldev, port, gid)
targets['lun'][port] = True
for port, gid in targets['list'][head:]:
# When multipath is configured, Nova compute expects that
# target_lun define the same value in all storage target.
# Therefore, it should use same value of lun in other target.
@ -567,12 +643,19 @@ class HBSDREST(common.HBSDCommon):
lun2 = self._run_add_lun(ldev, port, gid, lun=lun)
if lun2 is not None:
targets['lun'][port] = True
raise_err = False
except exception.VolumeDriverException:
utils.output_log(MSG.MAP_LDEV_FAILED, ldev=ldev,
port=port, id=gid, lun=lun)
self.output_log(MSG.MAP_LDEV_FAILED, ldev=ldev,
port=port, id=gid, lun=lun)
if raise_err:
msg = self.output_log(
MSG.CONNECT_VOLUME_FAILED,
ldev=ldev, reason='Failed to attach in all ports.')
self.raise_error(msg)
return lun
def attach_ldev(self, volume, ldev, connector, is_snapshot, targets):
def attach_ldev(
self, volume, ldev, connector, is_snapshot, targets, lun=None):
"""Initialize connection between the server and the volume."""
target_ports = self.get_target_ports(connector)
target_ports = self.filter_target_ports(target_ports, volume,
@ -587,9 +670,10 @@ class HBSDREST(common.HBSDCommon):
targets['list'].sort()
for port in target_ports:
targets['lun'][port] = False
return int(self.map_ldev(targets, ldev))
return int(self.map_ldev(targets, ldev, lun))
def _find_mapped_targets_from_storage(self, targets, ldev, target_ports):
def _find_mapped_targets_from_storage(
self, targets, ldev, target_ports, is_pair=False):
"""Update port-gid list for the specified LDEV."""
ldev_info = self.get_ldev_info(['ports'], ldev)
if not ldev_info['ports']:
@ -597,7 +681,7 @@ class HBSDREST(common.HBSDCommon):
for port_info in ldev_info['ports']:
if _is_valid_target(self, port_info['portId'],
port_info['hostGroupName'],
target_ports):
target_ports, is_pair):
targets['list'].append(port_info)
def _get_unmap_targets_list(self, target_list, mapped_list):
@ -649,7 +733,7 @@ class HBSDREST(common.HBSDCommon):
self.client.delete_host_grp(port, gid)
result = 0
except exception.VolumeDriverException:
utils.output_log(MSG.DELETE_TARGET_FAILED, port=port, id=gid)
self.output_log(MSG.DELETE_TARGET_FAILED, port=port, id=gid)
else:
LOG.debug(
'Deleted target. (port: %(port)s, gid: %(gid)s)',
@ -717,7 +801,7 @@ class HBSDREST(common.HBSDCommon):
rest_api.MSGID_SPECIFIED_OBJECT_DOES_NOT_EXIST])
if 'errorSource' in result:
msg = utils.output_log(MSG.POOL_NOT_FOUND, pool=pool_id)
msg = self.output_log(MSG.POOL_NOT_FOUND, pool=pool_id)
self.raise_error(msg)
tp_cap = result['totalPoolCapacity'] // units.Ki
@ -731,7 +815,7 @@ class HBSDREST(common.HBSDCommon):
try:
result = self.client.get_pools()
except exception.VolumeDriverException:
utils.output_log(MSG.POOL_INFO_RETRIEVAL_FAILED, pool='all')
self.output_log(MSG.POOL_INFO_RETRIEVAL_FAILED, pool='all')
pool_infos = []
for pool_id in pool_ids:
for pool_data in result:
@ -739,7 +823,7 @@ class HBSDREST(common.HBSDCommon):
cap_data = self.get_pool_info(pool_id, pool_data)
break
else:
utils.output_log(MSG.POOL_NOT_FOUND, pool=pool_id)
self.output_log(MSG.POOL_NOT_FOUND, pool=pool_id)
cap_data = None
pool_infos.append(cap_data)
return pool_infos
@ -747,11 +831,11 @@ class HBSDREST(common.HBSDCommon):
def discard_zero_page(self, volume):
"""Return the volume's no-data pages to the storage pool."""
if self.conf.hitachi_discard_zero_page:
ldev = utils.get_ldev(volume)
ldev = self.get_ldev(volume)
try:
self.client.discard_zero_page(ldev)
except exception.VolumeDriverException:
utils.output_log(MSG.DISCARD_ZERO_PAGE_FAILED, ldev=ldev)
self.output_log(MSG.DISCARD_ZERO_PAGE_FAILED, ldev=ldev)
def _get_copy_pair_info(self, ldev):
"""Return info of the copy pair."""
@ -832,7 +916,7 @@ class HBSDREST(common.HBSDCommon):
"""Return the size[GB] of the specified LDEV."""
ldev_info = self.get_ldev_info(
_CHECK_LDEV_SIZE_KEYS, ldev)
_check_ldev_size(ldev_info, ldev, existing_ref)
_check_ldev_size(self, ldev_info, ldev, existing_ref)
return ldev_info['blockCapacity'] / utils.GIGABYTE_PER_BLOCK_SIZE
def _get_pool_id(self, pool_list, pool_name_or_id):
@ -844,7 +928,7 @@ class HBSDREST(common.HBSDCommon):
for pool_data in pool_list['pool_list']:
if pool_data['poolName'] == pool_name_or_id:
return pool_data['poolId']
msg = utils.output_log(MSG.POOL_NOT_FOUND, pool=pool_name_or_id)
msg = self.output_log(MSG.POOL_NOT_FOUND, pool=pool_name_or_id)
self.raise_error(msg)
def check_pool_id(self):
@ -942,11 +1026,11 @@ class HBSDREST(common.HBSDCommon):
obj_update['status'] = 'available' if isinstance(
exc, (exception.VolumeIsBusy,
exception.SnapshotIsBusy)) else 'error'
utils.output_log(
self.output_log(
MSG.GROUP_OBJECT_DELETE_FAILED,
obj='snapshot' if is_snapshot else 'volume',
group='group snapshot' if is_snapshot else 'group',
group_id=group.id, obj_id=obj.id, ldev=utils.get_ldev(obj),
group_id=group.id, obj_id=obj.id, ldev=self.get_ldev(obj),
reason=exc.msg)
raise loopingcall.LoopingCallDone(obj_update)
@ -977,9 +1061,9 @@ class HBSDREST(common.HBSDCommon):
def _create_group_volume_from_src(context, volume, src, from_snapshot):
volume_model_update = {'id': volume.id}
try:
ldev = utils.get_ldev(src)
ldev = self.get_ldev(src)
if ldev is None:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY,
type='snapshot' if from_snapshot else 'volume',
id=src.id)
@ -1009,7 +1093,7 @@ class HBSDREST(common.HBSDCommon):
msg = volume_model_update['msg']
else:
volumes_model_update.append(volume_model_update)
ldev = utils.get_ldev(volume_model_update)
ldev = self.get_ldev(volume_model_update)
if ldev is not None:
new_ldevs.append(ldev)
if not is_success:
@ -1020,18 +1104,18 @@ class HBSDREST(common.HBSDCommon):
try:
self.delete_ldev(new_ldev)
except exception.VolumeDriverException:
utils.output_log(MSG.DELETE_LDEV_FAILED, ldev=new_ldev)
self.output_log(MSG.DELETE_LDEV_FAILED, ldev=new_ldev)
return None, volumes_model_update
def update_group(self, group, add_volumes=None):
if add_volumes and volume_utils.is_group_a_cg_snapshot_type(group):
for volume in add_volumes:
ldev = utils.get_ldev(volume)
ldev = self.get_ldev(volume)
if ldev is None:
msg = utils.output_log(MSG.LDEV_NOT_EXIST_FOR_ADD_GROUP,
volume_id=volume.id,
group='consistency group',
group_id=group.id)
msg = self.output_log(MSG.LDEV_NOT_EXIST_FOR_ADD_GROUP,
volume_id=volume.id,
group='consistency group',
group_id=group.id)
self.raise_error(msg)
return None, None, None
@ -1048,7 +1132,7 @@ class HBSDREST(common.HBSDCommon):
fields.SnapshotStatus.AVAILABLE)
except Exception:
snapshot_model_update['status'] = fields.SnapshotStatus.ERROR
utils.output_log(
self.output_log(
MSG.GROUP_SNAPSHOT_CREATE_FAILED,
group=group_snapshot.group_id,
group_snapshot=group_snapshot.id,
@ -1084,8 +1168,8 @@ class HBSDREST(common.HBSDCommon):
try:
self._delete_pair_from_storage(pair['pvol'], pair['svol'])
except exception.VolumeDriverException:
utils.output_log(MSG.DELETE_PAIR_FAILED, pvol=pair['pvol'],
svol=pair['svol'])
self.output_log(MSG.DELETE_PAIR_FAILED, pvol=pair['pvol'],
svol=pair['svol'])
def _create_ctg_snap_pair(self, pairs):
snapshotgroup_name = self._create_ctg_snapshot_group_name(
@ -1107,12 +1191,12 @@ class HBSDREST(common.HBSDCommon):
_MAX_CTG_COUNT_EXCEEDED_ADD_SNAPSHOT) or
(utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
_MAX_PAIR_COUNT_IN_CTG_EXCEEDED_ADD_SNAPSHOT)):
msg = utils.output_log(MSG.FAILED_CREATE_CTG_SNAPSHOT)
msg = self.output_log(MSG.FAILED_CREATE_CTG_SNAPSHOT)
self.raise_error(msg)
elif (utils.safe_get_err_code(ex.kwargs.get('errobj')) ==
rest_api.INVALID_SNAPSHOT_POOL and
not self.conf.hitachi_snap_pool):
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_PARAMETER,
param=self.driver_info['param_prefix'] +
'_snap_pool')
@ -1134,9 +1218,9 @@ class HBSDREST(common.HBSDCommon):
def _create_cgsnapshot_volume(snapshot):
pair = {'snapshot': snapshot}
try:
pair['pvol'] = utils.get_ldev(snapshot.volume)
pair['pvol'] = self.get_ldev(snapshot.volume)
if pair['pvol'] is None:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY,
type='volume', id=snapshot.volume_id)
self.raise_error(msg)
@ -1150,9 +1234,9 @@ class HBSDREST(common.HBSDCommon):
try:
for snapshot in snapshots:
ldev = utils.get_ldev(snapshot.volume)
ldev = self.get_ldev(snapshot.volume)
if ldev is None:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY, type='volume',
id=snapshot.volume_id)
self.raise_error(msg)
@ -1177,7 +1261,7 @@ class HBSDREST(common.HBSDCommon):
try:
self.delete_ldev(pair['svol'])
except exception.VolumeDriverException:
utils.output_log(
self.output_log(
MSG.DELETE_LDEV_FAILED, ldev=pair['svol'])
model_update = {'status': fields.GroupSnapshotStatus.ERROR}
for snapshot in snapshots:
@ -1199,15 +1283,87 @@ class HBSDREST(common.HBSDCommon):
else:
return self._create_non_cgsnapshot(group_snapshot, snapshots)
def _init_pair_targets(self, targets_info):
self._pair_targets = []
for port in targets_info.keys():
if not targets_info[port]:
continue
params = {'portId': port}
host_grp_list = self.client.get_host_grps(params)
gid = None
for host_grp_data in host_grp_list:
if host_grp_data['hostGroupName'] == self._PAIR_TARGET_NAME:
gid = host_grp_data['hostGroupNumber']
break
if not gid:
try:
connector = {
'ip': self._PAIR_TARGET_NAME_BODY,
'wwpns': [self._PAIR_TARGET_NAME_BODY],
}
target_name, gid = self.create_target_to_storage(
port, connector, None)
LOG.debug(
'Created host group for pair operation. '
'(port: %(port)s, gid: %(gid)s)',
{'port': port, 'gid': gid})
except exception.VolumeDriverException:
self.output_log(MSG.CREATE_HOST_GROUP_FAILED, port=port)
continue
self._pair_targets.append((port, gid))
if not self._pair_targets:
msg = self.output_log(MSG.PAIR_TARGET_FAILED)
self.raise_error(msg)
self._pair_targets.sort(reverse=True)
LOG.debug('Setting pair_targets: %s', self._pair_targets)
def init_cinder_hosts(self, **kwargs):
targets = {
'info': {},
'list': [],
'iqns': {},
'target_map': {},
}
super(HBSDREST, self).init_cinder_hosts(targets=targets)
if self.storage_info['pair_ports']:
targets['info'] = {}
ports = self._get_pair_ports()
for port in ports:
targets['info'][port] = True
if hasattr(
self.conf,
self.driver_info['param_prefix'] + '_rest_pair_target_ports'):
self._init_pair_targets(targets['info'])
def initialize_pair_connection(self, ldev):
port, gid = None, None
for port, gid in self._pair_targets:
try:
targets = {
'info': {},
'list': [(port, gid)],
'lun': {},
}
return self.map_ldev(targets, ldev)
except exception.VolumeDriverException:
self.output_log(
MSG.MAP_LDEV_FAILED, ldev=ldev, port=port, id=gid,
lun=None)
msg = self.output_log(MSG.MAP_PAIR_TARGET_FAILED, ldev=ldev)
self.raise_error(msg)
def migrate_volume(self, volume, host, new_type=None):
"""Migrate the specified volume."""
attachments = volume.volume_attachment
if attachments:
return False, None
pvol = utils.get_ldev(volume)
pvol = self.get_ldev(volume)
if pvol is None:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY, type='volume', id=volume.id)
self.raise_error(msg)
@ -1226,7 +1382,7 @@ class HBSDREST(common.HBSDCommon):
(pvol, svol, copy_method, status)
for svol, copy_method, status in
zip(svols, copy_methods, svol_statuses)]
msg = utils.output_log(
msg = self.output_log(
MSG.MIGRATE_VOLUME_FAILED,
volume=volume.id, ldev=pvol,
pair_info=', '.join(pair_info))
@ -1239,7 +1395,7 @@ class HBSDREST(common.HBSDCommon):
pair_info = '(%s, %s, %s, %s)' % (
pair_info['pvol'], svol_info['ldev'],
utils.THIN, svol_info['status'])
msg = utils.output_log(
msg = self.output_log(
MSG.MIGRATE_VOLUME_FAILED,
volume=volume.id, ldev=svol_info['ldev'],
pair_info=pair_info)
@ -1272,7 +1428,7 @@ class HBSDREST(common.HBSDCommon):
try:
self.delete_ldev(pvol)
except exception.VolumeDriverException:
utils.output_log(MSG.DELETE_LDEV_FAILED, ldev=pvol)
self.output_log(MSG.DELETE_LDEV_FAILED, ldev=pvol)
return True, {
'provider_location': str(svol),
@ -1290,9 +1446,9 @@ class HBSDREST(common.HBSDCommon):
return False
return True
ldev = utils.get_ldev(volume)
ldev = self.get_ldev(volume)
if ldev is None:
msg = utils.output_log(
msg = self.output_log(
MSG.INVALID_LDEV_FOR_VOLUME_COPY, type='volume',
id=volume['id'])
self.raise_error(msg)
@ -1313,11 +1469,13 @@ class HBSDREST(common.HBSDCommon):
self._wait_copy_pair_status(svol, set([SMPL, PSUE]))
status = self._get_copy_pair_status(svol)
if status == PSUE:
msg = utils.output_log(
MSG.VOLUME_COPY_FAILED, pvol=pvol, svol=svol)
msg = self.output_log(MSG.VOLUME_COPY_FAILED, pvol=pvol, svol=svol)
self.raise_error(msg)
def create_target_name(self, connector):
if ('ip' in connector and connector['ip']
== self._PAIR_TARGET_NAME_BODY):
return self._PAIR_TARGET_NAME
wwn = (min(self.get_hba_ids_from_connector(connector)) if
self.format_info['group_name_var_cnt'][
common.GROUP_NAME_VAR_WWN] else '')

View File

@ -1,4 +1,4 @@
# Copyright (C) 2020, 2021, Hitachi, Ltd.
# Copyright (C) 2020, 2022, 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
@ -26,8 +26,6 @@ from oslo_service import loopingcall
from oslo_utils import timeutils
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.connection import HTTPConnection
from requests.packages.urllib3.poolmanager import PoolManager
from cinder import exception
from cinder.i18n import _
@ -46,13 +44,18 @@ _REST_SERVER_RESTART_TIMEOUT = 10 * 60
_REST_SERVER_ERROR_TIMEOUT = 10 * 60
_KEEP_SESSION_LOOP_INTERVAL = 3 * 60
_ANOTHER_LDEV_MAPPED_RETRY_TIMEOUT = 10 * 60
_LOCK_RESOURCE_GROUP_TIMEOUT = 3 * 60
_TCP_KEEPIDLE = 60
_TCP_KEEPINTVL = 15
_TCP_KEEPCNT = 4
_MIRROR_RESERVED_VIRTUAL_LDEV_ID = 65535
_HTTPS = 'https://'
_NOT_SPECIFIED = 'NotSpecified'
_REST_LOCKED_ERRORS = [
('2E11', '2205'),
('2E11', '2207'),
@ -90,6 +93,13 @@ LOG = logging.getLogger(__name__)
MSG = utils.HBSDMsg
def _get_device_group_name(remote_client, copy_group_name, is_secondary,
is_remote=False):
if remote_client is None and is_remote:
return _NOT_SPECIFIED
return copy_group_name + ('S' if is_secondary ^ is_remote else 'P')
def _build_base_url(ip_addr, ip_port):
return '%(https)s%(ip)s:%(port)s/ConfigurationManager' % {
'https': _HTTPS,
@ -101,7 +111,8 @@ def _build_base_url(ip_addr, ip_port):
class KeepAliveAdapter(HTTPAdapter):
def __init__(self, conf):
self.options = HTTPConnection.default_socket_options + [
self.socket_options = [
(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),
(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE,
conf.hitachi_rest_tcp_keepidle),
@ -113,11 +124,9 @@ class KeepAliveAdapter(HTTPAdapter):
super(KeepAliveAdapter, self).__init__()
def init_poolmanager(self, connections, maxsize, block=False):
self.poolmanager = PoolManager(num_pools=connections,
maxsize=maxsize,
block=block,
socket_options=self.options)
def init_poolmanager(self, *args, **kwargs):
kwargs['socket_options'] = self.socket_options
super(KeepAliveAdapter, self).init_poolmanager(*args, **kwargs)
class ResponseData(dict):
@ -226,7 +235,7 @@ class RestApiClient():
def __init__(self, conf, ip_addr, ip_port, storage_device_id,
user_id, user_pass, driver_prefix, tcp_keepalive=False,
verify=False):
verify=False, is_rep=False):
"""Initialize instance variables."""
self.conf = conf
self.ip_addr = ip_addr
@ -238,9 +247,12 @@ class RestApiClient():
self.tcp_keepalive = tcp_keepalive
self.verify = verify
self.connect_timeout = self.conf.hitachi_rest_connect_timeout
self.is_rep = is_rep
self.login_lock = threading.Lock()
self.keep_session_loop = loopingcall.FixedIntervalLoopingCall(
self._keep_session)
self.nested_count = 0
self.resource_lock = threading.Lock()
self.base_url = _build_base_url(ip_addr, self.ip_port)
self.object_url = '%(base_url)s/v1/objects/storages/%(storage_id)s' % {
@ -295,6 +307,10 @@ class RestApiClient():
else:
read_timeout = self.conf.hitachi_rest_get_api_response_timeout
remote_auth = kwargs.get('remote_auth')
if remote_auth:
headers["Remote-Authorization"] = 'Session ' + remote_auth.token
auth_data = kwargs.get('auth', self.get_my_session())
timeout = (self.connect_timeout, read_timeout)
@ -320,7 +336,7 @@ class RestApiClient():
verify=self.verify)
except Exception as e:
msg = utils.output_log(
msg = self.output_log(
MSG.REST_SERVER_CONNECT_FAILED,
exception=type(e), message=e,
method=method, url=url, params=params, body=body)
@ -361,11 +377,11 @@ class RestApiClient():
if (kwargs['no_retry'] or
utils.timed_out(
start_time, self.conf.hitachi_lock_timeout)):
msg = utils.output_log(MSG.REST_API_FAILED,
no_log=kwargs['no_log'],
method=method, url=url,
params=params, body=body,
**response.get_errobj())
msg = self.output_log(MSG.REST_API_FAILED,
no_log=kwargs['no_log'],
method=method, url=url,
params=params, body=body,
**response.get_errobj())
if kwargs['do_raise']:
message = _(
'%(prefix)s error occurred. %(msg)s' % {
@ -409,27 +425,27 @@ class RestApiClient():
retry = False
elif retry and utils.timed_out(start_time, kwargs['timeout']):
if kwargs['timeout_message']:
utils.output_log(kwargs['timeout_message'][0],
**kwargs['timeout_message'][1])
self.output_log(kwargs['timeout_message'][0],
**kwargs['timeout_message'][1])
if response.is_json():
msg = utils.output_log(MSG.REST_API_TIMEOUT,
no_log=kwargs['no_log'],
method=method, url=url,
params=params, body=body,
**response.get_job_result())
msg = self.output_log(MSG.REST_API_TIMEOUT,
no_log=kwargs['no_log'],
method=method, url=url,
params=params, body=body,
**response.get_job_result())
if errobj:
msg = utils.output_log(MSG.REST_API_FAILED,
no_log=kwargs['no_log'],
method=method, url=url,
params=params, body=body,
**response.get_errobj())
msg = self.output_log(MSG.REST_API_FAILED,
no_log=kwargs['no_log'],
method=method, url=url,
params=params, body=body,
**response.get_errobj())
else:
msg = utils.output_log(MSG.REST_API_HTTP_ERROR,
no_log=kwargs['no_log'],
status_code=response['status_code'],
response_body=rsp_body,
method=method, url=url,
params=params, body=body)
msg = self.output_log(MSG.REST_API_HTTP_ERROR,
no_log=kwargs['no_log'],
status_code=response['status_code'],
response_body=rsp_body,
method=method, url=url,
params=params, body=body)
if kwargs['do_raise']:
message = _(
'%(prefix)s error occurred. %(msg)s' % {
@ -448,18 +464,18 @@ class RestApiClient():
if not retry:
if response.is_json():
msg = utils.output_log(MSG.REST_API_FAILED,
no_log=kwargs['no_log'],
method=method, url=url,
params=params, body=body,
**response.get_errobj())
msg = self.output_log(MSG.REST_API_FAILED,
no_log=kwargs['no_log'],
method=method, url=url,
params=params, body=body,
**response.get_errobj())
else:
msg = utils.output_log(MSG.REST_API_HTTP_ERROR,
no_log=kwargs['no_log'],
status_code=response['status_code'],
response_body=rsp_body,
method=method, url=url,
params=params, body=body)
msg = self.output_log(MSG.REST_API_HTTP_ERROR,
no_log=kwargs['no_log'],
status_code=response['status_code'],
response_body=rsp_body,
method=method, url=url,
params=params, body=body)
if kwargs['do_raise']:
message = _(
'%(prefix)s error occurred. %(msg)s' % {
@ -471,6 +487,39 @@ class RestApiClient():
message, errobj=errobj)
return retry, rsp_body, errobj
def lock_resource_group(self, waittime=_LOCK_RESOURCE_GROUP_TIMEOUT):
"""Lock resources.
Lock resources of a resource group allocated to the user who
executes API requests, preventing other users from performing
operations on the resources.
"""
with self.resource_lock:
if self.nested_count <= 0:
url = '%(url)s/resource-group-service/actions/%(action)s' % {
'url': self.service_url,
'action': 'lock',
} + '/invoke'
if waittime:
body = {"parameters": {"waitTime": waittime}}
self._invoke(url, body=body, timeout=waittime)
else:
self._invoke(url)
self.nested_count += 1
def unlock_resource_group(self):
"""If the lock is already released, there is no need to unlock."""
with self.resource_lock:
if self.nested_count == 0:
return
self.nested_count -= 1
if self.nested_count <= 0:
url = '%(url)s/resource-group-service/actions/%(action)s' % {
'url': self.service_url,
'action': 'unlock',
} + '/invoke'
self._invoke(url)
def set_my_session(self, session):
self.session = session
@ -527,7 +576,7 @@ class RestApiClient():
LOG.debug("Trying to re-login.")
retry = self._login(do_raise=False)
if not retry:
utils.output_log(
self.output_log(
MSG.REST_LOGIN_FAILED,
no_log=no_log, user=self.user_id)
return retry
@ -838,3 +887,171 @@ class RestApiClient():
'action': 'discard-zero-page',
}
self._invoke(url)
def get_remote_copy_grps(self, remote_client):
url = '%(url)s/remote-mirror-copygroups' % {
'url': self.object_url,
}
params = {"remoteStorageDeviceId": remote_client.storage_id}
with RemoteSession(remote_client) as session:
return self._get_objects(url, params=params, remote_auth=session)
def get_remote_copy_grp(self, remote_client, copy_group_name, **kwargs):
url = '%(url)s/remote-mirror-copygroups/%(id)s' % {
'url': self.object_url,
'id': self._remote_copygroup_id(remote_client, copy_group_name),
}
with RemoteSession(remote_client) as session:
return self._get_object(url, remote_auth=session, **kwargs)
def get_remote_copypair(self, remote_client, copy_group_name,
pvol_ldev_id, svol_ldev_id, is_secondary=False,
**kwargs):
url = '%(url)s/remote-mirror-copypairs/%(id)s' % {
'url': self.object_url,
'id': self._remote_copypair_id(
remote_client, copy_group_name, pvol_ldev_id, svol_ldev_id,
is_secondary),
}
if remote_client:
with RemoteSession(remote_client) as session:
return self._get_object(url, remote_auth=session, **kwargs)
return self._get_object(url, **kwargs)
def add_remote_copypair(self, remote_client, body):
url = '%(url)s/remote-mirror-copypairs' % {
'url': self.object_url,
}
if self.storage_id > remote_client.storage_id:
client1, client2 = self, remote_client
else:
client1, client2 = remote_client, self
with ResourceGroupLock(client1):
with ResourceGroupLock(client2):
session = remote_client.get_my_session()
return self._add_object(url, body=body,
no_relogin=True,
remote_auth=session,
job_nowait=True)[0]
@utils.synchronized_on_copy_group()
def split_remote_copypair(self, remote_client, copy_group_name,
pvol_ldev_id, svol_ldev_id, rep_type):
body = {"parameters": {"replicationType": rep_type}}
url = '%(url)s/remote-mirror-copypairs/%(id)s/actions/%(action)s' % {
'url': self.object_url,
'id': self._remote_copypair_id(remote_client, copy_group_name,
pvol_ldev_id, svol_ldev_id),
'action': 'split',
} + '/invoke'
with RemoteSession(remote_client) as session:
self._invoke(url, body=body, remote_auth=session, job_nowait=True)
@utils.synchronized_on_copy_group()
def resync_remote_copypair(
self, remote_client, copy_group_name, pvol_ldev_id, svol_ldev_id,
rep_type, copy_speed=None):
body = {"parameters": {"replicationType": rep_type}}
if copy_speed:
body["parameters"]["copyPace"] = copy_speed
url = '%(url)s/remote-mirror-copypairs/%(id)s/actions/%(action)s' % {
'url': self.object_url,
'id': self._remote_copypair_id(remote_client, copy_group_name,
pvol_ldev_id, svol_ldev_id),
'action': 'resync',
} + '/invoke'
with RemoteSession(remote_client) as session:
self._invoke(url, body=body, remote_auth=session, job_nowait=True)
@utils.synchronized_on_copy_group()
def delete_remote_copypair(self, remote_client, copy_group_name,
pvol_ldev_id, svol_ldev_id):
url = '%(url)s/remote-mirror-copypairs/%(id)s' % {
'url': self.object_url,
'id': self._remote_copypair_id(
remote_client, copy_group_name, pvol_ldev_id, svol_ldev_id),
}
if self.storage_id > remote_client.storage_id:
client1, client2 = self, remote_client
else:
client1, client2 = remote_client, self
with ResourceGroupLock(client1):
with ResourceGroupLock(client2):
session = remote_client.get_my_session()
self._delete_object(
url, no_relogin=True, remote_auth=session)
def _remote_copygroup_id(self, remote_client, copy_group_name,
is_secondary=False):
storage_id = (remote_client.storage_id if remote_client
else _NOT_SPECIFIED)
return "%s,%s,%s,%s" % (
storage_id,
copy_group_name,
_get_device_group_name(remote_client, copy_group_name,
is_secondary),
_get_device_group_name(remote_client, copy_group_name,
is_secondary, is_remote=True))
def _remote_copypair_id(self, remote_client, copy_group_name,
pvol_ldev_id, svol_ldev_id, is_secondary=False):
return "%s,HBSD-LDEV-%d-%d" % (
self._remote_copygroup_id(remote_client, copy_group_name,
is_secondary),
pvol_ldev_id,
svol_ldev_id)
def assign_virtual_ldevid(
self, ldev_id,
virtual_ldev_id=_MIRROR_RESERVED_VIRTUAL_LDEV_ID):
url = '%(url)s/ldevs/%(id)s/actions/%(action)s/invoke' % {
'url': self.object_url,
'id': ldev_id,
'action': 'assign-virtual-ldevid',
}
body = {"parameters": {"virtualLdevId": virtual_ldev_id}}
ignore_error = [('2E21', '9305'), ('2E30', '0088')]
self._invoke(url, body=body, ignore_error=ignore_error)
def unassign_virtual_ldevid(
self, ldev_id,
virtual_ldev_id=_MIRROR_RESERVED_VIRTUAL_LDEV_ID):
url = '%(url)s/ldevs/%(id)s/actions/%(action)s/invoke' % {
'url': self.object_url,
'id': ldev_id,
'action': 'unassign-virtual-ldevid',
}
body = {"parameters": {"virtualLdevId": virtual_ldev_id}}
self._invoke(url, body=body)
def output_log(self, msg_enum, **kwargs):
if self.is_rep:
return utils.output_log(
msg_enum, storage_id=self.storage_id, **kwargs)
else:
return utils.output_log(msg_enum, **kwargs)
class RemoteSession(object):
def __init__(self, remote_client):
self.remote_client = remote_client
def __enter__(self):
return self.remote_client.get_my_session()
def __exit__(self, exc_type, exc_value, traceback):
pass
class ResourceGroupLock(object):
def __init__(self, client):
self.client = client
def __enter__(self):
self.client.lock_resource_group()
return self
def __exit__(self, exc_type, exc_value, traceback):
self.client.unlock_resource_group()

View File

@ -57,6 +57,12 @@ class HBSDRESTFC(rest.HBSDREST):
"""Prepare for using the storage."""
target_ports = self.conf.hitachi_target_ports
compute_target_ports = self.conf.hitachi_compute_target_ports
if hasattr(
self.conf,
self.driver_info['param_prefix'] + '_rest_pair_target_ports'):
pair_target_ports = self.conf.hitachi_rest_pair_target_ports
else:
pair_target_ports = []
available_ports = []
available_compute_ports = []
@ -64,13 +70,15 @@ class HBSDRESTFC(rest.HBSDREST):
# The port attributes must contain TAR.
params = {'portAttributes': 'TAR'}
port_list = self.client.get_ports(params=params)
for port in set(target_ports + compute_target_ports):
for port in set(target_ports + compute_target_ports +
pair_target_ports):
if port not in [port_data['portId'] for port_data in port_list]:
utils.output_log(MSG.INVALID_PORT, port=port,
additional_info='portAttributes: not TAR')
self.output_log(MSG.INVALID_PORT, port=port,
additional_info='portAttributes: not TAR')
for port_data in port_list:
port = port_data['portId']
if port not in set(target_ports + compute_target_ports):
if port not in set(target_ports + compute_target_ports +
pair_target_ports):
continue
secure_fc_port = True
can_port_schedule = True
@ -89,7 +97,7 @@ class HBSDRESTFC(rest.HBSDREST):
port_data.get('portConnection') == 'PtoP')):
can_port_schedule = False
if not secure_fc_port or not can_port_schedule:
utils.output_log(
self.output_log(
MSG.INVALID_PORT, port=port,
additional_info='portType: %s, lunSecuritySetting: %s, '
'fabricMode: %s, portConnection: %s' %
@ -107,6 +115,8 @@ class HBSDRESTFC(rest.HBSDREST):
can_port_schedule):
available_compute_ports.append(port)
self.storage_info['wwns'][port] = wwn
if pair_target_ports and port in pair_target_ports:
self.storage_info['pair_ports'].append(port)
if target_ports:
for port in target_ports:
@ -118,8 +128,14 @@ class HBSDRESTFC(rest.HBSDREST):
self.storage_info['compute_ports'].append(port)
self.check_ports_info()
utils.output_log(MSG.SET_CONFIG_VALUE, object='port-wwn list',
value=self.storage_info['wwns'])
if pair_target_ports and not self.storage_info['pair_ports']:
msg = self.output_log(
MSG.RESOURCE_NOT_FOUND, resource="Pair target ports")
self.raise_error(msg)
self.output_log(MSG.SET_CONFIG_VALUE, object='pair_target_ports',
value=self.storage_info['pair_ports'])
self.output_log(MSG.SET_CONFIG_VALUE, object='port-wwn list',
value=self.storage_info['wwns'])
def check_param(self):
"""Check parameter values and consistency among them."""
@ -150,15 +166,15 @@ class HBSDRESTFC(rest.HBSDREST):
self.client.add_hba_wwn(port, gid, wwn, no_log=True)
registered_wwns.append(wwn)
except exception.VolumeDriverException as ex:
utils.output_log(MSG.ADD_HBA_WWN_FAILED, port=port, gid=gid,
wwn=wwn)
self.output_log(MSG.ADD_HBA_WWN_FAILED, port=port, gid=gid,
wwn=wwn)
if (self.get_port_scheduler_param() and
utils.safe_get_err_code(ex.kwargs.get('errobj'))
== rest_api.EXCEED_WWN_MAX):
raise ex
if not registered_wwns:
msg = utils.output_log(MSG.NO_HBA_WWN_ADDED_TO_HOST_GRP, port=port,
gid=gid)
msg = self.output_log(MSG.NO_HBA_WWN_ADDED_TO_HOST_GRP, port=port,
gid=gid)
self.raise_error(msg)
def set_target_mode(self, port, gid):
@ -265,10 +281,12 @@ class HBSDRESTFC(rest.HBSDREST):
return not_found_count
def initialize_connection(self, volume, connector, is_snapshot=False):
def initialize_connection(
self, volume, connector, is_snapshot=False, lun=None,
is_mirror=False):
"""Initialize connection between the server and the volume."""
conn_info, map_info = super(HBSDRESTFC, self).initialize_connection(
volume, connector, is_snapshot)
volume, connector, is_snapshot, lun)
if self.conf.hitachi_zoning_request:
if (self.get_port_scheduler_param() and
not self.is_controller(connector)):
@ -279,10 +297,11 @@ class HBSDRESTFC(rest.HBSDREST):
self._lookup_service)
if init_targ_map:
conn_info['data']['initiator_target_map'] = init_targ_map
fczm_utils.add_fc_zone(conn_info)
if not is_mirror:
fczm_utils.add_fc_zone(conn_info)
return conn_info
def terminate_connection(self, volume, connector):
def terminate_connection(self, volume, connector, is_mirror=False):
"""Terminate connection between the server and the volume."""
conn_info = super(HBSDRESTFC, self).terminate_connection(
volume, connector)
@ -293,7 +312,8 @@ class HBSDRESTFC(rest.HBSDREST):
self._lookup_service)
if init_targ_map:
conn_info['data']['initiator_target_map'] = init_targ_map
fczm_utils.remove_fc_zone(conn_info)
if not is_mirror:
fczm_utils.remove_fc_zone(conn_info)
return conn_info
def _get_wwpns(self, port, hostgroup):
@ -335,8 +355,8 @@ class HBSDRESTFC(rest.HBSDREST):
active_hba_ids = list(set(active_hba_ids))
if not active_hba_ids:
msg = utils.output_log(MSG.NO_ACTIVE_WWN, wwn=', '.join(hba_ids),
volume=vol_id)
msg = self.output_log(MSG.NO_ACTIVE_WWN, wwn=', '.join(hba_ids),
volume=vol_id)
self.raise_error(msg)
active_target_wwns = list(set(active_target_wwns))
@ -347,7 +367,7 @@ class HBSDRESTFC(rest.HBSDREST):
port_wwns += ", "
port_wwns += ("port, WWN: " + port +
", " + self.storage_info['wwns'][port])
msg = utils.output_log(
msg = self.output_log(
MSG.NO_PORT_WITH_ACTIVE_WWN, port_wwns=port_wwns,
volume=vol_id)
self.raise_error(msg)
@ -371,17 +391,17 @@ class HBSDRESTFC(rest.HBSDREST):
== rest_api.MSGID_SPECIFIED_OBJECT_DOES_NOT_EXIST)
or (_MSG_EXCEED_HOST_GROUP_MAX
in utils.safe_get_message(ex.kwargs.get('errobj')))):
utils.output_log(
self.output_log(
MSG.HOST_GROUP_NUMBER_IS_MAXIMUM, port=ports[index])
elif (utils.safe_get_err_code(ex.kwargs.get('errobj'))
== rest_api.EXCEED_WWN_MAX):
utils.output_log(
self.output_log(
MSG.WWN_NUMBER_IS_MAXIMUM, port=ports[index],
wwn=", ". join(hba_ids))
else:
raise ex
msg = utils.output_log(
msg = self.output_log(
MSG.HOST_GROUP_OR_WWN_IS_NOT_AVAILABLE, ports=', '.join(ports))
self.raise_error(msg)
@ -391,7 +411,7 @@ class HBSDRESTFC(rest.HBSDREST):
active_ports = []
if not devmap:
msg = utils.output_log(MSG.ZONE_MANAGER_IS_NOT_AVAILABLE)
msg = self.output_log(MSG.ZONE_MANAGER_IS_NOT_AVAILABLE)
self.raise_error(msg)
for fabric_name in devmap.keys():
available_ports = []
@ -409,7 +429,7 @@ class HBSDRESTFC(rest.HBSDREST):
if port in available_ports and port in filter_ports:
active_ports.append(port)
elif port not in available_ports and port in filter_ports:
utils.output_log(
self.output_log(
MSG.INVALID_PORT_BY_ZONE_MANAGER, port=port)
for wwpns in wwpn_groups:
try:

View File

@ -46,20 +46,28 @@ class HBSDRESTISCSI(rest.HBSDREST):
"""Prepare for using the storage."""
target_ports = self.conf.hitachi_target_ports
compute_target_ports = self.conf.hitachi_compute_target_ports
if hasattr(
self.conf,
self.driver_info['param_prefix'] + '_rest_pair_target_ports'):
pair_target_ports = self.conf.hitachi_rest_pair_target_ports
else:
pair_target_ports = []
super(HBSDRESTISCSI, self).connect_storage()
# The port type must be ISCSI and the port attributes must contain TAR.
params = {'portType': 'ISCSI',
'portAttributes': 'TAR'}
port_list = self.client.get_ports(params=params)
for port in set(target_ports + compute_target_ports):
for port in set(target_ports + compute_target_ports +
pair_target_ports):
if port not in [port_data['portId'] for port_data in port_list]:
utils.output_log(
self.output_log(
MSG.INVALID_PORT, port=port, additional_info='(portType, '
'portAttributes): not (ISCSI, TAR)')
for port_data in port_list:
port = port_data['portId']
if port not in set(target_ports + compute_target_ports):
if port not in set(target_ports + compute_target_ports +
pair_target_ports):
continue
has_addr = True
if not port_data['lunSecuritySetting']:
@ -70,7 +78,7 @@ class HBSDRESTISCSI(rest.HBSDREST):
addr_info = (', ipv4Address: %s, tcpPort: %s' %
(ipv4_addr, tcp_port))
if not port_data['lunSecuritySetting'] or not has_addr:
utils.output_log(
self.output_log(
MSG.INVALID_PORT, port=port,
additional_info='portType: %s, lunSecuritySetting: %s%s' %
(port_data['portType'], port_data['lunSecuritySetting'],
@ -82,11 +90,20 @@ class HBSDRESTISCSI(rest.HBSDREST):
if (compute_target_ports and port in compute_target_ports and
has_addr):
self.storage_info['compute_ports'].append(port)
if pair_target_ports and port in pair_target_ports:
self.storage_info['pair_ports'].append(port)
self.check_ports_info()
utils.output_log(MSG.SET_CONFIG_VALUE,
object='port-<IP address:port> list',
value=self.storage_info['portals'])
if pair_target_ports and not self.storage_info['pair_ports']:
msg = self.output_log(
MSG.RESOURCE_NOT_FOUND, resource="Pair target ports")
self.raise_error(msg)
self.output_log(MSG.SET_CONFIG_VALUE,
object='pair_target_ports',
value=self.storage_info['pair_ports'])
self.output_log(MSG.SET_CONFIG_VALUE,
object='port-<IP address:port> list',
value=self.storage_info['portals'])
def create_target_to_storage(self, port, connector, hba_ids):
"""Create an iSCSI target on the specified port."""
@ -194,12 +211,19 @@ class HBSDRESTISCSI(rest.HBSDREST):
not_found_count += 1
return not_found_count
def initialize_connection(self, volume, connector, is_snapshot=False):
def initialize_connection(
self, volume, connector, is_snapshot=False, lun=None,
is_mirror=False):
"""Initialize connection between the server and the volume."""
conn_info, map_info = super(HBSDRESTISCSI, self).initialize_connection(
volume, connector, is_snapshot)
volume, connector, is_snapshot, lun)
return conn_info
def terminate_connection(self, volume, connector, is_mirror=False):
"""Terminate connection between the server and the volume."""
return super(HBSDRESTISCSI, self).terminate_connection(
volume, connector)
def get_properties_iscsi(self, targets, multipath):
"""Return iSCSI-specific server-LDEV connection info."""
if not multipath:
@ -213,8 +237,8 @@ class HBSDRESTISCSI(rest.HBSDREST):
target_info = self.client.get_host_grp(port, gid)
iqn = target_info.get('iscsiName') if target_info else None
if not iqn:
msg = utils.output_log(MSG.RESOURCE_NOT_FOUND,
resource='Target IQN')
msg = self.output_log(MSG.RESOURCE_NOT_FOUND,
resource='Target IQN')
self.raise_error(msg)
targets['iqns'][target] = iqn
LOG.debug(

View File

@ -15,17 +15,17 @@
"""Utility module for Hitachi HBSD Driver."""
import enum
import functools
import logging as base_logging
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import timeutils
from oslo_utils import units
from cinder import exception
from cinder import utils as cinder_utils
VERSION = '2.3.2'
VERSION = '2.3.3'
CI_WIKI_NAME = 'Hitachi_VSP_CI'
PARAM_PREFIX = 'hitachi'
VENDOR_NAME = 'Hitachi'
@ -38,9 +38,13 @@ HDT_VOL_ATTR = 'HDT'
NVOL_LDEV_TYPE = 'DP-VOL'
TARGET_IQN_SUFFIX = '.hbsd-target'
PAIR_ATTR = 'HTI'
MIRROR_ATTR = 'GAD'
GIGABYTE_PER_BLOCK_SIZE = units.Gi / 512
PRIMARY_STR = 'primary'
SECONDARY_STR = 'secondary'
NORMAL_LDEV_TYPE = 'Normal'
FULL = 'Full copy'
@ -202,6 +206,20 @@ class HBSDMsg(enum.Enum):
'(port: %(port)s, WWN: %(wwn)s)',
'suffix': WARNING_SUFFIX,
}
REPLICATION_VOLUME_OPERATION_FAILED = {
'msg_id': 337,
'loglevel': base_logging.WARNING,
'msg': 'Failed to %(operation)s the %(type)s in a replication pair. '
'(volume: %(volume_id)s, reason: %(reason)s)',
'suffix': WARNING_SUFFIX,
}
SITE_INITIALIZATION_FAILED = {
'msg_id': 338,
'loglevel': base_logging.WARNING,
'msg': 'Failed to initialize the driver for the %(site)s storage '
'system.',
'suffix': WARNING_SUFFIX,
}
INVALID_PORT = {
'msg_id': 339,
'loglevel': base_logging.WARNING,
@ -301,6 +319,19 @@ class HBSDMsg(enum.Enum):
'msg': 'Failed to add the logical device.',
'suffix': ERROR_SUFFIX,
}
PAIR_TARGET_FAILED = {
'msg_id': 638,
'loglevel': base_logging.ERROR,
'msg': 'Failed to add the pair target.',
'suffix': ERROR_SUFFIX,
}
MAP_PAIR_TARGET_FAILED = {
'msg_id': 639,
'loglevel': base_logging.ERROR,
'msg': 'Failed to map a logical device to any pair targets. '
'(LDEV: %(ldev)s)',
'suffix': ERROR_SUFFIX,
}
POOL_NOT_FOUND = {
'msg_id': 640,
'loglevel': base_logging.ERROR,
@ -391,11 +422,18 @@ class HBSDMsg(enum.Enum):
'This driver does not support unmanaging snapshots.',
'suffix': ERROR_SUFFIX,
}
INVALID_EXTRA_SPEC_KEY = {
'msg_id': 723,
'loglevel': base_logging.ERROR,
'msg': 'Failed to create a volume. '
'An invalid value is specified for the extra spec key '
'"%(key)s" of the volume type. (value: %(value)s)',
'suffix': ERROR_SUFFIX,
}
VOLUME_COPY_FAILED = {
'msg_id': 725,
'loglevel': base_logging.ERROR,
'msg': 'Failed to copy a volume. (copy method: %(copy_method)s, '
'P-VOL: %(pvol)s, S-VOL: %(svol)s)',
'msg': 'Failed to copy a volume. (P-VOL: %(pvol)s, S-VOL: %(svol)s)',
'suffix': ERROR_SUFFIX
}
REST_SERVER_CONNECT_FAILED = {
@ -482,6 +520,61 @@ class HBSDMsg(enum.Enum):
'resource of host group or wwn was found. (ports: %(ports)s)',
'suffix': ERROR_SUFFIX,
}
SITE_NOT_INITIALIZED = {
'msg_id': 751,
'loglevel': base_logging.ERROR,
'msg': 'The driver is not initialized for the %(site)s storage '
'system.',
'suffix': ERROR_SUFFIX,
}
CREATE_REPLICATION_VOLUME_FAILED = {
'msg_id': 752,
'loglevel': base_logging.ERROR,
'msg': 'Failed to create the %(type)s for a %(rep_type)s pair. '
'(volume: %(volume_id)s, volume type: %(volume_type)s, '
'size: %(size)s)',
'suffix': ERROR_SUFFIX,
}
CREATE_REPLICATION_PAIR_FAILED = {
'msg_id': 754,
'loglevel': base_logging.ERROR,
'msg': 'Failed to create a %(rep_type)s pair or '
'to mirror data in a %(rep_type)s pair. '
'(P-VOL: %(pvol)s, S-VOL: %(svol)s, copy group: '
'%(copy_group)s, pair status: %(status)s)',
'suffix': ERROR_SUFFIX,
}
SPLIT_REPLICATION_PAIR_FAILED = {
'msg_id': 755,
'loglevel': base_logging.ERROR,
'msg': 'Failed to split a %(rep_type)s pair. '
'(P-VOL: %(pvol)s, S-VOL: %(svol)s, '
'copy group: %(copy_group)s, pair status: %(status)s)',
'suffix': ERROR_SUFFIX,
}
PAIR_CHANGE_TIMEOUT = {
'msg_id': 756,
'loglevel': base_logging.ERROR,
'msg': 'A timeout occurred before the status of '
'the %(rep_type)s pair changes. '
'(P-VOL: %(pvol)s, S-VOL: %(svol)s, copy group: '
'%(copy_group)s, current status: %(current_status)s, '
'expected status: %(expected_status)s, timeout: %(timeout)s '
'seconds)',
'suffix': ERROR_SUFFIX,
}
EXTEND_REPLICATION_VOLUME_ERROR = {
'msg_id': 758,
'loglevel': base_logging.ERROR,
'msg': 'Failed to extend a volume. The LDEVs for the volume are in '
'a %(rep_type)s pair and the volume is attached. '
'(volume: %(volume_id)s, '
'LDEV: %(ldev)s, source size: %(source_size)s, destination '
'size: %(destination_size)s, P-VOL: %(pvol)s, S-VOL: %(svol)s, '
'P-VOL[numOfPorts]: %(pvol_num_of_ports)s, '
'S-VOL[numOfPorts]: %(svol_num_of_ports)s)',
'suffix': ERROR_SUFFIX,
}
MIGRATE_VOLUME_FAILED = {
'msg_id': 760,
'loglevel': base_logging.ERROR,
@ -490,6 +583,21 @@ class HBSDMsg(enum.Enum):
'(P-VOL, S-VOL, copy method, status): %(pair_info)s)',
'suffix': ERROR_SUFFIX,
}
REPLICATION_PAIR_ERROR = {
'msg_id': 766,
'loglevel': base_logging.ERROR,
'msg': 'Failed to %(operation)s. The LDEV for the volume is in '
'a remote replication pair. (volume: %(volume)s, '
'%(snapshot_info)sLDEV: %(ldev)s)',
'suffix': ERROR_SUFFIX,
}
LDEV_NUMBER_NOT_FOUND = {
'msg_id': 770,
'loglevel': base_logging.ERROR,
'msg': 'Failed to %(operation)s. The LDEV number is not found in the '
'Cinder object. (%(obj)s: %(obj_id)s)',
'suffix': ERROR_SUFFIX,
}
def __init__(self, error_info):
"""Initialize Enum attributes."""
@ -498,48 +606,36 @@ class HBSDMsg(enum.Enum):
self.msg = error_info['msg']
self.suffix = error_info['suffix']
def output_log(self, **kwargs):
def output_log(self, storage_id, **kwargs):
"""Output the message to the log file and return the message."""
msg = self.msg % kwargs
LOG.log(self.level, "MSGID%(msg_id)04d-%(msg_suffix)s: %(msg)s",
if storage_id:
LOG.log(
self.level,
"%(storage_id)s MSGID%(msg_id)04d-%(msg_suffix)s: %(msg)s",
{'storage_id': storage_id[-6:], 'msg_id': self.msg_id,
'msg_suffix': self.suffix, 'msg': msg})
else:
LOG.log(
self.level, "MSGID%(msg_id)04d-%(msg_suffix)s: %(msg)s",
{'msg_id': self.msg_id, 'msg_suffix': self.suffix, 'msg': msg})
return msg
def output_log(msg_enum, **kwargs):
def output_log(msg_enum, storage_id=None, **kwargs):
"""Output the specified message to the log file and return the message."""
return msg_enum.output_log(**kwargs)
return msg_enum.output_log(storage_id, **kwargs)
LOG = logging.getLogger(__name__)
MSG = HBSDMsg
def get_ldev(obj):
"""Get the LDEV number from the given object and return it as integer."""
if not obj:
return None
ldev = obj.get('provider_location')
if not ldev or not ldev.isdigit():
return None
return int(ldev)
def timed_out(start_time, timeout):
"""Check if the specified time has passed."""
return timeutils.is_older_than(start_time, timeout)
def check_opt_value(conf, names):
"""Check if the parameter names and values are valid."""
for name in names:
try:
getattr(conf, name)
except (cfg.NoSuchOptError, cfg.ConfigFileValueError):
with excutils.save_and_reraise_exception():
output_log(MSG.INVALID_PARAMETER, param=name)
def build_initiator_target_map(connector, target_wwns, lookup_service):
"""Return a dictionary mapping server-wwns and lists of storage-wwns."""
init_targ_map = {}
@ -614,3 +710,52 @@ def get_exception_msg(exc):
exc, exception.CinderException) else exc.args[0]
else:
return ""
def synchronized_on_copy_group():
def wrap(func):
@functools.wraps(func)
def inner(self, remote_client, copy_group_name, *args, **kwargs):
sync_key = '%s-%s' % (copy_group_name,
self.storage_id[-6:])
@cinder_utils.synchronized(sync_key, external=True)
def _inner():
return func(self, remote_client, copy_group_name,
*args, **kwargs)
return _inner()
return inner
return wrap
DICT = '_dict'
CONF = '_conf'
class Config(object):
def __init__(self, conf):
super().__setattr__(CONF, conf)
super().__setattr__(DICT, dict())
self._opts = {}
def __getitem__(self, name):
return (super().__getattribute__(DICT)[name]
if name in super().__getattribute__(DICT)
else super().__getattribute__(CONF).safe_get(name))
def __getattr__(self, name):
return (super().__getattribute__(DICT)[name]
if name in super().__getattribute__(DICT)
else getattr(super().__getattribute__(CONF), name))
def __setitem__(self, key, value):
super().__getattribute__(DICT)[key] = value
def __setattr__(self, key, value):
self.__setitem__(key, value)
def safe_get(self, name):
return (super().__getattribute__(DICT)[name]
if name in super().__getattribute__(DICT)
else super().__getattribute__(CONF).safe_get(name))

View File

@ -0,0 +1,11 @@
---
features:
- |
Hitachi driver: Support Global-Active Device (GAD) volume.
GAD is a one of Hitachi storage fucntion uses volume replication
to provide a high-availability environment for hosts across storage
systems and sites. New properties will be added in configuration.
``hbsd:topology`` sets to ``active_active_mirror_volumex`` would
specify a GAD volume. ``hitachi_mirror_xxx`` parameters would
specify a secondary storage for GAD volume.