# Copyright (c) 2014 X-IO Technologies. # All Rights Reserved. # # 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. import mock from six.moves import http_client from cinder import context from cinder import exception from cinder import test from cinder.tests.unit import utils from cinder.volume.drivers import xio from cinder.volume import qos_specs from cinder.volume import volume_types ISE_IP1 = '10.12.12.1' ISE_IP2 = '10.11.12.2' ISE_ISCSI_IP1 = '1.2.3.4' ISE_ISCSI_IP2 = '1.2.3.5' ISE_GID = 'isegid' ISE_IQN = ISE_GID ISE_WWN1 = ISE_GID + '1' ISE_WWN2 = ISE_GID + '2' ISE_WWN3 = ISE_GID + '3' ISE_WWN4 = ISE_GID + '4' ISE_TARGETS = [ISE_WWN1, ISE_WWN2, ISE_WWN3, ISE_WWN4] ISE_INIT_TARGET_MAP = {'init_wwn1': ISE_TARGETS, 'init_wwn2': ISE_TARGETS} VOLUME_SIZE = 10 NEW_VOLUME_SIZE = 20 VOLUME1 = {'id': '1', 'name': 'volume1', 'size': VOLUME_SIZE, 'volume_type_id': 'type1'} VOLUME2 = {'id': '2', 'name': 'volume2', 'size': VOLUME_SIZE, 'volume_type_id': 'type2', 'provider_auth': 'CHAP abc abc'} VOLUME3 = {'id': '3', 'name': 'volume3', 'size': VOLUME_SIZE, 'volume_type_id': None} SNAPSHOT1 = {'name': 'snapshot1', 'volume_name': VOLUME1['name'], 'volume_type_id': 'type3'} CLONE1 = {'id': '3', 'name': 'clone1', 'size': VOLUME_SIZE, 'volume_type_id': 'type4'} HOST1 = 'host1' HOST2 = 'host2' ISCSI_CONN1 = {'initiator': 'init_iqn1', 'host': HOST1} ISCSI_CONN2 = {'initiator': 'init_iqn2', 'host': HOST2} FC_CONN1 = {'wwpns': ['init_wwn1', 'init_wwn2'], 'host': HOST1} FC_CONN2 = {'wwpns': ['init_wwn3', 'init_wwn4'], 'host': HOST2} ISE_HTTP_IP = 'http://' + ISE_IP1 ISE_HOST_LOCATION = '/storage/hosts/1' ISE_HOST_LOCATION_URL = ISE_HTTP_IP + ISE_HOST_LOCATION ISE_VOLUME1_LOCATION = '/storage/volumes/volume1' ISE_VOLUME1_LOCATION_URL = ISE_HTTP_IP + ISE_VOLUME1_LOCATION ISE_VOLUME2_LOCATION = '/storage/volumes/volume2' ISE_VOLUME2_LOCATION_URL = ISE_HTTP_IP + ISE_VOLUME2_LOCATION ISE_VOLUME3_LOCATION = '/storage/volumes/volume3' ISE_VOLUME3_LOCATION_URL = ISE_HTTP_IP + ISE_VOLUME3_LOCATION ISE_SNAPSHOT_LOCATION = '/storage/volumes/snapshot1' ISE_SNAPSHOT_LOCATION_URL = ISE_HTTP_IP + ISE_SNAPSHOT_LOCATION ISE_CLONE_LOCATION = '/storage/volumes/clone1' ISE_CLONE_LOCATION_URL = ISE_HTTP_IP + ISE_CLONE_LOCATION ISE_ALLOCATION_LOCATION = '/storage/allocations/a1' ISE_ALLOCATION_LOCATION_URL = ISE_HTTP_IP + ISE_ALLOCATION_LOCATION ISE_GET_QUERY_XML =\ """ ABC12345 %s %s """ % (ISE_IP1, ISE_IP2) ISE_GET_QUERY_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_QUERY_XML.split())} ISE_GET_QUERY_NO_CAP_XML =\ """ ABC12345 %s %s """ % (ISE_IP1, ISE_IP2) ISE_GET_QUERY_NO_CAP_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_QUERY_NO_CAP_XML.split())} ISE_GET_QUERY_NO_CTRL_XML =\ """ ABC12345 """ ISE_GET_QUERY_NO_CTRL_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_QUERY_NO_CTRL_XML.split())} ISE_GET_QUERY_NO_IP_XML =\ """ ABC12345 """ ISE_GET_QUERY_NO_IP_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_QUERY_NO_IP_XML.split())} ISE_GET_QUERY_NO_GID_XML =\ """ %s %s """ % (ISE_IP1, ISE_IP2) ISE_GET_QUERY_NO_GID_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_QUERY_NO_GID_XML.split())} ISE_GET_QUERY_NO_CLONE_XML =\ """ ABC12345 %s %s """ % (ISE_IP1, ISE_IP2) ISE_GET_QUERY_NO_CLONE_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_QUERY_NO_CLONE_XML.split())} ISE_GET_STORAGE_POOLS_XML =\ """ Pool 1 1
None
60 30 45 0 40 0 100 volgid volgid2
""" ISE_GET_STORAGE_POOLS_RESP =\ {'status': http_client.OK, 'location': 'Pool location', 'content': " ".join(ISE_GET_STORAGE_POOLS_XML.split())} ISE_GET_VOL_STATUS_NO_VOL_NODE_XML =\ """""" ISE_GET_VOL_STATUS_NO_VOL_NODE_RESP =\ {'status': http_client.OK, 'location': 'u%s' % ISE_VOLUME1_LOCATION_URL, 'content': " ".join(ISE_GET_VOL_STATUS_NO_VOL_NODE_XML.split())} ISE_GET_VOL_STATUS_NO_STATUS_XML =\ """ """ % (ISE_VOLUME1_LOCATION_URL) ISE_GET_VOL_STATUS_NO_STATUS_RESP =\ {'status': http_client.OK, 'location': 'u%s' % ISE_VOLUME1_LOCATION_URL, 'content': " ".join(ISE_GET_VOL_STATUS_NO_STATUS_XML.split())} ISE_GET_VOL1_STATUS_XML =\ """
Prepared
10
""" % (ISE_VOLUME1_LOCATION_URL) ISE_GET_VOL1_STATUS_RESP =\ {'status': http_client.OK, 'location': 'u%s' % ISE_VOLUME1_LOCATION_URL, 'content': " ".join(ISE_GET_VOL1_STATUS_XML.split())} ISE_GET_VOL2_STATUS_XML =\ """
Prepared
""" % (ISE_VOLUME2_LOCATION_URL) ISE_GET_VOL2_STATUS_RESP =\ {'status': http_client.OK, 'location': 'u%s' % ISE_VOLUME2_LOCATION_URL, 'content': " ".join(ISE_GET_VOL2_STATUS_XML.split())} ISE_GET_VOL3_STATUS_XML =\ """
Prepared
""" % (ISE_VOLUME3_LOCATION_URL) ISE_GET_VOL3_STATUS_RESP =\ {'status': http_client.OK, 'location': 'u%s' % ISE_VOLUME3_LOCATION_URL, 'content': " ".join(ISE_GET_VOL3_STATUS_XML.split())} ISE_GET_SNAP1_STATUS_XML =\ """
Prepared
""" % (ISE_SNAPSHOT_LOCATION_URL) ISE_GET_SNAP1_STATUS_RESP =\ {'status': http_client.OK, 'location': 'u%s' % ISE_SNAPSHOT_LOCATION_URL, 'content': " ".join(ISE_GET_SNAP1_STATUS_XML.split())} ISE_GET_CLONE1_STATUS_XML =\ """
Prepared
""" % (ISE_CLONE_LOCATION_URL) ISE_GET_CLONE1_STATUS_RESP =\ {'status': http_client.OK, 'location': 'u%s' % ISE_CLONE_LOCATION_URL, 'content': " ".join(ISE_GET_CLONE1_STATUS_XML.split())} ISE_CREATE_VOLUME_XML = """""" ISE_CREATE_VOLUME_RESP =\ {'status': http_client.CREATED, 'location': ISE_VOLUME1_LOCATION_URL, 'content': " ".join(ISE_CREATE_VOLUME_XML.split())} ISE_GET_IONETWORKS_XML =\ """ """ ISE_GET_IONETWORKS_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_IONETWORKS_XML.split())} ISE_GET_IONETWORKS_CHAP_XML =\ """ abc abc """ ISE_GET_IONETWORKS_CHAP_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_IONETWORKS_CHAP_XML.split())} ISE_DELETE_VOLUME_XML = """""" ISE_DELETE_VOLUME_RESP =\ {'status': http_client.NO_CONTENT, 'location': '', 'content': " ".join(ISE_DELETE_VOLUME_XML.split())} ISE_GET_ALLOC_WITH_EP_XML =\ """ %s %s 1 """ %\ (ISE_ALLOCATION_LOCATION_URL, VOLUME1['name'], HOST1) ISE_GET_ALLOC_WITH_EP_RESP =\ {'status': http_client.OK, 'location': ISE_ALLOCATION_LOCATION_URL, 'content': " ".join(ISE_GET_ALLOC_WITH_EP_XML.split())} ISE_GET_ALLOC_WITH_NO_ALLOC_XML =\ """""" % ISE_ALLOCATION_LOCATION_URL ISE_GET_ALLOC_WITH_NO_ALLOC_RESP =\ {'status': http_client.OK, 'location': ISE_ALLOCATION_LOCATION_URL, 'content': " ".join(ISE_GET_ALLOC_WITH_NO_ALLOC_XML.split())} ISE_DELETE_ALLOC_XML = """""" ISE_DELETE_ALLOC_RESP =\ {'status': http_client.NO_CONTENT, 'location': '', 'content': " ".join(ISE_DELETE_ALLOC_XML.split())} ISE_GET_HOSTS_NOHOST_XML =\ """""" ISE_GET_HOSTS_NOHOST_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_HOSTS_NOHOST_XML.split())} ISE_GET_HOSTS_HOST1_XML =\ """ "OPENSTACK" %s 1 init_wwn1 init_wwn2 init_iqn1 """ % HOST1 ISE_GET_HOSTS_HOST1_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_HOSTS_HOST1_XML.split())} ISE_GET_HOSTS_HOST1_HOST_TYPE_XML =\ """ "WINDOWS" %s 1 init_wwn1 init_wwn2 init_iqn1 """ % HOST1 ISE_GET_HOSTS_HOST1_HOST_TYPE_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_HOSTS_HOST1_HOST_TYPE_XML.split())} ISE_GET_HOSTS_HOST2_XML =\ """ %s 2 init_wwn3 init_wwn4 init_iqn2 """ % HOST2 ISE_GET_HOSTS_HOST2_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_HOSTS_HOST2_XML.split())} ISE_CREATE_HOST_XML =\ """""" ISE_CREATE_HOST_RESP =\ {'status': http_client.CREATED, 'location': 'http://ip/storage/hosts/host1', 'content': " ".join(ISE_CREATE_HOST_XML.split())} ISE_CREATE_ALLOC_XML =\ """""" ISE_CREATE_ALLOC_RESP =\ {'status': http_client.CREATED, 'location': ISE_ALLOCATION_LOCATION_URL, 'content': " ".join(ISE_CREATE_ALLOC_XML.split())} ISE_GET_ENDPOINTS_XML =\ """ isegid iSCSI ise1 a1 isegid Fibre Channel ise1 a1 """ % (ISE_ALLOCATION_LOCATION_URL, ISE_ALLOCATION_LOCATION_URL) ISE_GET_ENDPOINTS_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_ENDPOINTS_XML.split())} ISE_GET_CONTROLLERS_XML =\ """ %s isegid %s %s %s isegid %s %s """ % (ISE_ISCSI_IP1, ISE_WWN1, ISE_WWN2, ISE_ISCSI_IP2, ISE_WWN3, ISE_WWN4) ISE_GET_CONTROLLERS_RESP =\ {'status': http_client.OK, 'location': '', 'content': " ".join(ISE_GET_CONTROLLERS_XML.split())} ISE_CREATE_SNAPSHOT_XML = """""" ISE_CREATE_SNAPSHOT_RESP =\ {'status': http_client.CREATED, 'location': ISE_SNAPSHOT_LOCATION_URL, 'content': " ".join(ISE_CREATE_SNAPSHOT_XML.split())} ISE_PREP_SNAPSHOT_XML = """""" ISE_PREP_SNAPSHOT_RESP =\ {'status': http_client.ACCEPTED, 'location': ISE_SNAPSHOT_LOCATION_URL, 'content': " ".join(ISE_PREP_SNAPSHOT_XML.split())} ISE_MODIFY_VOLUME_XML = """""" ISE_MODIFY_VOLUME_RESP =\ {'status': http_client.CREATED, 'location': ISE_VOLUME1_LOCATION_URL, 'content': " ".join(ISE_MODIFY_VOLUME_XML.split())} ISE_MODIFY_HOST_XML = """""" ISE_MODIFY_HOST_RESP =\ {'status': http_client.CREATED, 'location': ISE_HOST_LOCATION_URL, 'content': " ".join(ISE_MODIFY_HOST_XML.split())} ISE_BAD_CONNECTION_RESP =\ {'status': 0, 'location': '', 'content': " "} ISE_400_RESP =\ {'status': http_client.BAD_REQUEST, 'location': '', 'content': ""} ISE_GET_VOL_STATUS_404_XML = \ """VOLUME not found.""" ISE_GET_VOL_STATUS_404_RESP =\ {'status': http_client.NOT_FOUND, 'location': '', 'content': " ".join(ISE_GET_VOL_STATUS_404_XML.split())} ISE_400_INVALID_STATE_XML = \ """Not in a valid state.""" ISE_400_INVALID_STATE_RESP =\ {'status': http_client.BAD_REQUEST, 'location': '', 'content': " ".join(ISE_400_INVALID_STATE_XML.split())} ISE_409_CONFLICT_XML = \ """Conflict""" ISE_409_CONFLICT_RESP =\ {'status': http_client.CONFLICT, 'location': '', 'content': " ".join(ISE_409_CONFLICT_XML.split())} DRIVER = "cinder.volume.drivers.xio.XIOISEDriver" @mock.patch(DRIVER + "._opener", autospec=True) class XIOISEDriverTestCase(object): # Test cases for X-IO volume driver def setUp(self): super(XIOISEDriverTestCase, self).setUp() # set good default values self.configuration = mock.Mock() self.configuration.san_ip = ISE_IP1 self.configuration.san_user = 'fakeuser' self.configuration.san_password = 'fakepass' self.configuration.iscsi_ip_address = ISE_ISCSI_IP1 self.configuration.driver_use_ssl = False self.configuration.ise_completion_retries = 30 self.configuration.ise_connection_retries = 5 self.configuration.ise_retry_interval = 1 self.configuration.volume_backend_name = 'ise1' self.driver = None self.protocol = '' self.connector = None self.connection_failures = 0 self.hostgid = '' self.use_response_table = 1 def setup_test(self, protocol): self.protocol = protocol # set good default values if self.protocol == 'iscsi': self.configuration.ise_protocol = protocol self.connector = ISCSI_CONN1 self.hostgid = self.connector['initiator'] elif self.protocol == 'fibre_channel': self.configuration.ise_protocol = protocol self.connector = FC_CONN1 self.hostgid = self.connector['wwpns'][0] def setup_driver(self): # this setups up driver object with previously set configuration values if self.configuration.ise_protocol == 'iscsi': self.driver =\ xio.XIOISEISCSIDriver(configuration=self.configuration) elif self.configuration.ise_protocol == 'fibre_channel': self.driver =\ xio.XIOISEFCDriver(configuration=self.configuration) elif self.configuration.ise_protocol == 'test_prot': # if test_prot specified override with correct protocol # used to bypass protocol specific driver self.configuration.ise_protocol = self.protocol self.driver = xio.XIOISEDriver(configuration=self.configuration) else: # Invalid protocol type raise exception.Invalid() ################################# # UNIT TESTS # ################################# def test_do_setup(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP]) self.driver.do_setup(None) def test_negative_do_setup_no_clone_support(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_NO_CLONE_RESP]) self.assertRaises(exception.XIODriverException, self.driver.do_setup, None) def test_negative_do_setup_no_capabilities(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_NO_CAP_RESP]) self.assertRaises(exception.XIODriverException, self.driver.do_setup, None) def test_negative_do_setup_no_ctrl(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_NO_CTRL_RESP]) self.assertRaises(exception.XIODriverException, self.driver.do_setup, None) def test_negative_do_setup_no_ipaddress(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_NO_IP_RESP]) self.driver.do_setup(None) def test_negative_do_setup_bad_globalid_none(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_NO_GID_RESP]) self.assertRaises(exception.XIODriverException, self.driver.do_setup, None) def test_check_for_setup_error(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP]) self.setup_driver() self.driver.check_for_setup_error() def test_negative_do_setup_bad_ip(self, mock_req): # set san_ip to bad value self.configuration.san_ip = '' mock_req.side_effect = iter([ISE_GET_QUERY_RESP]) self.setup_driver() self.assertRaises(exception.XIODriverException, self.driver.check_for_setup_error) def test_negative_do_setup_bad_user_blank(self, mock_req): # set san_user to bad value self.configuration.san_login = '' mock_req.side_effect = iter([ISE_GET_QUERY_RESP]) self.setup_driver() self.assertRaises(exception.XIODriverException, self.driver.check_for_setup_error) def test_negative_do_setup_bad_password_blank(self, mock_req): # set san_password to bad value self.configuration.san_password = '' mock_req.side_effect = iter([ISE_GET_QUERY_RESP]) self.setup_driver() self.assertRaises(exception.XIODriverException, self.driver.check_for_setup_error) def test_get_volume_stats(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_STORAGE_POOLS_RESP]) backend_name = self.configuration.volume_backend_name if self.configuration.ise_protocol == 'iscsi': protocol = 'iSCSI' else: protocol = 'fibre_channel' exp_result = {'vendor_name': "X-IO", 'driver_version': "1.1.4", 'volume_backend_name': backend_name, 'reserved_percentage': 0, 'total_capacity_gb': 100, 'free_capacity_gb': 60, 'QoS_support': True, 'affinity': True, 'thin': False, 'pools': [{'pool_ise_name': "Pool 1", 'pool_name': "1", 'status': "Operational", 'status_details': "None", 'free_capacity_gb': 60, 'free_capacity_gb_raid_0': 60, 'free_capacity_gb_raid_1': 30, 'free_capacity_gb_raid_5': 45, 'allocated_capacity_gb': 40, 'allocated_capacity_gb_raid_0': 0, 'allocated_capacity_gb_raid_1': 40, 'allocated_capacity_gb_raid_5': 0, 'health': 100, 'media': "Hybrid", 'total_capacity_gb': 100, 'QoS_support': True, 'reserved_percentage': 0}], 'active_volumes': 2, 'storage_protocol': protocol} act_result = self.driver.get_volume_stats(True) self.assertDictEqual(exp_result, act_result) def test_get_volume_stats_ssl(self, mock_req): self.configuration.driver_use_ssl = True self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_STORAGE_POOLS_RESP]) self.driver.get_volume_stats(True) def test_negative_get_volume_stats_bad_primary(self, mock_req): self.configuration.ise_connection_retries = 1 self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_BAD_CONNECTION_RESP, ISE_GET_STORAGE_POOLS_RESP]) self.driver.get_volume_stats(True) def test_create_volume(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) VOLUME1['volume_type_id'] = type_ref['id'] self.setup_driver() if self.configuration.ise_protocol == 'iscsi': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_CREATE_VOLUME_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_GET_IONETWORKS_RESP]) exp_result = {} exp_result = {"provider_auth": ""} act_result = self.driver.create_volume(VOLUME1) self.assertDictEqual(exp_result, act_result) elif self.configuration.ise_protocol == 'fibre_channel': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_CREATE_VOLUME_RESP, ISE_GET_VOL1_STATUS_RESP]) self.driver.create_volume(VOLUME1) def test_create_volume_chap(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) VOLUME1['volume_type_id'] = type_ref['id'] self.setup_driver() if self.configuration.ise_protocol == 'iscsi': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_CREATE_VOLUME_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_GET_IONETWORKS_CHAP_RESP]) exp_result = {} exp_result = {"provider_auth": "CHAP abc abc"} act_result = self.driver.create_volume(VOLUME1) self.assertDictEqual(exp_result, act_result) elif self.configuration.ise_protocol == 'fibre_channel': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_CREATE_VOLUME_RESP, ISE_GET_VOL1_STATUS_RESP]) self.driver.create_volume(VOLUME1) def test_create_volume_type_none(self, mock_req): self.setup_driver() if self.configuration.ise_protocol == 'iscsi': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_CREATE_VOLUME_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_GET_IONETWORKS_RESP]) elif self.configuration.ise_protocol == 'fibre_channel': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_CREATE_VOLUME_RESP, ISE_GET_VOL1_STATUS_RESP]) self.driver.create_volume(VOLUME3) def test_delete_volume(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_DELETE_ALLOC_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_DELETE_VOLUME_RESP, ISE_GET_VOL_STATUS_404_RESP]) self.setup_driver() self.driver.delete_volume(VOLUME1) def test_delete_volume_delayed(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_DELETE_ALLOC_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_DELETE_VOLUME_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_GET_VOL_STATUS_404_RESP]) self.setup_driver() self.driver.delete_volume(VOLUME1) def test_delete_volume_timeout(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_DELETE_ALLOC_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_DELETE_VOLUME_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_GET_VOL1_STATUS_RESP]) self.configuration.ise_completion_retries = 3 self.setup_driver() self.driver.delete_volume(VOLUME1) def test_delete_volume_none_existing(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_DELETE_ALLOC_RESP, ISE_GET_VOL1_STATUS_RESP]) self.setup_driver() self.driver.delete_volume(VOLUME2) def test_initialize_connection_positive(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST2_RESP, ISE_CREATE_HOST_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_CREATE_ALLOC_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_GET_CONTROLLERS_RESP]) self.setup_driver() exp_result = {} if self.configuration.ise_protocol == 'iscsi': exp_result = {"driver_volume_type": "iscsi", "data": {"target_lun": 1, "volume_id": '1', "target_discovered": False, "target_iqn": ISE_IQN, "target_portal": ISE_ISCSI_IP1 + ":3260"}} elif self.configuration.ise_protocol == 'fibre_channel': exp_result = {"driver_volume_type": "fibre_channel", "data": {"target_lun": 1, "volume_id": '1', "target_discovered": True, "initiator_target_map": ISE_INIT_TARGET_MAP, "target_wwn": ISE_TARGETS}} act_result =\ self.driver.initialize_connection(VOLUME1, self.connector) self.assertDictEqual(exp_result, act_result) def test_initialize_connection_positive_host_type(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST1_HOST_TYPE_RESP, ISE_MODIFY_HOST_RESP, ISE_CREATE_ALLOC_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_GET_CONTROLLERS_RESP]) self.setup_driver() exp_result = {} if self.configuration.ise_protocol == 'iscsi': exp_result = {"driver_volume_type": "iscsi", "data": {"target_lun": 1, "volume_id": '1', "target_discovered": False, "target_iqn": ISE_IQN, "target_portal": ISE_ISCSI_IP1 + ":3260"}} elif self.configuration.ise_protocol == 'fibre_channel': exp_result = {"driver_volume_type": "fibre_channel", "data": {"target_lun": 1, "volume_id": '1', "target_discovered": True, "initiator_target_map": ISE_INIT_TARGET_MAP, "target_wwn": ISE_TARGETS}} act_result =\ self.driver.initialize_connection(VOLUME1, self.connector) self.assertDictEqual(exp_result, act_result) def test_initialize_connection_positive_chap(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST2_RESP, ISE_CREATE_HOST_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_CREATE_ALLOC_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_GET_CONTROLLERS_RESP]) self.setup_driver() exp_result = {} if self.configuration.ise_protocol == 'iscsi': exp_result = {"driver_volume_type": "iscsi", "data": {"target_lun": 1, "volume_id": '2', "target_discovered": False, "target_iqn": ISE_IQN, "target_portal": ISE_ISCSI_IP1 + ":3260", 'auth_method': 'CHAP', 'auth_username': 'abc', 'auth_password': 'abc'}} elif self.configuration.ise_protocol == 'fibre_channel': exp_result = {"driver_volume_type": "fibre_channel", "data": {"target_lun": 1, "volume_id": '2', "target_discovered": True, "initiator_target_map": ISE_INIT_TARGET_MAP, "target_wwn": ISE_TARGETS}} act_result =\ self.driver.initialize_connection(VOLUME2, self.connector) self.assertDictEqual(exp_result, act_result) def test_initialize_connection_negative_no_host(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST2_RESP, ISE_CREATE_HOST_RESP, ISE_GET_HOSTS_HOST2_RESP]) self.setup_driver() self.assertRaises(exception.XIODriverException, self.driver.initialize_connection, VOLUME2, self.connector) def test_initialize_connection_negative_host_type(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST1_HOST_TYPE_RESP, ISE_400_RESP]) self.setup_driver() self.assertRaises(exception.XIODriverException, self.driver.initialize_connection, VOLUME2, self.connector) def test_terminate_connection_positive(self, mock_req): self.setup_driver() if self.configuration.ise_protocol == 'iscsi': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_DELETE_ALLOC_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_DELETE_ALLOC_RESP]) elif self.configuration.ise_protocol == 'fibre_channel': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_DELETE_ALLOC_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_GET_CONTROLLERS_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_DELETE_ALLOC_RESP]) self.driver.terminate_connection(VOLUME1, self.connector) def test_terminate_connection_positive_noalloc(self, mock_req): self.setup_driver() if self.configuration.ise_protocol == 'iscsi': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_GET_ALLOC_WITH_NO_ALLOC_RESP, ISE_GET_ALLOC_WITH_NO_ALLOC_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_DELETE_ALLOC_RESP]) elif self.configuration.ise_protocol == 'fibre_channel': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_GET_ALLOC_WITH_NO_ALLOC_RESP, ISE_GET_ALLOC_WITH_NO_ALLOC_RESP, ISE_GET_CONTROLLERS_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_DELETE_ALLOC_RESP]) self.driver.terminate_connection(VOLUME1, self.connector) def test_negative_terminate_connection_bad_host(self, mock_req): self.setup_driver() test_connector = {} if self.configuration.ise_protocol == 'iscsi': test_connector['initiator'] = 'bad_iqn' test_connector['host'] = '' mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST1_RESP]) elif self.configuration.ise_protocol == 'fibre_channel': test_connector['wwpns'] = 'bad_wwn' test_connector['host'] = '' mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_HOSTS_HOST1_RESP, ISE_GET_CONTROLLERS_RESP]) self.driver.terminate_connection(VOLUME1, test_connector) def test_create_snapshot(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) SNAPSHOT1['volume_type_id'] = type_ref['id'] mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_PREP_SNAPSHOT_RESP, ISE_GET_SNAP1_STATUS_RESP, ISE_CREATE_SNAPSHOT_RESP, ISE_GET_SNAP1_STATUS_RESP]) self.setup_driver() self.driver.create_snapshot(SNAPSHOT1) @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=utils.ZeroIntervalLoopingCall) def test_negative_create_snapshot_invalid_state_recover(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) SNAPSHOT1['volume_type_id'] = type_ref['id'] mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_400_INVALID_STATE_RESP, ISE_PREP_SNAPSHOT_RESP, ISE_GET_SNAP1_STATUS_RESP, ISE_CREATE_SNAPSHOT_RESP, ISE_GET_SNAP1_STATUS_RESP]) self.setup_driver() self.driver.create_snapshot(SNAPSHOT1) @mock.patch('oslo_service.loopingcall.FixedIntervalLoopingCall', new=utils.ZeroIntervalLoopingCall) def test_negative_create_snapshot_invalid_state_norecover(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) SNAPSHOT1['volume_type_id'] = type_ref['id'] mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_400_INVALID_STATE_RESP, ISE_400_INVALID_STATE_RESP, ISE_400_INVALID_STATE_RESP, ISE_400_INVALID_STATE_RESP, ISE_400_INVALID_STATE_RESP]) self.configuration.ise_completion_retries = 5 self.setup_driver() self.assertRaises(exception.XIODriverException, self.driver.create_snapshot, SNAPSHOT1) def test_negative_create_snapshot_conflict(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) SNAPSHOT1['volume_type_id'] = type_ref['id'] mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_409_CONFLICT_RESP]) self.configuration.ise_completion_retries = 1 self.setup_driver() self.assertRaises(exception.XIODriverException, self.driver.create_snapshot, SNAPSHOT1) def test_delete_snapshot(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_ALLOC_WITH_EP_RESP, ISE_DELETE_ALLOC_RESP, ISE_GET_SNAP1_STATUS_RESP, ISE_DELETE_VOLUME_RESP]) self.setup_driver() self.driver.delete_snapshot(SNAPSHOT1) def test_clone_volume(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) VOLUME1['volume_type_id'] = type_ref['id'] mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_PREP_SNAPSHOT_RESP, ISE_GET_SNAP1_STATUS_RESP, ISE_CREATE_SNAPSHOT_RESP, ISE_GET_SNAP1_STATUS_RESP]) self.setup_driver() self.driver.create_cloned_volume(CLONE1, VOLUME1) def test_extend_volume(self, mock_req): mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_MODIFY_VOLUME_RESP]) self.setup_driver() self.driver.extend_volume(VOLUME1, NEW_VOLUME_SIZE) def test_retype_volume(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) VOLUME1['volume_type_id'] = type_ref['id'] # New volume type extra_specs = {"Feature:Pool": "1", "Feature:Raid": "5", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT2', extra_specs) specs = {'qos:minIOPS': '30', 'qos:maxIOPS': '3000', 'qos:burstIOPS': '10000'} qos = qos_specs.create(ctxt, 'fake-qos2', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_MODIFY_VOLUME_RESP]) self.setup_driver() self.driver.retype(ctxt, VOLUME1, type_ref, 0, 0) def test_create_volume_from_snapshot(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) SNAPSHOT1['volume_type_id'] = type_ref['id'] mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_SNAP1_STATUS_RESP, ISE_PREP_SNAPSHOT_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_CREATE_SNAPSHOT_RESP, ISE_GET_VOL1_STATUS_RESP]) self.setup_driver() self.driver.create_volume_from_snapshot(VOLUME1, SNAPSHOT1) def test_manage_existing(self, mock_req): ctxt = context.get_admin_context() extra_specs = {"Feature:Pool": "1", "Feature:Raid": "1", "Affinity:Type": "flash", "Alloc:Type": "thick"} type_ref = volume_types.create(ctxt, 'VT1', extra_specs) specs = {'qos:minIOPS': '20', 'qos:maxIOPS': '2000', 'qos:burstIOPS': '5000'} qos = qos_specs.create(ctxt, 'fake-qos', specs) qos_specs.associate_qos_with_type(ctxt, qos['id'], type_ref['id']) VOLUME1['volume_type_id'] = type_ref['id'] self.setup_driver() if self.configuration.ise_protocol == 'iscsi': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_MODIFY_VOLUME_RESP, ISE_GET_IONETWORKS_RESP]) elif self.configuration.ise_protocol == 'fibre_channel': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_MODIFY_VOLUME_RESP]) self.driver.manage_existing(VOLUME1, {'source-name': 'testvol'}) def test_manage_existing_no_source_name(self, mock_req): self.setup_driver() if self.configuration.ise_protocol == 'iscsi': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_MODIFY_VOLUME_RESP, ISE_GET_IONETWORKS_RESP]) elif self.configuration.ise_protocol == 'fibre_channel': mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP, ISE_MODIFY_VOLUME_RESP]) self.assertRaises(exception.XIODriverException, self.driver.manage_existing, VOLUME1, {}) def test_manage_existing_get_size(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP]) exp_result = 10 act_result = \ self.driver.manage_existing_get_size(VOLUME1, {'source-name': 'a'}) self.assertEqual(exp_result, act_result) def test_manage_existing_get_size_no_source_name(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP]) self.assertRaises(exception.XIODriverException, self.driver.manage_existing_get_size, VOLUME1, {}) def test_unmanage(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL1_STATUS_RESP]) self.driver.unmanage(VOLUME1) def test_negative_unmanage_no_volume_status_xml(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL_STATUS_NO_STATUS_RESP]) self.driver.unmanage(VOLUME1) def test_negative_unmanage_no_volume_xml(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL_STATUS_NO_VOL_NODE_RESP]) self.assertRaises(exception.XIODriverException, self.driver.unmanage, VOLUME1) def test_negative_unmanage_non_existing_volume(self, mock_req): self.setup_driver() mock_req.side_effect = iter([ISE_GET_QUERY_RESP, ISE_GET_VOL_STATUS_404_RESP]) self.assertRaises(exception.XIODriverException, self.driver.unmanage, VOLUME1) class XIOISEISCSIDriverTestCase(XIOISEDriverTestCase, test.TestCase): def setUp(self): super(XIOISEISCSIDriverTestCase, self).setUp() self.setup_test('iscsi') class XIOISEFCDriverTestCase(XIOISEDriverTestCase, test.TestCase): def setUp(self): super(XIOISEFCDriverTestCase, self).setUp() self.setup_test('fibre_channel')