# Copyright (c) 2012 - 2014 EMC Corporation, Inc. # 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 cinder import exception from cinder import test from cinder.volume.drivers.emc import xtremio typ2id = {'volumes': 'vol-id', 'snapshots': 'vol-id', 'initiators': 'initiator-id', 'initiator-groups': 'ig-id', 'lun-maps': 'mapping-id'} xms_data = {'xms': {1: {'version': '4.0.0'}}, 'clusters': {'cluster1': {'name': 'cluster1', 'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73", 'ud-ssd-space': '8146708710', 'ud-ssd-space-in-use': '708710', 'vol-size': '29884416', 'chap-authentication-mode': 'disabled', 'chap-discovery-mode': 'disabled', "index": 1}, 1: {'name': 'cluster1', 'sys-sw-version': "3.0.0-devel_ba23ee5381eeab73", 'ud-ssd-space': '8146708710', 'ud-ssd-space-in-use': '708710', 'vol-size': '29884416', 'chap-authentication-mode': 'disabled', 'chap-discovery-mode': 'disabled', "index": 1}}, 'target-groups': {'Default': {"index": 1, }}, 'iscsi-portals': {'10.205.68.5/16': {"port-address": "iqn.2008-05.com.xtremio:001e67939c34", "ip-port": 3260, "ip-addr": "10.205.68.5/16", "name": "10.205.68.5/16", "index": 1}}, 'targets': {'X1-SC2-fc1': {'index': 1, "name": "X1-SC2-fc1", "port-address": "21:00:00:24:ff:57:b2:36", 'port-state': 'up'}, 'X1-SC2-fc2': {'index': 2, "name": "X1-SC2-fc2", "port-address": "21:00:00:24:ff:57:b2:55", 'port-state': 'up'} }, 'volumes': {}, 'initiator-groups': {}, 'initiators': {}, 'lun-maps': {}, } def clean_xms_data(): xms_data['volumes'] = {} xms_data['initiator-groups'] = {} xms_data['initiators'] = {} xms_data['lun-maps'] = {} def fix_data(data, object_type): d = {} for key, value in data.items(): if 'name' in key: key = 'name' d[key] = value if object_type == 'lun-maps': d['lun'] = 1 d[typ2id[object_type]] = ["a91e8c81c2d14ae4865187ce4f866f8a", d.get('name'), len(xms_data[object_type]) + 1] d['index'] = len(xms_data[object_type]) + 1 return d def get_xms_obj_key(data): for key in data.keys(): if 'name' in key: return key def xms_request(object_type='volumes', request_typ='GET', data=None, name=None, idx=None): if object_type == 'snapshots': object_type = 'volumes' obj_key = name if name else idx if request_typ == 'GET': try: res = xms_data[object_type] except KeyError: raise exception.VolumeDriverException if name or idx: if obj_key not in res: raise exception.NotFound() return {"content": res[obj_key]} else: if data and data.get('full') == 1: return {object_type: res.values()} else: return {object_type: [{"href": "/%s/%d" % (object_type, obj['index']), "name": obj.get('name')} for obj in res.values()]} elif request_typ == 'POST': data = fix_data(data, object_type) data['index'] = len(xms_data[object_type]) + 1 xms_data[object_type][data['index']] = data # find the name key name_key = get_xms_obj_key(data) if object_type == 'lun-maps': data['ig-name'] = data['ig-id'] if name_key: if data[name_key] in xms_data[object_type]: raise (exception .VolumeBackendAPIException ('Volume by this name already exists')) xms_data[object_type][data[name_key]] = data return {"links": [{"href": "/%s/%d" % (object_type, data[typ2id[object_type]][2])}]} elif request_typ == 'DELETE': if obj_key in xms_data[object_type]: data = xms_data[object_type][obj_key] del xms_data[object_type][data['index']] del xms_data[object_type][data[typ2id[object_type]][1]] else: raise exception.NotFound() elif request_typ == 'PUT': if obj_key in xms_data[object_type]: obj = xms_data[object_type][obj_key] obj.update(data) key = get_xms_obj_key(data) if key: xms_data[object_type][data[key]] = obj else: raise exception.NotFound() def xms_bad_request(object_type='volumes', request_typ='GET', data=None, name=None, idx=None): if request_typ == 'GET': raise exception.NotFound() elif request_typ == 'POST': raise exception.VolumeBackendAPIException('Failed to create ig') def xms_failed_rename_snapshot_request(object_type='volumes', request_typ='GET', data=None, name=None, idx=None): if request_typ == 'POST': xms_data['volumes'][27] = {} return { "links": [ { "href": "https://host/api/json/v2/types/snapshots/27", "rel": "self"}]} elif request_typ == 'PUT': raise exception.VolumeBackendAPIException(msg='Failed to delete') elif request_typ == 'DELETE': del xms_data['volumes'][27] class D(dict): def update(self, *args, **kwargs): self.__dict__.update(*args, **kwargs) return dict.update(self, *args, **kwargs) class CommonData(object): connector = {'ip': '10.0.0.2', 'initiator': 'iqn.1993-08.org.debian:01:222', 'wwpns': ["123456789012345", "123456789054321"], 'wwnns': ["223456789012345", "223456789054321"], 'host': 'fakehost'} test_volume = {'name': 'vol1', 'size': 1, 'volume_name': 'vol1', 'id': '192eb39b-6c2f-420c-bae3-3cfd117f0001', 'provider_auth': None, 'project_id': 'project', 'display_name': 'vol1', 'display_description': 'test volume', 'volume_type_id': None} test_snapshot = D() test_snapshot.update({'name': 'snapshot1', 'size': 1, 'id': '192eb39b-6c2f-420c-bae3-3cfd117f0002', 'volume_name': 'vol-vol1', 'volume_id': '192eb39b-6c2f-420c-bae3-3cfd117f0001', 'project_id': 'project'}) test_snapshot.__dict__.update(test_snapshot) test_volume2 = {'name': 'vol2', 'size': 1, 'volume_name': 'vol2', 'id': '192eb39b-6c2f-420c-bae3-3cfd117f0004', 'provider_auth': None, 'project_id': 'project', 'display_name': 'vol2', 'display_description': 'test volume 2', 'volume_type_id': None} test_clone = {'name': 'clone1', 'size': 1, 'volume_name': 'vol3', 'id': '192eb39b-6c2f-420c-bae3-3cfd117f0003', 'provider_auth': None, 'project_id': 'project', 'display_name': 'clone1', 'display_description': 'volume created from snapshot', 'volume_type_id': None} unmanaged1 = {'id': 'unmanaged1', 'name': 'unmanaged1', 'size': 3} @mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOClient.req') class EMCXIODriverISCSITestCase(test.TestCase): def setUp(self): super(EMCXIODriverISCSITestCase, self).setUp() configuration = mock.Mock() configuration.san_login = '' configuration.san_password = '' configuration.san_ip = '' configuration.xtremio_cluster_name = '' configuration.xtremio_provisioning_factor = 20.0 def safe_get(key): getattr(configuration, key) configuration.safe_get = safe_get self.driver = xtremio.XtremIOISCSIDriver(configuration=configuration) self.data = CommonData() def test_check_for_setup_error(self, req): req.side_effect = xms_request clusters = xms_data['clusters'] del xms_data['clusters'] self.assertRaises(exception.VolumeDriverException, self.driver.check_for_setup_error) xms_data['clusters'] = clusters self.driver.check_for_setup_error() def test_create_extend_delete_volume(self, req): req.side_effect = xms_request clean_xms_data() self.driver.create_volume(self.data.test_volume) self.driver.extend_volume(self.data.test_volume, 5) self.driver.delete_volume(self.data.test_volume) def test_create_delete_snapshot(self, req): req.side_effect = xms_request clean_xms_data() self.driver.create_volume(self.data.test_volume) self.driver.create_snapshot(self.data.test_snapshot) self.assertEqual(self.data.test_snapshot['id'], xms_data['volumes'][3]['name']) self.driver.delete_snapshot(self.data.test_snapshot) self.driver.delete_volume(self.data.test_volume) def test_failed_rename_snapshot(self, req): req.side_effect = xms_failed_rename_snapshot_request self.driver.create_snapshot(self.data.test_snapshot) self.assertIn(27, xms_data['volumes']) clean_xms_data() def test_volume_from_snapshot(self, req): req.side_effect = xms_request clean_xms_data() xms_data['volumes'] = {} self.driver.create_volume(self.data.test_volume) self.driver.create_snapshot(self.data.test_snapshot) self.driver.create_volume_from_snapshot(self.data.test_volume2, self.data.test_snapshot) self.driver.delete_volume(self.data.test_volume2) self.driver.delete_volume(self.data.test_snapshot) self.driver.delete_volume(self.data.test_volume) def test_clone_volume(self, req): req.side_effect = xms_request clean_xms_data() self.driver.create_volume(self.data.test_volume) self.driver.create_cloned_volume(self.data.test_clone, self.data.test_volume) self.driver.delete_volume(self.data.test_clone) self.driver.delete_volume(self.data.test_volume) def test_duplicate_volume(self, req): req.side_effect = xms_request clean_xms_data() self.driver.create_volume(self.data.test_volume) self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume, self.data.test_volume) self.driver.delete_volume(self.data.test_volume) def test_no_portals_configured(self, req): req.side_effect = xms_request clean_xms_data() portals = xms_data['iscsi-portals'].copy() xms_data['iscsi-portals'].clear() lunmap = {'lun': 4} self.assertRaises(exception.VolumeDriverException, self.driver._get_iscsi_properties, lunmap) xms_data['iscsi-portals'] = portals def test_initialize_terminate_connection(self, req): req.side_effect = xms_request clean_xms_data() self.driver.create_volume(self.data.test_volume) map_data = self.driver.initialize_connection(self.data.test_volume, self.data.connector) self.assertEqual(map_data['data']['target_lun'], 1) self.driver.terminate_connection(self.data.test_volume, self.data.connector) def test_initialize_connection_bad_ig(self, req): req.side_effect = xms_bad_request clean_xms_data() self.assertRaises(exception.VolumeBackendAPIException, self.driver.initialize_connection, self.data.test_volume, self.data.connector) self.driver.delete_volume(self.data.test_volume) def test_get_stats(self, req): req.side_effect = xms_request clean_xms_data() stats = self.driver.get_volume_stats(True) self.assertEqual(stats['volume_backend_name'], self.driver.backend_name) def test_manage_unmanage(self, req): req.side_effect = xms_request clean_xms_data() xms_data['volumes'] = {'unmanaged1': {'vol-name': 'unmanaged1', 'index': 'unmanaged1', 'vol-size': '3'}} ref_vol = {"source-name": "unmanaged1"} invalid_ref = {"source-name": "invalid"} self.assertRaises(exception.ManageExistingInvalidReference, self.driver.manage_existing_get_size, self.data.test_volume, invalid_ref) self.driver.manage_existing_get_size(self.data.test_volume, ref_vol) self.assertRaises(exception.ManageExistingInvalidReference, self.driver.manage_existing, self.data.test_volume, invalid_ref) self.driver.manage_existing(self.data.test_volume, ref_vol) self.assertRaises(exception.VolumeNotFound, self.driver.unmanage, self.data.test_volume2) self.driver.unmanage(self.data.test_volume) @mock.patch('cinder.volume.drivers.emc.xtremio.XtremIOClient.req') class EMCXIODriverFibreChannelTestCase(test.TestCase): def setUp(self): super(EMCXIODriverFibreChannelTestCase, self).setUp() configuration = mock.Mock() configuration.san_login = '' configuration.san_password = '' configuration.san_ip = '' configuration.xtremio_cluster_name = '' configuration.xtremio_provisioning_factor = 20.0 self.driver = xtremio.XtremIOFibreChannelDriver( configuration=configuration) self.data = CommonData() def test_initialize_terminate_connection(self, req): req.side_effect = xms_request clean_xms_data() self.driver.create_volume(self.data.test_volume) map_data = self.driver.initialize_connection(self.data.test_volume, self.data.connector) self.assertEqual(map_data['data']['target_lun'], 1) self.driver.terminate_connection(self.data.test_volume, self.data.connector) self.driver.delete_volume(self.data.test_volume)