diff options
Diffstat (limited to 'cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hbsd_mirror_fc.py')
-rw-r--r-- | cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hbsd_mirror_fc.py | 1561 |
1 files changed, 1561 insertions, 0 deletions
diff --git a/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hbsd_mirror_fc.py b/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hbsd_mirror_fc.py new file mode 100644 index 000000000..e65c2fde7 --- /dev/null +++ b/cinder/tests/unit/volume/drivers/hitachi/test_hitachi_hbsd_mirror_fc.py @@ -0,0 +1,1561 @@ +# 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. +# +"""Unit tests for Hitachi HBSD Driver.""" + +import json +from unittest import mock + +from oslo_config import cfg +import requests + +from cinder import context as cinder_context +from cinder.db.sqlalchemy import api as sqlalchemy_api +from cinder import exception +from cinder.objects import group_snapshot as obj_group_snap +from cinder.objects import snapshot as obj_snap +from cinder.tests.unit import fake_group +from cinder.tests.unit import fake_group_snapshot +from cinder.tests.unit import fake_snapshot +from cinder.tests.unit import fake_volume +from cinder.tests.unit import test +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_rest +from cinder.volume.drivers.hitachi import hbsd_rest_api +from cinder.volume.drivers.hitachi import hbsd_utils +from cinder.volume import volume_types +from cinder.volume import volume_utils +from cinder.zonemanager import utils as fczm_utils + +# Configuration parameter values +CONFIG_MAP = { + 'serial': '886000123456', + 'my_ip': '127.0.0.1', + 'rest_server_ip_addr': '172.16.18.108', + 'rest_server_ip_port': '23451', + 'port_id': 'CL1-A', + 'host_grp_name': 'HBSD-0123456789abcdef', + 'host_mode': 'LINUX/IRIX', + 'host_wwn': '0123456789abcdef', + 'target_wwn': '1111111123456789', + 'user_id': 'user', + 'user_pass': 'password', + 'pool_name': 'test_pool', + 'auth_user': 'auth_user', + 'auth_password': 'auth_password', +} + +REMOTE_CONFIG_MAP = { + 'serial': '886000456789', + 'my_ip': '127.0.0.1', + 'rest_server_ip_addr': '172.16.18.107', + 'rest_server_ip_port': '334', + 'port_id': 'CL2-B', + 'host_grp_name': 'HBSD-0123456789abcdef', + 'host_mode': 'LINUX/IRIX', + 'host_wwn': '0123456789abcdef', + 'target_wwn': '2222222234567891', + 'user_id': 'remote-user', + 'user_pass': 'remote-password', + 'pool_name': 'remote_pool', + 'auth_user': 'remote_user', + 'auth_password': 'remote_password', +} + +# Dummy response for FC zoning device mapping +DEVICE_MAP = { + 'fabric_name': { + 'initiator_port_wwn_list': [CONFIG_MAP['host_wwn']], + 'target_port_wwn_list': [CONFIG_MAP['target_wwn']]}} + +REMOTE_DEVICE_MAP = { + 'fabric_name': { + 'initiator_port_wwn_list': [REMOTE_CONFIG_MAP['host_wwn']], + 'target_port_wwn_list': [REMOTE_CONFIG_MAP['target_wwn']]}} + +DEFAULT_CONNECTOR = { + 'host': 'host', + 'ip': CONFIG_MAP['my_ip'], + 'wwpns': [CONFIG_MAP['host_wwn']], + 'multipath': False, +} + +REMOTE_DEFAULT_CONNECTOR = { + 'host': 'host', + 'ip': REMOTE_CONFIG_MAP['my_ip'], + 'wwpns': [REMOTE_CONFIG_MAP['host_wwn']], + 'multipath': False, +} + +CTXT = cinder_context.get_admin_context() + +TEST_VOLUME = [] +for i in range(7): + volume = {} + volume['id'] = '00000000-0000-0000-0000-{0:012d}'.format(i) + volume['name'] = 'test-volume{0:d}'.format(i) + volume['volume_type_id'] = '00000000-0000-0000-0000-{0:012d}'.format(i) + if i == 3: + volume['provider_location'] = None + elif i == 4: + volume['provider_location'] = json.dumps( + {'pldev': 4, 'sldev': 4, + 'remote-copy': hbsd_utils.MIRROR_ATTR}) + elif i == 5: + volume['provider_location'] = json.dumps( + {'pldev': 5, 'sldev': 5, + 'remote-copy': hbsd_utils.MIRROR_ATTR}) + elif i == 6: + volume['provider_location'] = json.dumps( + {'pldev': 6, 'sldev': 6, + 'remote-copy': hbsd_utils.MIRROR_ATTR}) + else: + volume['provider_location'] = '{0:d}'.format(i) + volume['size'] = 128 + if i == 2 or i == 6: + volume['status'] = 'in-use' + else: + volume['status'] = 'available' + volume = fake_volume.fake_volume_obj(CTXT, **volume) + volume.volume_type = fake_volume.fake_volume_type_obj(CTXT) + TEST_VOLUME.append(volume) + + +def _volume_get(context, volume_id): + """Return predefined volume info.""" + return TEST_VOLUME[int(volume_id.replace("-", ""))] + + +TEST_SNAPSHOT = [] +snapshot = {} +snapshot['id'] = '10000000-0000-0000-0000-{0:012d}'.format(0) +snapshot['name'] = 'TEST_SNAPSHOT{0:d}'.format(0) +snapshot['provider_location'] = '{0:d}'.format(1) +snapshot['status'] = 'available' +snapshot['volume_id'] = '00000000-0000-0000-0000-{0:012d}'.format(0) +snapshot['volume'] = _volume_get(None, snapshot['volume_id']) +snapshot['volume_name'] = 'test-volume{0:d}'.format(0) +snapshot['volume_size'] = 128 +snapshot = obj_snap.Snapshot._from_db_object( + CTXT, obj_snap.Snapshot(), + fake_snapshot.fake_db_snapshot(**snapshot)) +TEST_SNAPSHOT.append(snapshot) + +TEST_GROUP = [] +for i in range(2): + group = {} + group['id'] = '20000000-0000-0000-0000-{0:012d}'.format(i) + group['status'] = 'available' + group = fake_group.fake_group_obj(CTXT, **group) + TEST_GROUP.append(group) + +TEST_GROUP_SNAP = [] +group_snapshot = {} +group_snapshot['id'] = '30000000-0000-0000-0000-{0:012d}'.format(0) +group_snapshot['status'] = 'available' +group_snapshot = obj_group_snap.GroupSnapshot._from_db_object( + CTXT, obj_group_snap.GroupSnapshot(), + fake_group_snapshot.fake_db_group_snapshot(**group_snapshot)) +TEST_GROUP_SNAP.append(group_snapshot) + +# Dummy response for REST API +POST_SESSIONS_RESULT = { + "token": "b74777a3-f9f0-4ea8-bd8f-09847fac48d3", + "sessionId": 0, +} + +REMOTE_POST_SESSIONS_RESULT = { + "token": "b74777a3-f9f0-4ea8-bd8f-09847fac48d4", + "sessionId": 0, +} + +GET_PORTS_RESULT = { + "data": [ + { + "portId": CONFIG_MAP['port_id'], + "portType": "FIBRE", + "portAttributes": [ + "TAR", + "MCU", + "RCU", + "ELUN" + ], + "fabricMode": True, + "portConnection": "PtoP", + "lunSecuritySetting": True, + "wwn": CONFIG_MAP['target_wwn'], + }, + ], +} + +REMOTE_GET_PORTS_RESULT = { + "data": [ + { + "portId": REMOTE_CONFIG_MAP['port_id'], + "portType": "FIBRE", + "portAttributes": [ + "TAR", + "MCU", + "RCU", + "ELUN" + ], + "fabricMode": True, + "portConnection": "PtoP", + "lunSecuritySetting": True, + "wwn": REMOTE_CONFIG_MAP['target_wwn'], + }, + ], +} + +GET_HOST_WWNS_RESULT = { + "data": [ + { + "hostGroupNumber": 0, + "hostWwn": CONFIG_MAP['host_wwn'], + }, + ], +} + +REMOTE_GET_HOST_WWNS_RESULT = { + "data": [ + { + "hostGroupNumber": 0, + "hostWwn": REMOTE_CONFIG_MAP['host_wwn'], + }, + ], +} + +COMPLETED_SUCCEEDED_RESULT = { + "status": "Completed", + "state": "Succeeded", + "affectedResources": ('a/b/c/1',), +} + +REMOTE_COMPLETED_SUCCEEDED_RESULT = { + "status": "Completed", + "state": "Succeeded", + "affectedResources": ('a/b/c/2',), +} + +COMPLETED_FAILED_RESULT_LU_DEFINED = { + "status": "Completed", + "state": "Failed", + "error": { + "errorCode": { + "SSB1": "B958", + "SSB2": "015A", + }, + }, +} + +GET_LDEV_RESULT = { + "emulationType": "OPEN-V-CVS", + "blockCapacity": 2097152, + "attributes": ["CVS", "HDP"], + "status": "NML", + "poolId": 30, + "dataReductionStatus": "DISABLED", + "dataReductionMode": "disabled", +} + +GET_LDEV_RESULT_MAPPED = { + "emulationType": "OPEN-V-CVS", + "blockCapacity": 2097152, + "attributes": ["CVS", "HDP"], + "status": "NML", + "ports": [ + { + "portId": CONFIG_MAP['port_id'], + "hostGroupNumber": 0, + "hostGroupName": CONFIG_MAP['host_grp_name'], + "lun": 1 + }, + ], +} + +REMOTE_GET_LDEV_RESULT_MAPPED = { + "emulationType": "OPEN-V-CVS", + "blockCapacity": 2097152, + "attributes": ["CVS", "HDP"], + "status": "NML", + "ports": [ + { + "portId": REMOTE_CONFIG_MAP['port_id'], + "hostGroupNumber": 0, + "hostGroupName": REMOTE_CONFIG_MAP['host_grp_name'], + "lun": 1 + }, + ], +} + +GET_LDEV_RESULT_PAIR = { + "emulationType": "OPEN-V-CVS", + "blockCapacity": 2097152, + "attributes": ["CVS", "HDP", "HTI"], + "status": "NML", +} + +GET_LDEV_RESULT_REP = { + "emulationType": "OPEN-V-CVS", + "blockCapacity": 2097152, + "attributes": ["CVS", "HDP", "GAD"], + "status": "NML", + "numOfPorts": 1, +} + +GET_POOL_RESULT = { + "availableVolumeCapacity": 480144, + "totalPoolCapacity": 507780, + "totalLocatedCapacity": 71453172, + "virtualVolumeCapacityRate": -1, +} + +GET_POOLS_RESULT = { + "data": [ + { + "poolId": 30, + "poolName": CONFIG_MAP['pool_name'], + "availableVolumeCapacity": 480144, + "totalPoolCapacity": 507780, + "totalLocatedCapacity": 71453172, + "virtualVolumeCapacityRate": -1, + }, + ], +} + +GET_SNAPSHOTS_RESULT = { + "data": [ + { + "primaryOrSecondary": "S-VOL", + "status": "PSUS", + "pvolLdevId": 0, + "muNumber": 1, + "svolLdevId": 1, + }, + ], +} + +GET_SNAPSHOTS_RESULT_PAIR = { + "data": [ + { + "primaryOrSecondary": "S-VOL", + "status": "PAIR", + "pvolLdevId": 0, + "muNumber": 1, + "svolLdevId": 1, + }, + ], +} + +GET_SNAPSHOTS_RESULT_BUSY = { + "data": [ + { + "primaryOrSecondary": "P-VOL", + "status": "PSUP", + "pvolLdevId": 0, + "muNumber": 1, + "svolLdevId": 1, + }, + ], +} + +GET_LUNS_RESULT = { + "data": [ + { + "ldevId": 0, + "lun": 1, + }, + ], +} + +GET_HOST_GROUP_RESULT = { + "hostGroupName": CONFIG_MAP['host_grp_name'], +} + +GET_HOST_GROUPS_RESULT = { + "data": [ + { + "hostGroupNumber": 0, + "portId": CONFIG_MAP['port_id'], + "hostGroupName": "HBSD-test", + }, + ], +} + +GET_HOST_GROUPS_RESULT_PAIR = { + "data": [ + { + "hostGroupNumber": 1, + "portId": CONFIG_MAP['port_id'], + "hostGroupName": "HBSD-pair00", + }, + ], +} + +REMOTE_GET_HOST_GROUPS_RESULT_PAIR = { + "data": [ + { + "hostGroupNumber": 1, + "portId": REMOTE_CONFIG_MAP['port_id'], + "hostGroupName": "HBSD-pair00", + }, + ], +} + +GET_LDEVS_RESULT = { + "data": [ + { + "ldevId": 0, + "label": "15960cc738c94c5bb4f1365be5eeed44", + }, + { + "ldevId": 1, + "label": "15960cc738c94c5bb4f1365be5eeed45", + }, + ], +} + +GET_REMOTE_MIRROR_COPYPAIR_RESULT = { + 'pvolLdevId': 4, + 'svolLdevId': 4, + 'pvolStatus': 'PAIR', + 'svolStatus': 'PAIR', + 'replicationType': hbsd_utils.MIRROR_ATTR, +} + +GET_REMOTE_MIRROR_COPYPAIR_RESULT_SPLIT = { + 'pvolLdevId': 4, + 'svolLdevId': 4, + 'pvolStatus': 'PSUS', + 'svolStatus': 'SSUS', + 'replicationType': hbsd_utils.MIRROR_ATTR, +} + +GET_REMOTE_MIRROR_COPYGROUP_RESULT = { + 'copyGroupName': 'HBSD-127.0.0.100U00', + 'copyPairs': [GET_REMOTE_MIRROR_COPYPAIR_RESULT], +} + +GET_REMOTE_MIRROR_COPYGROUP_RESULT_ERROR = { + "errorSource": "<URL>", + "message": "<message>", + "solution": "<solution>", + "messageId": "aaa", + "errorCode": { + "SSB1": "", + "SSB2": "", + } +} + +NOTFOUND_RESULT = { + "data": [], +} + +ERROR_RESULT = { + "errorSource": "<URL>", + "message": "<message>", + "solution": "<solution>", + "messageId": "<messageId>", + "errorCode": { + "SSB1": "", + "SSB2": "", + } +} + + +def _brick_get_connector_properties(multipath=False, enforce_multipath=False): + """Return a predefined connector object.""" + return DEFAULT_CONNECTOR + + +class FakeLookupService(): + """Dummy FC zoning mapping lookup service class.""" + + def get_device_mapping_from_network(self, initiator_wwns, target_wwns): + """Return predefined FC zoning mapping.""" + return DEVICE_MAP + + +class FakeResponse(): + + def __init__(self, status_code, data=None, headers=None): + self.status_code = status_code + self.data = data + self.text = data + self.content = data + self.headers = {'Content-Type': 'json'} if headers is None else headers + + def json(self): + return self.data + + +class HBSDMIRRORFCDriverTest(test.TestCase): + """Unit test class for HBSD MIRROR interface fibre channel module.""" + + test_existing_ref = {'source-id': '1'} + test_existing_ref_name = { + 'source-name': '15960cc7-38c9-4c5b-b4f1-365be5eeed45'} + + def setUp(self): + """Set up the test environment.""" + def _set_required(opts, required): + for opt in opts: + opt.required = required + + # Initialize Cinder and avoid checking driver options. + rest_required_opts = [ + opt for opt in hbsd_rest.REST_VOLUME_OPTS if opt.required] + common_required_opts = [ + opt for opt in hbsd_common.COMMON_VOLUME_OPTS if opt.required] + _set_required(rest_required_opts, False) + _set_required(common_required_opts, False) + super(HBSDMIRRORFCDriverTest, self).setUp() + _set_required(rest_required_opts, True) + _set_required(common_required_opts, True) + + self.configuration = conf.Configuration(None) + self.ctxt = cinder_context.get_admin_context() + self._setup_config() + self._setup_driver() + + def _setup_config(self): + """Set configuration parameter values.""" + self.configuration.config_group = "REST" + + self.configuration.volume_backend_name = "RESTFC" + self.configuration.volume_driver = ( + "cinder.volume.drivers.hitachi.hbsd_fc.HBSDFCDriver") + self.configuration.reserved_percentage = "0" + self.configuration.use_multipath_for_image_xfer = False + self.configuration.enforce_multipath_for_image_xfer = False + self.configuration.max_over_subscription_ratio = 500.0 + self.configuration.driver_ssl_cert_verify = False + + self.configuration.hitachi_storage_id = CONFIG_MAP['serial'] + self.configuration.hitachi_pools = ["30"] + self.configuration.hitachi_snap_pool = None + self.configuration.hitachi_ldev_range = "0-1" + self.configuration.hitachi_target_ports = [CONFIG_MAP['port_id']] + self.configuration.hitachi_compute_target_ports\ + = [CONFIG_MAP['port_id']] + self.configuration.hitachi_group_create = True + self.configuration.hitachi_group_delete = True + self.configuration.hitachi_copy_speed = 3 + self.configuration.hitachi_copy_check_interval = 3 + self.configuration.hitachi_async_copy_check_interval = 10 + + self.configuration.san_login = CONFIG_MAP['user_id'] + self.configuration.san_password = CONFIG_MAP['user_pass'] + self.configuration.san_ip = CONFIG_MAP[ + 'rest_server_ip_addr'] + self.configuration.san_api_port = CONFIG_MAP[ + 'rest_server_ip_port'] + 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_lun_timeout = hbsd_rest._LUN_TIMEOUT + self.configuration.hitachi_lun_retry_interval = ( + hbsd_rest._LUN_RETRY_INTERVAL) + self.configuration.hitachi_restore_timeout = hbsd_rest._RESTORE_TIMEOUT + self.configuration.hitachi_state_transition_timeout = ( + hbsd_rest._STATE_TRANSITION_TIMEOUT) + self.configuration.hitachi_lock_timeout = hbsd_rest_api._LOCK_TIMEOUT + self.configuration.hitachi_rest_timeout = hbsd_rest_api._REST_TIMEOUT + self.configuration.hitachi_extend_timeout = ( + hbsd_rest_api._EXTEND_TIMEOUT) + self.configuration.hitachi_exec_retry_interval = ( + hbsd_rest_api._EXEC_RETRY_INTERVAL) + self.configuration.hitachi_rest_connect_timeout = ( + hbsd_rest_api._DEFAULT_CONNECT_TIMEOUT) + self.configuration.hitachi_rest_job_api_response_timeout = ( + hbsd_rest_api._JOB_API_RESPONSE_TIMEOUT) + self.configuration.hitachi_rest_get_api_response_timeout = ( + hbsd_rest_api._GET_API_RESPONSE_TIMEOUT) + self.configuration.hitachi_rest_server_busy_timeout = ( + hbsd_rest_api._REST_SERVER_BUSY_TIMEOUT) + self.configuration.hitachi_rest_keep_session_loop_interval = ( + hbsd_rest_api._KEEP_SESSION_LOOP_INTERVAL) + self.configuration.hitachi_rest_another_ldev_mapped_retry_timeout = ( + hbsd_rest_api._ANOTHER_LDEV_MAPPED_RETRY_TIMEOUT) + self.configuration.hitachi_rest_tcp_keepidle = ( + hbsd_rest_api._TCP_KEEPIDLE) + self.configuration.hitachi_rest_tcp_keepintvl = ( + hbsd_rest_api._TCP_KEEPINTVL) + self.configuration.hitachi_rest_tcp_keepcnt = ( + hbsd_rest_api._TCP_KEEPCNT) + self.configuration.hitachi_host_mode_options = [] + + self.configuration.hitachi_zoning_request = False + + self.configuration.use_chap_auth = True + self.configuration.chap_username = CONFIG_MAP['auth_user'] + self.configuration.chap_password = CONFIG_MAP['auth_password'] + + self.configuration.san_thin_provision = True + self.configuration.san_private_key = '' + self.configuration.san_clustername = '' + self.configuration.san_ssh_port = '22' + self.configuration.san_is_local = False + self.configuration.ssh_conn_timeout = '30' + self.configuration.ssh_min_pool_conn = '1' + self.configuration.ssh_max_pool_conn = '5' + + self.configuration.hitachi_replication_status_check_short_interval = 5 + self.configuration.hitachi_replication_status_check_long_interval\ + = 10 * 60 + self.configuration.hitachi_replication_status_check_timeout\ + = 24 * 60 * 60 + + self.configuration.hitachi_replication_number = 0 + self.configuration.hitachi_pair_target_number = 0 + self.configuration.hitachi_rest_pair_target_ports\ + = [CONFIG_MAP['port_id']] + self.configuration.hitachi_quorum_disk_id = 13 + self.configuration.hitachi_mirror_copy_speed = 3 + self.configuration.hitachi_mirror_storage_id\ + = REMOTE_CONFIG_MAP['serial'] + self.configuration.hitachi_mirror_pool = '40' + self.configuration.hitachi_mirror_snap_pool = None + self.configuration.hitachi_mirror_ldev_range = '2-3' + self.configuration.hitachi_mirror_target_ports\ + = [REMOTE_CONFIG_MAP['port_id']] + self.configuration.hitachi_mirror_compute_target_ports\ + = [REMOTE_CONFIG_MAP['port_id']] + self.configuration.hitachi_mirror_pair_target_number = 0 + self.configuration.hitachi_mirror_rest_pair_target_ports\ + = [REMOTE_CONFIG_MAP['port_id']] + self.configuration.hitachi_mirror_rest_user\ + = REMOTE_CONFIG_MAP['user_id'] + self.configuration.hitachi_mirror_rest_password\ + = REMOTE_CONFIG_MAP['user_pass'] + self.configuration.hitachi_mirror_rest_api_ip\ + = REMOTE_CONFIG_MAP['rest_server_ip_addr'] + self.configuration.hitachi_mirror_rest_api_port\ + = REMOTE_CONFIG_MAP['rest_server_ip_port'] + self.configuration.hitachi_set_mirror_reserve_attribute = True + self.configuration.hitachi_path_group_id = 0 + + self.configuration.hitachi_mirror_use_chap_auth = True + self.configuration.hitachi_mirror_chap_user = CONFIG_MAP['auth_user'] + self.configuration.hitachi_mirror_chap_password\ + = CONFIG_MAP['auth_password'] + + self.configuration.hitachi_mirror_ssl_cert_verify = False + self.configuration.hitachi_mirror_ssl_cert_path = '/root/path' + + self.configuration.safe_get = self._fake_safe_get + + CONF = cfg.CONF + CONF.my_ip = CONFIG_MAP['my_ip'] + + def _fake_safe_get(self, value): + """Retrieve a configuration value avoiding throwing an exception.""" + try: + val = getattr(self.configuration, value) + except AttributeError: + val = None + return val + + @mock.patch.object(requests.Session, "request") + @mock.patch.object( + volume_utils, 'brick_get_connector_properties', + side_effect=_brick_get_connector_properties) + def _setup_driver( + self, brick_get_connector_properties=None, request=None): + """Set up the driver environment.""" + self.driver = hbsd_fc.HBSDFCDriver( + configuration=self.configuration) + + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method == 'POST': + return FakeResponse(200, POST_SESSIONS_RESULT) + elif '/ports' in url: + return FakeResponse(200, GET_PORTS_RESULT) + elif '/host-wwns' in url: + return FakeResponse(200, GET_HOST_WWNS_RESULT) + elif '/host-groups' in url: + return FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR) + else: + if method == 'POST': + return FakeResponse(200, REMOTE_POST_SESSIONS_RESULT) + elif '/ports' in url: + return FakeResponse(200, REMOTE_GET_PORTS_RESULT) + elif '/host-wwns' in url: + return FakeResponse(200, REMOTE_GET_HOST_WWNS_RESULT) + elif '/host-groups' in url: + return FakeResponse( + 200, REMOTE_GET_HOST_GROUPS_RESULT_PAIR) + return FakeResponse( + 500, ERROR_RESULT, headers={'Content-Type': 'json'}) + request.side_effect = _request_side_effect + self.driver.do_setup(None) + self.driver.check_for_setup_error() + self.driver.local_path(None) + self.driver.create_export(None, None, None) + self.driver.ensure_export(None, None) + self.driver.remove_export(None, None) + self.driver.create_export_snapshot(None, None, None) + self.driver.remove_export_snapshot(None, None) + # stop the Loopingcall within the do_setup treatment + self.driver.common.rep_primary.client.keep_session_loop.stop() + self.driver.common.rep_secondary.client.keep_session_loop.stop() + + def tearDown(self): + self.client = None + super(HBSDMIRRORFCDriverTest, self).tearDown() + + # API test cases + @mock.patch.object(requests.Session, "request") + @mock.patch.object( + volume_utils, 'brick_get_connector_properties', + side_effect=_brick_get_connector_properties) + def test_do_setup(self, brick_get_connector_properties, request): + drv = hbsd_fc.HBSDFCDriver( + configuration=self.configuration) + self._setup_config() + self.configuration.hitachi_pair_target_number = 10 + self.configuration.hitachi_mirror_pair_target_number = 20 + + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method == 'POST': + return FakeResponse(200, POST_SESSIONS_RESULT) + elif '/ports' in url: + return FakeResponse(200, GET_PORTS_RESULT) + elif '/host-wwns' in url: + return FakeResponse(200, GET_HOST_WWNS_RESULT) + elif '/host-groups' in url: + return FakeResponse(200, GET_HOST_GROUPS_RESULT_PAIR) + else: + if method == 'POST': + return FakeResponse(200, REMOTE_POST_SESSIONS_RESULT) + elif '/ports' in url: + return FakeResponse(200, REMOTE_GET_PORTS_RESULT) + elif '/host-wwns' in url: + return FakeResponse(200, REMOTE_GET_HOST_WWNS_RESULT) + elif '/host-groups' in url: + return FakeResponse( + 200, REMOTE_GET_HOST_GROUPS_RESULT_PAIR) + return FakeResponse( + 500, ERROR_RESULT, headers={'Content-Type': 'json'}) + request.side_effect = _request_side_effect + drv.do_setup(None) + self.assertEqual( + {CONFIG_MAP['port_id']: CONFIG_MAP['target_wwn']}, + drv.common.rep_primary.storage_info['wwns']) + self.assertEqual( + {REMOTE_CONFIG_MAP['port_id']: REMOTE_CONFIG_MAP['target_wwn']}, + drv.common.rep_secondary.storage_info['wwns']) + self.assertEqual(2, brick_get_connector_properties.call_count) + self.assertEqual(10, request.call_count) + self.assertEqual( + "HBSD-pair%2d" % self.configuration.hitachi_pair_target_number, + drv.common.rep_primary._PAIR_TARGET_NAME) + self.assertEqual( + ("HBSD-pair%2d" % + self.configuration.hitachi_mirror_pair_target_number), + drv.common.rep_secondary._PAIR_TARGET_NAME) + # stop the Loopingcall within the do_setup treatment + self.driver.common.rep_primary.client.keep_session_loop.stop() + self.driver.common.rep_primary.client.keep_session_loop.wait() + self.driver.common.rep_secondary.client.keep_session_loop.stop() + self.driver.common.rep_secondary.client.keep_session_loop.wait() + self._setup_config() + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_create_volume(self, get_volume_type_extra_specs, request): + extra_specs = {"test1": "aaa"} + get_volume_type_extra_specs.return_value = extra_specs + request.return_value = FakeResponse(202, COMPLETED_SUCCEEDED_RESULT) + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_volume(fake_volume.fake_volume_obj(self.ctxt)) + actual = {'provider_location': '1'} + self.assertEqual(actual, ret) + self.assertEqual(2, request.call_count) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_create_volume_replication( + self, get_volume_type_extra_specs, get_volume_type, request): + extra_specs = {"test1": "aaa", + "hbsd:topology": "active_active_mirror_volume"} + get_volume_type_extra_specs.return_value = extra_specs + get_volume_type.return_value = {} + + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method in ('POST', 'PUT'): + return FakeResponse(202, COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/remote-mirror-copygroups' in url: + return FakeResponse(200, NOTFOUND_RESULT) + elif '/remote-mirror-copypairs/' in url: + return FakeResponse( + 200, GET_REMOTE_MIRROR_COPYPAIR_RESULT) + else: + if method in ('POST', 'PUT'): + return FakeResponse(202, REMOTE_COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/remote-mirror-copygroups' in url: + return FakeResponse(200, NOTFOUND_RESULT) + return FakeResponse( + 500, ERROR_RESULT, headers={'Content-Type': 'json'}) + request.side_effect = _request_side_effect + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_volume(TEST_VOLUME[3]) + actual = { + 'provider_location': json.dumps( + {'pldev': 1, 'sldev': 2, + 'remote-copy': hbsd_utils.MIRROR_ATTR})} + self.assertEqual(actual, ret) + self.assertEqual(14, request.call_count) + + @mock.patch.object(requests.Session, "request") + def test_delete_volume(self, request): + request.side_effect = [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_volume(TEST_VOLUME[0]) + self.assertEqual(5, request.call_count) + + @mock.patch.object(requests.Session, "request") + def test_delete_volume_replication(self, request): + self.copygroup_count = 0 + self.ldev_count = 0 + + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method in ('POST', 'PUT', 'DELETE'): + return FakeResponse(202, COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/remote-mirror-copygroups/' in url: + if self.copygroup_count < 2: + self.copygroup_count = self.copygroup_count + 1 + return FakeResponse( + 200, GET_REMOTE_MIRROR_COPYGROUP_RESULT) + else: + return FakeResponse( + 500, GET_REMOTE_MIRROR_COPYGROUP_RESULT_ERROR, + headers={'Content-Type': 'json'}) + elif '/remote-mirror-copypairs/' in url: + return FakeResponse( + 200, GET_REMOTE_MIRROR_COPYPAIR_RESULT_SPLIT) + elif '/ldevs/' in url: + if self.ldev_count == 0: + self.ldev_count = self.ldev_count + 1 + return FakeResponse(200, GET_LDEV_RESULT_REP) + else: + return FakeResponse(200, GET_LDEV_RESULT) + else: + if method in ('POST', 'PUT', 'DELETE'): + return FakeResponse(202, REMOTE_COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/ldevs/' in url: + return FakeResponse(200, GET_LDEV_RESULT) + return FakeResponse( + 500, ERROR_RESULT, headers={'Content-Type': 'json'}) + request.side_effect = _request_side_effect + self.driver.delete_volume(TEST_VOLUME[4]) + self.assertEqual(17, request.call_count) + + @mock.patch.object(requests.Session, "request") + def test_extend_volume(self, request): + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + self.driver.extend_volume(TEST_VOLUME[0], 256) + self.assertEqual(4, request.call_count) + + @mock.patch.object(requests.Session, "request") + def test_extend_volume_replication(self, request): + self.ldev_count = 0 + self.copypair_count = 0 + + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method in ('POST', 'PUT', 'DELETE'): + return FakeResponse(202, COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/remote-mirror-copygroups/' in url: + return FakeResponse( + 200, GET_REMOTE_MIRROR_COPYGROUP_RESULT) + elif '/remote-mirror-copygroups' in url: + return FakeResponse(200, NOTFOUND_RESULT) + elif '/remote-mirror-copypairs/' in url: + if self.copypair_count == 0: + self.copypair_count = self.copypair_count + 1 + return FakeResponse( + 200, GET_REMOTE_MIRROR_COPYPAIR_RESULT_SPLIT) + else: + return FakeResponse( + 200, GET_REMOTE_MIRROR_COPYPAIR_RESULT) + elif '/ldevs/' in url: + if self.ldev_count < 2: + self.ldev_count = self.ldev_count + 1 + return FakeResponse(200, GET_LDEV_RESULT_REP) + else: + return FakeResponse(200, GET_LDEV_RESULT) + else: + if method in ('POST', 'PUT', 'DELETE'): + return FakeResponse(202, REMOTE_COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/ldevs/' in url: + return FakeResponse(200, GET_LDEV_RESULT) + return FakeResponse( + 500, ERROR_RESULT, headers={'Content-Type': 'json'}) + request.side_effect = _request_side_effect + self.driver.extend_volume(TEST_VOLUME[4], 256) + self.assertEqual(23, request.call_count) + + @mock.patch.object(driver.FibreChannelDriver, "get_goodness_function") + @mock.patch.object(driver.FibreChannelDriver, "get_filter_function") + @mock.patch.object(requests.Session, "request") + def test_get_volume_stats( + self, request, get_filter_function, get_goodness_function): + request.return_value = FakeResponse(200, GET_POOLS_RESULT) + get_filter_function.return_value = None + get_goodness_function.return_value = None + stats = self.driver.get_volume_stats(True) + self.assertEqual('Hitachi', stats['vendor_name']) + self.assertTrue(stats["pools"][0]['multiattach']) + self.assertEqual(1, request.call_count) + self.assertEqual(1, get_filter_function.call_count) + self.assertEqual(1, get_goodness_function.call_count) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + @mock.patch.object(sqlalchemy_api, 'volume_get', side_effect=_volume_get) + def test_create_snapshot( + self, volume_get, get_volume_type_extra_specs, request): + extra_specs = {"test1": "aaa", + "hbsd:topology": "active_active_mirror_volume"} + get_volume_type_extra_specs.return_value = extra_specs + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT)] + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_snapshot(TEST_SNAPSHOT[0]) + actual = {'provider_location': '1'} + self.assertEqual(actual, ret) + self.assertEqual(4, request.call_count) + + @mock.patch.object(requests.Session, "request") + def test_delete_snapshot(self, request): + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR), + FakeResponse(200, NOTFOUND_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_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(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + self.driver.delete_snapshot(TEST_SNAPSHOT[0]) + self.assertEqual(14, request.call_count) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_create_cloned_volume( + self, get_volume_type_extra_specs, get_volume_type, request): + extra_specs = {"test1": "aaa"} + get_volume_type_extra_specs.return_value = extra_specs + get_volume_type.return_value = {} + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_cloned_volume(TEST_VOLUME[0], TEST_VOLUME[1]) + actual = {'provider_location': '1'} + self.assertEqual(actual, ret) + self.assertEqual(5, request.call_count) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_create_cloned_volume_replication( + self, get_volume_type_extra_specs, get_volume_type, request): + extra_specs = {"test1": "aaa", + "hbsd:topology": "active_active_mirror_volume"} + get_volume_type_extra_specs.return_value = extra_specs + get_volume_type.return_value = {} + self.snapshot_count = 0 + + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method in ('POST', 'PUT'): + return FakeResponse(202, COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/remote-mirror-copygroups' in url: + return FakeResponse(200, NOTFOUND_RESULT) + elif '/remote-mirror-copypairs/' in url: + return FakeResponse( + 200, GET_REMOTE_MIRROR_COPYPAIR_RESULT) + elif '/ldevs/' in url: + return FakeResponse(200, GET_LDEV_RESULT_REP) + elif '/snapshots' in url: + if self.snapshot_count < 1: + self.snapshot_count = self.snapshot_count + 1 + return FakeResponse(200, GET_SNAPSHOTS_RESULT) + else: + return FakeResponse(200, NOTFOUND_RESULT) + else: + if method in ('POST', 'PUT'): + return FakeResponse(202, REMOTE_COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/remote-mirror-copygroups' in url: + return FakeResponse(200, NOTFOUND_RESULT) + return FakeResponse( + 500, ERROR_RESULT, headers={'Content-Type': 'json'}) + request.side_effect = _request_side_effect + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_cloned_volume(TEST_VOLUME[4], TEST_VOLUME[5]) + actual = { + 'provider_location': json.dumps( + {'pldev': 1, 'sldev': 2, + 'remote-copy': hbsd_utils.MIRROR_ATTR})} + self.assertEqual(actual, ret) + self.assertEqual(23, request.call_count) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_create_volume_from_snapshot( + self, get_volume_type_extra_specs, get_volume_type, request): + extra_specs = {"test1": "aaa"} + get_volume_type_extra_specs.return_value = extra_specs + get_volume_type.return_value = {} + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_volume_from_snapshot( + TEST_VOLUME[0], TEST_SNAPSHOT[0]) + actual = {'provider_location': '1'} + self.assertEqual(actual, ret) + self.assertEqual(5, request.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_initialize_connection( + self, get_volume_type_extra_specs, request, add_fc_zone): + self.driver.common.conf.hitachi_zoning_request = True + self.driver.common.rep_primary.lookup_service = FakeLookupService() + self.driver.common.rep_secondary.lookup_service = FakeLookupService() + extra_specs = {"test1": "aaa"} + get_volume_type_extra_specs.return_value = extra_specs + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method in ('POST', 'PUT'): + return FakeResponse(202, COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + return FakeResponse(200, GET_HOST_WWNS_RESULT) + else: + if method in ('POST', 'PUT'): + return FakeResponse(202, REMOTE_COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + return FakeResponse(200, REMOTE_GET_HOST_WWNS_RESULT) + return FakeResponse( + 500, ERROR_RESULT, headers={'Content-Type': 'json'}) + request.side_effect = _request_side_effect + ret = self.driver.initialize_connection( + TEST_VOLUME[4], DEFAULT_CONNECTOR) + self.assertEqual('fibre_channel', ret['driver_volume_type']) + self.assertEqual( + [CONFIG_MAP['target_wwn'], REMOTE_CONFIG_MAP['target_wwn']], + ret['data']['target_wwn']) + self.assertEqual(1, ret['data']['target_lun']) + self.assertEqual(4, request.call_count) + self.assertEqual(1, 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): + self.driver.common.conf.hitachi_zoning_request = True + self.driver.common.rep_primary.lookup_service = FakeLookupService() + self.driver.common.rep_secondary.lookup_service = FakeLookupService() + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method in ('POST', 'PUT', 'DELETE'): + return FakeResponse(202, COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/ldevs/' in url: + return FakeResponse(200, GET_LDEV_RESULT_MAPPED) + elif '/host-wwns' in url: + return FakeResponse(200, GET_HOST_WWNS_RESULT) + else: + return FakeResponse(200, NOTFOUND_RESULT) + else: + if method in ('POST', 'PUT', 'DELETE'): + return FakeResponse(202, REMOTE_COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/ldevs/' in url: + return FakeResponse(200, REMOTE_GET_LDEV_RESULT_MAPPED) + elif '/host-wwns' in url: + return FakeResponse(200, REMOTE_GET_HOST_WWNS_RESULT) + else: + return FakeResponse(200, NOTFOUND_RESULT) + return FakeResponse( + 500, ERROR_RESULT, headers={'Content-Type': 'json'}) + request.side_effect = _request_side_effect + self.driver.terminate_connection(TEST_VOLUME[6], DEFAULT_CONNECTOR) + self.assertEqual(10, request.call_count) + self.assertEqual(1, remove_fc_zone.call_count) + + @mock.patch.object(fczm_utils, "add_fc_zone") + @mock.patch.object(requests.Session, "request") + def test_initialize_connection_snapshot(self, request, add_fc_zone): + self.driver.common.rep_primary.conf.hitachi_zoning_request = True + self.driver.common.lookup_service = FakeLookupService() + request.side_effect = [FakeResponse(200, GET_HOST_WWNS_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + ret = self.driver.initialize_connection_snapshot( + TEST_SNAPSHOT[0], DEFAULT_CONNECTOR) + self.assertEqual('fibre_channel', ret['driver_volume_type']) + self.assertEqual([CONFIG_MAP['target_wwn']], ret['data']['target_wwn']) + self.assertEqual(1, ret['data']['target_lun']) + self.assertEqual(2, request.call_count) + self.assertEqual(1, add_fc_zone.call_count) + + @mock.patch.object(fczm_utils, "remove_fc_zone") + @mock.patch.object(requests.Session, "request") + def test_terminate_connection_snapshot(self, request, remove_fc_zone): + self.driver.common.rep_primary.conf.hitachi_zoning_request = True + self.driver.common.lookup_service = FakeLookupService() + request.side_effect = [FakeResponse(200, GET_HOST_WWNS_RESULT), + FakeResponse(200, GET_LDEV_RESULT_MAPPED), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(200, NOTFOUND_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + self.driver.terminate_connection_snapshot( + TEST_SNAPSHOT[0], DEFAULT_CONNECTOR) + self.assertEqual(5, request.call_count) + self.assertEqual(1, remove_fc_zone.call_count) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type') + def test_manage_existing(self, get_volume_type, request): + get_volume_type.return_value = {} + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + ret = self.driver.manage_existing( + TEST_VOLUME[0], self.test_existing_ref) + actual = {'provider_location': '1'} + self.assertEqual(actual, ret) + self.assertEqual(2, request.call_count) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_manage_existing_get_size( + self, get_volume_type_extra_specs, request): + extra_specs = {"test1": "aaa"} + get_volume_type_extra_specs.return_value = extra_specs + request.return_value = FakeResponse(200, GET_LDEV_RESULT) + self.driver.manage_existing_get_size( + TEST_VOLUME[0], self.test_existing_ref) + self.assertEqual(1, request.call_count) + + @mock.patch.object(requests.Session, "request") + def test_unmanage(self, request): + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, GET_LDEV_RESULT)] + self.driver.unmanage(TEST_VOLUME[0]) + self.assertEqual(3, request.call_count) + + @mock.patch.object(requests.Session, "request") + def test_copy_image_to_volume(self, request): + image_service = 'fake_image_service' + image_id = 'fake_image_id' + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, COMPLETED_SUCCEEDED_RESULT)] + with mock.patch.object(driver.VolumeDriver, 'copy_image_to_volume') \ + as mock_copy_image: + self.driver.copy_image_to_volume( + self.ctxt, TEST_VOLUME[0], image_service, image_id) + mock_copy_image.assert_called_with( + self.ctxt, TEST_VOLUME[0], image_service, image_id) + self.assertEqual(2, request.call_count) + + @mock.patch.object(requests.Session, "request") + def test_update_migrated_volume(self, request): + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, COMPLETED_SUCCEEDED_RESULT)] + self.assertRaises( + NotImplementedError, + self.driver.update_migrated_volume, + self.ctxt, + TEST_VOLUME[0], + TEST_VOLUME[1], + "available") + self.assertEqual(2, request.call_count) + + def test_unmanage_snapshot(self): + """The driver don't support unmange_snapshot.""" + self.assertRaises( + NotImplementedError, + self.driver.unmanage_snapshot, + TEST_SNAPSHOT[0]) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + @mock.patch.object(obj_snap.SnapshotList, 'get_all_for_volume') + def test_retype(self, get_all_for_volume, + get_volume_type_extra_specs, request): + extra_specs = {'test1': 'aaa', + 'hbsd:target_ports': 'CL2-A'} + get_volume_type_extra_specs.return_value = extra_specs + get_all_for_volume.return_value = True + + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, GET_LDEV_RESULT)] + + old_specs = {'hbsd:target_ports': 'CL1-A'} + new_specs = {'hbsd:target_ports': 'CL2-A'} + old_type_ref = volume_types.create(self.ctxt, 'old', old_specs) + new_type_ref = volume_types.create(self.ctxt, 'new', new_specs) + new_type = volume_types.get_volume_type(self.ctxt, new_type_ref['id']) + + diff = volume_types.volume_types_diff(self.ctxt, old_type_ref['id'], + new_type_ref['id'])[0] + host = { + 'capabilities': { + 'location_info': { + 'pool_id': 30, + }, + }, + } + + ret = self.driver.retype( + self.ctxt, TEST_VOLUME[0], new_type, diff, host) + self.assertEqual(2, request.call_count) + self.assertFalse(ret) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_retype_replication(self, get_volume_type_extra_specs, request): + extra_specs = {'test1': 'aaa', + 'hbsd:topology': 'active_active_mirror_volume'} + get_volume_type_extra_specs.return_value = extra_specs + + request.return_value = FakeResponse(200, GET_LDEV_RESULT) + + new_type_ref = volume_types.create(self.ctxt, 'new', extra_specs) + new_type = volume_types.get_volume_type(self.ctxt, new_type_ref['id']) + diff = {} + host = { + 'capabilities': { + 'location_info': { + 'pool_id': 30, + }, + }, + } + ret = self.driver.retype( + self.ctxt, TEST_VOLUME[0], new_type, diff, host) + self.assertEqual(1, request.call_count) + self.assertFalse(ret) + + @mock.patch.object(requests.Session, "request") + def test_migrate_volume( + self, request): + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(200, GET_LDEV_RESULT)] + host = { + 'capabilities': { + 'location_info': { + 'storage_id': CONFIG_MAP['serial'], + 'pool_id': 30, + }, + }, + } + ret = self.driver.migrate_volume(self.ctxt, TEST_VOLUME[0], host) + self.assertEqual(3, request.call_count) + actual = (True, None) + self.assertTupleEqual(actual, ret) + + @mock.patch.object(requests.Session, "request") + def test_revert_to_snapshot(self, request): + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR), + FakeResponse(200, GET_LDEV_RESULT_PAIR), + FakeResponse(200, GET_LDEV_RESULT_PAIR), + FakeResponse(200, GET_LDEV_RESULT_PAIR), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT)] + self.driver.revert_to_snapshot( + self.ctxt, TEST_VOLUME[0], TEST_SNAPSHOT[0]) + self.assertEqual(8, request.call_count) + + def test_create_group(self): + ret = self.driver.create_group(self.ctxt, TEST_GROUP[0]) + self.assertIsNone(ret) + + @mock.patch.object(requests.Session, "request") + def test_delete_group(self, request): + request.side_effect = [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( + self.ctxt, TEST_GROUP[0], [TEST_VOLUME[0]]) + self.assertEqual(5, request.call_count) + actual = ( + {'status': TEST_GROUP[0]['status']}, + [{'id': TEST_VOLUME[0]['id'], 'status': 'deleted'}] + ) + self.assertTupleEqual(actual, ret) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_create_group_from_src_volume( + self, get_volume_type_extra_specs, get_volume_type, request): + extra_specs = {"test1": "aaa"} + get_volume_type_extra_specs.return_value = extra_specs + get_volume_type.return_value = {} + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_group_from_src( + self.ctxt, TEST_GROUP[1], [TEST_VOLUME[1]], + source_group=TEST_GROUP[0], source_vols=[TEST_VOLUME[0]] + ) + self.assertEqual(5, request.call_count) + actual = ( + None, + [{'id': TEST_VOLUME[1]['id'], + 'provider_location': '1'}]) + self.assertTupleEqual(actual, ret) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_create_group_from_src_snapshot( + self, get_volume_type_extra_specs, get_volume_type, request): + extra_specs = {"test1": "aaa"} + get_volume_type_extra_specs.return_value = extra_specs + get_volume_type.return_value = {} + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT)] + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_group_from_src( + self.ctxt, TEST_GROUP[0], [TEST_VOLUME[0]], + group_snapshot=TEST_GROUP_SNAP[0], snapshots=[TEST_SNAPSHOT[0]] + ) + self.assertEqual(5, request.call_count) + actual = ( + None, + [{'id': TEST_VOLUME[0]['id'], + 'provider_location': '1'}]) + self.assertTupleEqual(actual, ret) + + @mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type') + def test_update_group(self, is_group_a_cg_snapshot_type): + is_group_a_cg_snapshot_type.return_value = False + ret = self.driver.update_group( + self.ctxt, TEST_GROUP[0], add_volumes=[TEST_VOLUME[0]]) + self.assertTupleEqual((None, None, None), ret) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + @mock.patch.object(sqlalchemy_api, 'volume_get', side_effect=_volume_get) + @mock.patch.object(volume_utils, 'is_group_a_cg_snapshot_type') + def test_create_group_snapshot_non_cg( + self, is_group_a_cg_snapshot_type, volume_get, + get_volume_type_extra_specs, request): + is_group_a_cg_snapshot_type.return_value = False + extra_specs = {"test1": "aaa"} + get_volume_type_extra_specs.return_value = extra_specs + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT)] + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + ret = self.driver.create_group_snapshot( + self.ctxt, TEST_GROUP_SNAP[0], [TEST_SNAPSHOT[0]] + ) + self.assertEqual(4, request.call_count) + actual = ( + {'status': 'available'}, + [{'id': TEST_SNAPSHOT[0]['id'], + 'provider_location': '1', + 'status': 'available'}] + ) + self.assertTupleEqual(actual, ret) + + @mock.patch.object(requests.Session, "request") + def test_delete_group_snapshot(self, request): + request.side_effect = [FakeResponse(200, GET_LDEV_RESULT_PAIR), + FakeResponse(200, NOTFOUND_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(200, GET_SNAPSHOTS_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_RESULT), + FakeResponse(202, COMPLETED_SUCCEEDED_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(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(14, request.call_count) + actual = ( + {'status': TEST_GROUP_SNAP[0]['status']}, + [{'id': TEST_SNAPSHOT[0]['id'], 'status': 'deleted'}] + ) + self.assertTupleEqual(actual, ret) + + @mock.patch.object(requests.Session, "request") + @mock.patch.object(volume_types, 'get_volume_type') + @mock.patch.object(volume_types, 'get_volume_type_extra_specs') + def test_create_rep_ldev_and_pair_deduplication_compression( + self, get_volume_type_extra_specs, get_volume_type, request): + get_volume_type_extra_specs.return_value = { + 'hbsd:topology': 'active_active_mirror_volume', + 'hbsd:capacity_saving': 'deduplication_compression'} + get_volume_type.return_value = {} + self.snapshot_count = 0 + + def _request_side_effect( + method, url, params, json, headers, auth, timeout, verify): + if self.configuration.hitachi_storage_id in url: + if method in ('POST', 'PUT'): + return FakeResponse(202, COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if ('/remote-mirror-copygroups' in url or + '/journals' in url): + return FakeResponse(200, NOTFOUND_RESULT) + elif '/remote-mirror-copypairs/' in url: + return FakeResponse( + 200, GET_REMOTE_MIRROR_COPYPAIR_RESULT) + elif '/ldevs/' in url: + return FakeResponse(200, GET_LDEV_RESULT_REP) + elif '/snapshots' in url: + if self.snapshot_count < 1: + self.snapshot_count = self.snapshot_count + 1 + return FakeResponse(200, GET_SNAPSHOTS_RESULT) + else: + return FakeResponse(200, NOTFOUND_RESULT) + else: + if method in ('POST', 'PUT'): + return FakeResponse(400, REMOTE_COMPLETED_SUCCEEDED_RESULT) + elif method == 'GET': + if '/remote-mirror-copygroups' in url: + return FakeResponse(200, NOTFOUND_RESULT) + elif '/ldevs/' in url: + return FakeResponse(200, GET_LDEV_RESULT_REP) + if '/ldevs/' in url: + return FakeResponse(200, GET_LDEV_RESULT_REP) + else: + return FakeResponse( + 200, COMPLETED_SUCCEEDED_RESULT) + self.driver.common.rep_primary._stats = {} + self.driver.common.rep_primary._stats['pools'] = [ + {'location_info': {'pool_id': 30}}] + self.driver.common.rep_secondary._stats = {} + self.driver.common.rep_secondary._stats['pools'] = [ + {'location_info': {'pool_id': 40}}] + request.side_effect = _request_side_effect + self.assertRaises(exception.VolumeDriverException, + self.driver.create_cloned_volume, + TEST_VOLUME[4], + TEST_VOLUME[5]) + self.assertEqual(2, get_volume_type_extra_specs.call_count) + self.assertEqual(0, get_volume_type.call_count) + self.assertEqual(14, request.call_count) |