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:
parent
e6637b94fa
commit
a92aa06e46
@ -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
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
972
cinder/volume/drivers/hitachi/hbsd_replication.py
Normal file
972
cinder/volume/drivers/hitachi/hbsd_replication.py
Normal 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)
|
@ -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 '')
|
||||
|
@ -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()
|
||||
|
@ -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:
|
||||
|
@ -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(
|
||||
|
@ -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))
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user