--- /dev/null
+# Copyright (c) 2014 Dell Inc.
+#
+# 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 context
+from cinder import exception
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder.volume.drivers.dell import dell_storagecenter_api
+from cinder.volume.drivers.dell import dell_storagecenter_fc
+
+
+LOG = logging.getLogger(__name__)
+
+# We patch these here as they are used by every test to keep
+# from trying to contact a Dell Storage Center.
+
+
+@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ '__init__',
+ return_value=None)
+@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'open_connection')
+@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'close_connection')
+class DellSCSanFCDriverTestCase(test.TestCase):
+
+ VOLUME = {u'instanceId': u'64702.4829',
+ u'scSerialNumber': 64702,
+ u'replicationSource': False,
+ u'liveVolume': False,
+ u'vpdId': 4831,
+ u'objectType': u'ScVolume',
+ u'index': 4829,
+ u'volumeFolderPath': u'dopnstktst/',
+ u'hostCacheEnabled': False,
+ u'usedByLegacyFluidFsNasVolume': False,
+ u'inRecycleBin': False,
+ u'volumeFolderIndex': 17,
+ u'instanceName': u'5729f1db-4c45-416c-bc15-c8ea13a4465d',
+ u'statusMessage': u'',
+ u'status': u'Down',
+ u'storageType': {u'instanceId': u'64702.1',
+ u'instanceName': u'Assigned - Redundant - 2 MB',
+ u'objectType': u'ScStorageType'},
+ u'cmmDestination': False,
+ u'replicationDestination': False,
+ u'volumeFolder': {u'instanceId': u'64702.17',
+ u'instanceName': u'opnstktst',
+ u'objectType': u'ScVolumeFolder'},
+ u'deviceId': u'6000d31000fcbe0000000000000012df',
+ u'active': False,
+ u'portableVolumeDestination': False,
+ u'deleteAllowed': True,
+ u'name': u'5729f1db-4c45-416c-bc15-c8ea13a4465d',
+ u'scName': u'Storage Center 64702',
+ u'secureDataUsed': False,
+ u'serialNumber': u'0000fcbe-000012df',
+ u'replayAllowed': False,
+ u'flashOptimized': False,
+ u'configuredSize': u'1.073741824E9 Bytes',
+ u'mapped': False,
+ u'cmmSource': False}
+
+ SCSERVER = {u'scName': u'Storage Center 64702',
+ u'volumeCount': 0,
+ u'removeHbasAllowed': True,
+ u'legacyFluidFs': False,
+ u'serverFolderIndex': 4,
+ u'alertOnConnectivity': True,
+ u'objectType': u'ScPhysicalServer',
+ u'instanceName': u'Server_21000024ff30441d',
+ u'instanceId': u'64702.47',
+ u'serverFolderPath': u'opnstktst/',
+ u'portType': [u'FibreChannel'],
+ u'type': u'Physical',
+ u'statusMessage': u'Only 5 of 6 expected paths are up',
+ u'status': u'Degraded',
+ u'scSerialNumber': 64702,
+ u'serverFolder': {u'instanceId': u'64702.4',
+ u'instanceName': u'opnstktst',
+ u'objectType': u'ScServerFolder'},
+ u'parentIndex': 0,
+ u'connectivity': u'Partial',
+ u'hostCacheIndex': 0,
+ u'deleteAllowed': True,
+ u'pathCount': 5,
+ u'name': u'Server_21000024ff30441d',
+ u'hbaPresent': True,
+ u'hbaCount': 2,
+ u'notes': u'Created by Dell Cinder Driver',
+ u'mapped': False,
+ u'operatingSystem': {u'instanceId': u'64702.38',
+ u'instanceName': u'Red Hat Linux 6.x',
+ u'objectType': u'ScServerOperatingSystem'}
+ }
+
+ MAPPING = {u'instanceId': u'64702.2183',
+ u'scName': u'Storage Center 64702',
+ u'scSerialNumber': 64702,
+ u'controller': {u'instanceId': u'64702.64702',
+ u'instanceName': u'SN 64702',
+ u'objectType': u'ScController'},
+ u'lunUsed': [1],
+ u'server': {u'instanceId': u'64702.47',
+ u'instanceName': u'Server_21000024ff30441d',
+ u'objectType': u'ScPhysicalServer'},
+ u'volume': {u'instanceId': u'64702.4829',
+ u'instanceName':
+ u'5729f1db-4c45-416c-bc15-c8ea13a4465d',
+ u'objectType': u'ScVolume'},
+ u'connectivity': u'Up',
+ u'readOnly': False,
+ u'objectType': u'ScMappingProfile',
+ u'hostCache': False,
+ u'mappedVia': u'Server',
+ u'mapCount': 2,
+ u'instanceName': u'4829-47',
+ u'lunRequested': u'N/A'
+ }
+
+ def setUp(self):
+ super(DellSCSanFCDriverTestCase, self).setUp()
+
+ # configuration is a mock. A mock is pretty much a blank
+ # slate. I believe mock's done in setup are not happy time
+ # mocks. So we just do a few things like driver config here.
+ self.configuration = mock.Mock()
+
+ self.configuration.san_is_local = False
+ self.configuration.san_ip = "192.168.0.1"
+ self.configuration.san_login = "admin"
+ self.configuration.san_password = "pwd"
+ self.configuration.dell_sc_ssn = 64702
+ self.configuration.dell_sc_server_folder = 'opnstktst'
+ self.configuration.dell_sc_volume_folder = 'opnstktst'
+ self.configuration.dell_sc_api_port = 3033
+ self._context = context.get_admin_context()
+
+ self.driver = dell_storagecenter_fc.DellStorageCenterFCDriver(
+ configuration=self.configuration)
+
+ self.driver.do_setup(None)
+
+ self.driver._stats = {'QoS_support': False,
+ 'volume_backend_name': 'dell-1',
+ 'free_capacity_gb': 12123,
+ 'driver_version': '1.0.1',
+ 'total_capacity_gb': 12388,
+ 'reserved_percentage': 0,
+ 'vendor_name': 'Dell',
+ 'storage_protocol': 'FC'}
+
+ self.volid = '5729f1db-4c45-416c-bc15-c8ea13a4465d'
+ self.volume_name = "volume" + self.volid
+ self.connector = {'ip': '192.168.0.77',
+ 'host': 'cinderfc-vm',
+ 'wwnns': ['20000024ff30441c', '20000024ff30441d'],
+ 'initiator': 'iqn.1993-08.org.debian:01:e1b1312f9e1',
+ 'wwpns': ['21000024ff30441c', '21000024ff30441d']}
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_server_multiple_hbas',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'map_volume',
+ return_value=MAPPING)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(1,
+ [u'5000D31000FCBE3D',
+ u'5000D31000FCBE35'],
+ {u'21000024FF30441C':
+ [u'5000D31000FCBE35'],
+ u'21000024FF30441D':
+ [u'5000D31000FCBE3D']}))
+ def test_initialize_connection(self,
+ mock_find_wwns,
+ mock_map_volume,
+ mock_find_volume,
+ mock_create_server,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name}
+ connector = self.connector
+ data = self.driver.initialize_connection(volume, connector)
+ self.assertEqual(data['driver_volume_type'], 'fibre_channel')
+ # verify find_volume has been called and that is has been called twice
+ mock_find_volume.assert_any_call(64702, self.volume_name)
+ assert mock_find_volume.call_count == 2
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'map_volume',
+ return_value=MAPPING)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(None, [], {}))
+ def test_initialize_connection_no_wwns(self,
+ mock_find_wwns,
+ mock_map_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name}
+ connector = self.connector
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_server_multiple_hbas',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'map_volume',
+ return_value=MAPPING)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(None, [], {}))
+ def test_initialize_connection_no_server(self,
+ mock_find_wwns,
+ mock_map_volume,
+ mock_find_volume,
+ mock_create_server,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name}
+ connector = self.connector
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'map_volume',
+ return_value=MAPPING)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(None, [], {}))
+ def test_initialize_connection_vol_not_found(self,
+ mock_find_wwns,
+ mock_map_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = self.connector
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=True)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(1,
+ [u'5000D31000FCBE3D',
+ u'5000D31000FCBE35'],
+ {u'21000024FF30441C':
+ [u'5000D31000FCBE35'],
+ u'21000024FF30441D':
+ [u'5000D31000FCBE3D']}))
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_volume_count',
+ return_value=1)
+ def test_terminate_connection(self,
+ mock_get_volume_count,
+ mock_find_wwns,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name}
+ connector = self.connector
+ self.driver.terminate_connection(volume, connector)
+ mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=True)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(1,
+ [u'5000D31000FCBE3D',
+ u'5000D31000FCBE35'],
+ {u'21000024FF30441C':
+ [u'5000D31000FCBE35'],
+ u'21000024FF30441D':
+ [u'5000D31000FCBE3D']}))
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_volume_count',
+ return_value=1)
+ def test_terminate_connection_no_server(self,
+ mock_get_volume_count,
+ mock_find_wwns,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = self.connector
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=True)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(1,
+ [u'5000D31000FCBE3D',
+ u'5000D31000FCBE35'],
+ {u'21000024FF30441C':
+ [u'5000D31000FCBE35'],
+ u'21000024FF30441D':
+ [u'5000D31000FCBE3D']}))
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_volume_count',
+ return_value=1)
+ def test_terminate_connection_no_volume(self,
+ mock_get_volume_count,
+ mock_find_wwns,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = self.connector
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=True)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(None,
+ [],
+ {}))
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_volume_count',
+ return_value=1)
+ def test_terminate_connection_no_wwns(self,
+ mock_get_volume_count,
+ mock_find_wwns,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = self.connector
+ # self.assertRaises(exception.VolumeBackendAPIException,
+ # self.driver.terminate_connection,
+ # volume,
+ # connector)
+ self.driver.terminate_connection(volume, connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=False)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_wwns',
+ return_value=(1,
+ [u'5000D31000FCBE3D',
+ u'5000D31000FCBE35'],
+ {u'21000024FF30441C':
+ [u'5000D31000FCBE35'],
+ u'21000024FF30441D':
+ [u'5000D31000FCBE3D']}))
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_volume_count',
+ return_value=1)
+ def test_terminate_connection_failure(self,
+ mock_get_volume_count,
+ mock_find_wwns,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = self.connector
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_storage_usage',
+ return_value={'availableSpace': 100, 'freeSpace': 50})
+ def test_update_volume_stats_with_refresh(self,
+ mock_get_storage_usage,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ stats = self.driver.get_volume_stats(True)
+ self.assertEqual(stats['storage_protocol'], 'FC')
+ mock_get_storage_usage.called_once_with(64702)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_storage_usage',
+ return_value={'availableSpace': 100, 'freeSpace': 50})
+ def test_get_volume_stats_no_refresh(self,
+ mock_get_storage_usage,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ stats = self.driver.get_volume_stats(False)
+ self.assertEqual(stats['storage_protocol'], 'FC')
+ assert mock_get_storage_usage.called is False
--- /dev/null
+# Copyright (c) 2014 Dell Inc.
+#
+# 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.
+
+from cinder import context
+from cinder import exception
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder.volume.drivers.dell import dell_storagecenter_api
+from cinder.volume.drivers.dell import dell_storagecenter_iscsi
+
+import mock
+
+import uuid
+
+
+LOG = logging.getLogger(__name__)
+
+# We patch these here as they are used by every test to keep
+# from trying to contact a Dell Storage Center.
+
+
+@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ '__init__',
+ return_value=None)
+@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'open_connection')
+@mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'close_connection')
+class DellSCSanISCSIDriverTestCase(test.TestCase):
+
+ VOLUME = {u'instanceId': u'64702.3494',
+ u'scSerialNumber': 64702,
+ u'replicationSource': False,
+ u'liveVolume': False,
+ u'vpdId': 3496,
+ u'objectType': u'ScVolume',
+ u'index': 3494,
+ u'volumeFolderPath': u'devstackvol/fcvm/',
+ u'hostCacheEnabled': False,
+ u'usedByLegacyFluidFsNasVolume': False,
+ u'inRecycleBin': False,
+ u'volumeFolderIndex': 17,
+ u'instanceName': u'volume-37883deb-85cd-426a-9a98-62eaad8671ea',
+ u'statusMessage': u'',
+ u'status': u'Up',
+ u'storageType': {u'instanceId': u'64702.1',
+ u'instanceName': u'Assigned - Redundant - 2 MB',
+ u'objectType': u'ScStorageType'},
+ u'cmmDestination': False,
+ u'replicationDestination': False,
+ u'volumeFolder': {u'instanceId': u'64702.17',
+ u'instanceName': u'fcvm',
+ u'objectType': u'ScVolumeFolder'},
+ u'deviceId': u'6000d31000fcbe000000000000000da8',
+ u'active': True,
+ u'portableVolumeDestination': False,
+ u'deleteAllowed': True,
+ u'name': u'volume-37883deb-85cd-426a-9a98-62eaad8671ea',
+ u'scName': u'Storage Center 64702',
+ u'secureDataUsed': False,
+ u'serialNumber': u'0000fcbe-00000da8',
+ u'replayAllowed': True,
+ u'flashOptimized': False,
+ u'configuredSize': u'1.073741824E9 Bytes',
+ u'mapped': False,
+ u'cmmSource': False}
+
+ SCSERVER = {u'scName': u'Storage Center 64702',
+ u'volumeCount': 0,
+ u'removeHbasAllowed': True,
+ u'legacyFluidFs': False,
+ u'serverFolderIndex': 4,
+ u'alertOnConnectivity': True,
+ u'objectType': u'ScPhysicalServer',
+ u'instanceName': u'Server_21000024ff30441d',
+ u'instanceId': u'64702.47',
+ u'serverFolderPath': u'devstacksrv/',
+ u'portType': [u'FibreChannel'],
+ u'type': u'Physical',
+ u'statusMessage': u'Only 5 of 6 expected paths are up',
+ u'status': u'Degraded',
+ u'scSerialNumber': 64702,
+ u'serverFolder': {u'instanceId': u'64702.4',
+ u'instanceName': u'devstacksrv',
+ u'objectType': u'ScServerFolder'},
+ u'parentIndex': 0,
+ u'connectivity': u'Partial',
+ u'hostCacheIndex': 0,
+ u'deleteAllowed': True,
+ u'pathCount': 5,
+ u'name': u'Server_21000024ff30441d',
+ u'hbaPresent': True,
+ u'hbaCount': 2,
+ u'notes': u'Created by Dell Cinder Driver',
+ u'mapped': False,
+ u'operatingSystem': {u'instanceId': u'64702.38',
+ u'instanceName': u'Red Hat Linux 6.x',
+ u'objectType': u'ScServerOperatingSystem'}
+ }
+
+ MAPPINGS = [{u'profile': {u'instanceId': u'64702.104',
+ u'instanceName': u'92-30',
+ u'objectType': u'ScMappingProfile'},
+ u'status': u'Down',
+ u'statusMessage': u'',
+ u'instanceId': u'64702.969.64702',
+ u'scName': u'Storage Center 64702',
+ u'scSerialNumber': 64702,
+ u'controller': {u'instanceId': u'64702.64702',
+ u'instanceName': u'SN 64702',
+ u'objectType': u'ScController'},
+ u'server': {u'instanceId': u'64702.30',
+ u'instanceName':
+ u'Server_iqn.1993-08.org.debian:01:3776df826e4f',
+ u'objectType': u'ScPhysicalServer'},
+ u'volume': {u'instanceId': u'64702.92',
+ u'instanceName':
+ u'volume-74a21934-60ad-4cf2-b89b-1f0dda309ddf',
+ u'objectType': u'ScVolume'},
+ u'readOnly': False,
+ u'lun': 1,
+ u'lunUsed': [1],
+ u'serverHba': {u'instanceId': u'64702.3454975614',
+ u'instanceName':
+ u'iqn.1993-08.org.debian:01:3776df826e4f',
+ u'objectType': u'ScServerHba'},
+ u'path': {u'instanceId': u'64702.64702.64702.31.8',
+ u'instanceName':
+ u'iqn.1993-08.org.debian:'
+ '01:3776df826e4f-5000D31000FCBE43',
+ u'objectType': u'ScServerHbaPath'},
+ u'controllerPort': {u'instanceId':
+ u'64702.5764839588723736131.91',
+ u'instanceName': u'5000D31000FCBE43',
+ u'objectType': u'ScControllerPort'},
+ u'instanceName': u'64702-969',
+ u'transport': u'Iscsi',
+ u'objectType': u'ScMapping'}]
+
+ RPLAY = {u'scSerialNumber': 64702,
+ u'globalIndex': u'64702-46-250',
+ u'description': u'Cinder Clone Replay',
+ u'parent': {u'instanceId': u'64702.46.249',
+ u'instanceName': u'64702-46-249',
+ u'objectType': u'ScReplay'},
+ u'instanceId': u'64702.46.250',
+ u'scName': u'Storage Center 64702',
+ u'consistent': False,
+ u'expires': True,
+ u'freezeTime': u'12/09/2014 03:52:08 PM',
+ u'createVolume': {u'instanceId': u'64702.46',
+ u'instanceName':
+ u'volume-ff9589d3-2d41-48d5-9ef5-2713a875e85b',
+ u'objectType': u'ScVolume'},
+ u'expireTime': u'12/09/2014 04:52:08 PM',
+ u'source': u'Manual',
+ u'spaceRecovery': False,
+ u'writesHeldDuration': 7910,
+ u'active': False,
+ u'markedForExpiration': False,
+ u'objectType': u'ScReplay',
+ u'instanceName': u'12/09/2014 03:52:08 PM',
+ u'size': u'0.0 Bytes'
+ }
+
+ IQN = 'iqn.2002-03.com.compellent:5000D31000000001'
+
+ def setUp(self):
+ super(DellSCSanISCSIDriverTestCase, self).setUp()
+
+ # configuration is a mock. A mock is pretty much a blank
+ # slate. I believe mock's done in setup are not happy time
+ # mocks. So we just do a few things like driver config here.
+ self.configuration = mock.Mock()
+
+ self.configuration.san_is_local = False
+ self.configuration.san_ip = "192.168.0.1"
+ self.configuration.san_login = "admin"
+ self.configuration.san_password = "mmm"
+ self.configuration.dell_sc_ssn = 12345
+ self.configuration.dell_sc_server_folder = 'opnstktst'
+ self.configuration.dell_sc_volume_folder = 'opnstktst'
+ self.configuration.dell_sc_api_port = 3033
+ self.configuration.iscsi_ip_address = '192.168.1.1'
+ self.configuration.iscsi_port = 3260
+ self._context = context.get_admin_context()
+
+ self.driver = dell_storagecenter_iscsi.DellStorageCenterISCSIDriver(
+ configuration=self.configuration)
+
+ self.driver.do_setup(None)
+
+ self.driver._stats = {'QoS_support': False,
+ 'volume_backend_name': 'dell-1',
+ 'free_capacity_gb': 12123,
+ 'driver_version': '1.0.1',
+ 'total_capacity_gb': 12388,
+ 'reserved_percentage': 0,
+ 'vendor_name': 'Dell',
+ 'storage_protocol': 'iSCSI'}
+
+ self.volid = str(uuid.uuid4())
+ self.volume_name = "volume" + self.volid
+ self.connector = {
+ 'ip': '10.0.0.2',
+ 'initiator': 'iqn.1993-08.org.debian:01:2227dab76162',
+ 'host': 'fakehost'}
+ self.access_record_output = [
+ "ID Initiator Ipaddress AuthMethod UserName Apply-To",
+ "--- --------------- ------------- ---------- ---------- --------",
+ "1 iqn.1993-08.org.debian:01:222 *.*.*.* none both",
+ " 7dab76162"]
+
+ self.fake_iqn = 'iqn.2002-03.com.compellent:5000D31000000001'
+ self.properties = {
+ 'target_discoverd': True,
+ 'target_portal': '%s:3260'
+ % self.driver.configuration.dell_sc_iscsi_ip,
+ 'target_iqn': self.fake_iqn,
+ 'volume_id': 1}
+ self._model_update = {
+ 'provider_location': "%s:3260,1 %s 0"
+ % (self.driver.configuration.dell_sc_iscsi_ip,
+ self.fake_iqn)
+ # ,
+ # 'provider_auth': 'CHAP %s %s' % (
+ # self.configuration.eqlx_chap_login,
+ # self.configuration.eqlx_chap_password)
+ }
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ def test_create_volume(self,
+ mock_find_sc,
+ mock_create_volume,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name, 'size': 1}
+ self.driver.create_volume(volume)
+ mock_create_volume.assert_called_once_with(self.volume_name,
+ 1,
+ 12345,
+ u'opnstktst')
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ def test_create_volume_failure(self,
+ mock_find_sc,
+ mock_create_volume,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name, 'size': 1}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_volume, volume)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'delete_volume',
+ return_value=True)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ def test_delete_volume(self,
+ mock_find_sc,
+ mock_delete_volume,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name, 'size': 1}
+ self.driver.delete_volume(volume)
+ mock_delete_volume.assert_called_once_with(12345, self.volume_name)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'delete_volume',
+ return_value=False)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ def test_delete_volume_failure(self,
+ mock_find_sc,
+ mock_delete_volume,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name, 'size': 1}
+ self.assertRaises(exception.VolumeIsBusy,
+ self.driver.delete_volume,
+ volume)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'map_volume',
+ return_value=MAPPINGS[0])
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_iqn',
+ return_value=IQN)
+ def test_initialize_connection(self,
+ mock_find_iqn,
+ mock_map_volume,
+ mock_find_volume,
+ mock_create_server,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name}
+ connector = self.connector
+ data = self.driver.initialize_connection(volume, connector)
+ self.assertEqual(data['driver_volume_type'], 'iscsi')
+ # verify find_volume has been called and that is has been called twice
+ mock_find_volume.assert_any_call(12345, self.volume_name)
+ assert mock_find_volume.call_count == 2
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'map_volume',
+ return_value=MAPPINGS)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_iqn',
+ return_value=None)
+ def test_initialize_connection_no_iqn(self,
+ mock_find_iqn,
+ mock_map_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name}
+ connector = {}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_server',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'map_volume',
+ return_value=MAPPINGS)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_iqn',
+ return_value=None)
+ def test_initialize_connection_no_server(self,
+ mock_find_iqn,
+ mock_map_volume,
+ mock_find_volume,
+ mock_create_server,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name}
+ connector = {}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'map_volume',
+ return_value=MAPPINGS)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_iqn',
+ return_value=None)
+ def test_initialize_connection_vol_not_found(self,
+ mock_find_iqn,
+ mock_map_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = {}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.initialize_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=True)
+ def test_terminate_connection(self,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name}
+ connector = self.connector
+ self.driver.terminate_connection(volume, connector)
+ mock_unmap_volume.assert_called_once_with(self.VOLUME, self.SCSERVER)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=True)
+ def test_terminate_connection_no_server(self,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = {'initiator': ''}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=True)
+ def test_terminate_connection_no_volume(self,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = {'initiator': ''}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_server',
+ return_value=SCSERVER)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'unmap_volume',
+ return_value=False)
+ def test_terminate_connection_failure(self,
+ mock_unmap_volume,
+ mock_find_volume,
+ mock_find_server,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name}
+ connector = {'initiator': ''}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.terminate_connection,
+ volume,
+ connector)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_replay',
+ return_value='fake')
+ def test_create_snapshot(self,
+ mock_create_replay,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ snapshot = {'volume_id': self.volume_name,
+ 'id': self.volume_name}
+ self.driver.create_snapshot(snapshot)
+ self.assertEqual('available', snapshot['status'])
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_replay',
+ return_value=None)
+ def test_create_snapshot_no_volume(self,
+ mock_create_replay,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ snapshot = {'volume_id': self.volume_name,
+ 'id': self.volume_name}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_snapshot,
+ snapshot)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_replay',
+ return_value=None)
+ def test_create_snapshot_failure(self,
+ mock_create_replay,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ snapshot = {'volume_id': self.volume_name,
+ 'id': self.volume_name}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_snapshot,
+ snapshot)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_replay',
+ return_value='fake')
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_view_volume',
+ return_value=VOLUME)
+ def test_create_volume_from_snapshot(self,
+ mock_create_view_volume,
+ mock_find_replay,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': 'fake'}
+ snapshot = {'id': 'fake', 'volume_id': 'fake'}
+ self.driver.create_volume_from_snapshot(volume, snapshot)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_replay',
+ return_value='fake')
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_view_volume',
+ return_value=None)
+ def test_create_volume_from_snapshot_failed(self,
+ mock_create_view_volume,
+ mock_find_replay,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': 'fake'}
+ snapshot = {'id': 'fake', 'volume_id': 'fake'}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_volume_from_snapshot,
+ volume, snapshot)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_replay',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_view_volume',
+ return_value=VOLUME)
+ def test_create_volume_from_snapshot_no_replay(self,
+ mock_create_view_volume,
+ mock_find_replay,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': 'fake'}
+ snapshot = {'id': 'fake', 'volume_id': 'fake'}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_volume_from_snapshot,
+ volume, snapshot)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_replay',
+ return_value='fake')
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_cloned_volume',
+ return_value=VOLUME)
+ def test_create_cloned_volume(self,
+ mock_create_cloned_volume,
+ mock_find_volume,
+ mock_find_replay,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name + '_clone'}
+ src_vref = {'id': self.volume_name}
+ self.driver.create_cloned_volume(volume, src_vref)
+ mock_create_cloned_volume. \
+ assert_called_once_with(self.volume_name + '_clone',
+ self.configuration.dell_sc_volume_folder,
+ self.VOLUME)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_replay',
+ return_value='fake')
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'create_cloned_volume',
+ return_value=VOLUME)
+ def test_create_cloned_volume_no_volume(self,
+ mock_create_cloned_volume,
+ mock_find_volume,
+ mock_find_replay,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'id': self.volume_name + '_clone'}
+ src_vref = {'id': self.volume_name}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.create_cloned_volume,
+ volume, src_vref)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'delete_replay',
+ return_value=True)
+ def test_delete_snapshot(self,
+ mock_delete_replay,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ snapshot = {'volume_id': self.volume_name,
+ 'id': self.volume_name}
+ self.driver.delete_snapshot(snapshot)
+ mock_delete_replay.assert_called_once_with(
+ self.VOLUME, self.volume_name)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'delete_replay',
+ return_value=True)
+ def test_delete_snapshot_no_volume(self,
+ mock_delete_replay,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ snapshot = {'volume_id': self.volume_name,
+ 'id': self.volume_name}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.delete_snapshot,
+ snapshot)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ def test_ensure_export(self,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ context = {}
+ volume = {'id': self.VOLUME.get(u'name')}
+ self.driver.ensure_export(context, volume)
+ mock_find_volume.assert_called_once_with(
+ 12345, self.VOLUME.get(u'name'))
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ def test_ensure_export_failed(self,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ context = {}
+ volume = {'id': self.VOLUME.get(u'name')}
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.ensure_export,
+ context, volume)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ def test_ensure_export_no_volume(self,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ context = {}
+ volume = {'id': self.VOLUME.get(u'name')}
+ #self.driver.ensure_export(context, volume)
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.ensure_export,
+ context,
+ volume)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=VOLUME)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'expand_volume',
+ return_value=VOLUME)
+ def test_extend_volume(self,
+ mock_expand_volume,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name, 'size': 1}
+ new_size = 2
+ self.driver.extend_volume(volume, new_size)
+ mock_expand_volume.assert_called_once_with(self.VOLUME, new_size)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=12345)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_volume',
+ return_value=None)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'expand_volume',
+ return_value=None)
+ def test_extend_volume_no_volume(self,
+ mock_expand_volume,
+ mock_find_volume,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ volume = {'name': self.volume_name, 'size': 1}
+ new_size = 2
+ self.assertRaises(exception.VolumeBackendAPIException,
+ self.driver.extend_volume,
+ volume, new_size)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_storage_usage',
+ return_value={'availableSpace': 100, 'freeSpace': 50})
+ def test_update_volume_stats_with_refresh(self,
+ mock_get_storage_usage,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ stats = self.driver.get_volume_stats(True)
+ self.assertEqual(stats['storage_protocol'], 'iSCSI')
+ mock_get_storage_usage.called_once_with(64702)
+
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'find_sc',
+ return_value=64702)
+ @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+ 'get_storage_usage',
+ return_value={'availableSpace': 100, 'freeSpace': 50})
+ def test_get_volume_stats_no_refresh(self,
+ mock_get_storage_usage,
+ mock_find_sc,
+ mock_close_connection,
+ mock_open_connection,
+ mock_init):
+ stats = self.driver.get_volume_stats(False)
+ self.assertEqual(stats['storage_protocol'], 'iSCSI')
+ assert mock_get_storage_usage.called is False
--- /dev/null
+# Copyright 2014 Dell Inc.
+#
+# 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.
+'''Interface for interacting with the Dell Storage Center array.'''
+
+import json
+import os.path
+
+import requests
+
+from cinder import exception
+from cinder.i18n import _, _LE, _LI, _LW
+from cinder.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+
+class PayloadFilter(object):
+
+ '''PayloadFilter
+
+ Simple class for creating filters for interacting with the Dell
+ Storage API.
+
+ Note that this defaults to "AND" filter types.
+ '''
+
+ def __init__(self):
+ self.payload = {}
+ self.payload['filterType'] = 'AND'
+ self.payload['filters'] = []
+
+ def append(self, name, val, filtertype='Equals'):
+ if val is not None:
+ apifilter = {}
+ apifilter['attributeName'] = name
+ apifilter['attributeValue'] = val
+ apifilter['filterType'] = filtertype
+ self.payload['filters'].append(apifilter)
+
+
+class HttpClient(object):
+
+ '''HttpClient
+
+ Helper for making the REST calls.
+ '''
+
+ def __init__(self, host, port, user, password):
+ self.baseUrl = 'https://%s:%s/api/rest/' % (host, port)
+ self.session = requests.Session()
+ self.session.auth = (user, password)
+ self.header = {}
+ self.header['Content-Type'] = 'application/json; charset=utf-8'
+ self.header['x-dell-api-version'] = '1.5'
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.session.close()
+
+ def __formatUrl(self, url):
+ return '%s%s' % (self.baseUrl, url if url[0] != '/' else url[1:])
+
+ def get(self, url):
+ return self.session.get(
+ self.__formatUrl(url),
+ headers=self.header,
+ verify=False)
+
+ def post(self, url, payload):
+ return self.session.post(
+ self.__formatUrl(url),
+ data=json.dumps(payload,
+ ensure_ascii=False).encode('utf-8'),
+ headers=self.header,
+ verify=False)
+
+ def put(self, url, payload):
+ return self.session.put(
+ self.__formatUrl(url),
+ data=json.dumps(payload,
+ ensure_ascii=False).encode('utf-8'),
+ headers=self.header,
+ verify=False)
+
+ def delete(self, url):
+ return self.session.delete(
+ self.__formatUrl(url),
+ headers=self.header,
+ verify=False)
+
+
+class StorageCenterApiHelper(object):
+
+ '''StorageCenterApiHelper
+
+ Helper class for API access. Handles opening and closing the
+ connection to the Storage Center.
+ '''
+
+ def __init__(self, config):
+ self.config = config
+
+ def open_connection(self):
+ '''Open connection to Enterprise Manager.'''
+ connection = StorageCenterApi(self.config.san_ip,
+ self.config.dell_sc_api_port,
+ self.config.san_login,
+ self.config.san_password)
+ connection.open_connection()
+ return connection
+
+
+class StorageCenterApi(object):
+
+ '''StorageCenterApi
+
+ Handles calls to EnterpriseManager via the REST API interface.
+ '''
+
+ def __init__(self, host, port, user, password):
+ self.notes = 'Created by Dell Cinder Driver'
+ self.client = HttpClient(host,
+ port,
+ user,
+ password)
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close_connection()
+
+ def _path_to_array(self, path):
+ array = []
+ while True:
+ (path, tail) = os.path.split(path)
+ if tail == '':
+ array.reverse()
+ return array
+ array.append(tail)
+
+ def _first_result(self, blob):
+ return self._get_result(blob, None, None)
+
+ def _get_result(self, blob, attribute, value):
+ rsp = None
+ try:
+ content = blob.json()
+ # we can get a list or a dict or nothing
+ if isinstance(content, list):
+ for r in content:
+ if attribute is None or r.get(attribute) == value:
+ rsp = r
+ break
+ else:
+ if attribute is None or content.get(attribute) == value:
+ rsp = content
+ except AttributeError:
+ LOG.error(_LE('Invalid return blob: %s'),
+ blob)
+ if rsp is None:
+ LOG.debug('Unable to find result where %(attr)s is %(val)s',
+ {'attr': attribute,
+ 'val': value})
+ return rsp
+
+ def _get_json(self, blob):
+ try:
+ return blob.json()
+ except AttributeError:
+ LOG.error(_LE('Error invalid json: %s'),
+ blob)
+ return None
+
+ def _get_id(self, blob):
+ try:
+ if isinstance(blob, dict):
+ return blob.get('instanceId')
+ except AttributeError:
+ LOG.error(_LE('Invalid API object: %s'),
+ blob)
+ return None
+
+ def open_connection(self):
+ # Authenticate against EM
+ r = self.client.post('ApiConnection/Login',
+ {})
+ if r.status_code != 200:
+ LOG.error(_LE('Login error: %(c)d %(r)s'),
+ {'c': r.status_code,
+ 'r': r.reason})
+ raise exception.VolumeBackendAPIException(
+ _('Failed to connect to Enterprise Manager'))
+
+ def close_connection(self):
+ r = self.client.post('ApiConnection/Logout',
+ {})
+ if r.status_code != 204:
+ LOG.warning(_LW('Logout error: %(c)d %(r)s'),
+ {'c': r.status_code,
+ 'r': r.reason})
+ self.client = None
+
+ def find_sc(self, ssn):
+ '''This is really just a check that the sc is there and being managed by
+ EM.
+ '''
+ r = self.client.get('StorageCenter/StorageCenter')
+ result = self._get_result(r,
+ 'scSerialNumber',
+ ssn)
+ if result is None:
+ LOG.error(_LE('Failed to find %(s)s. Result %(r)s'),
+ {'s': ssn,
+ 'r': r})
+ raise exception.VolumeBackendAPIException(
+ _('Failed to find Storage Center'))
+
+ return self._get_id(result)
+
+ # volume functions
+
+ def _create_folder(self, url, ssn, parent, folder):
+ '''This is generic to server and volume folders.
+ '''
+ f = None
+ payload = {}
+ payload['Name'] = folder
+ payload['StorageCenter'] = ssn
+ if parent != '':
+ payload['Parent'] = parent
+ payload['Notes'] = self.notes
+
+ r = self.client.post(url,
+ payload)
+ if r.status_code != 201:
+ LOG.debug('%(u)s error: %(c)d %(r)s',
+ {'u': url,
+ 'c': r.status_code,
+ 'r': r.reason})
+ else:
+ f = self._first_result(r)
+ return f
+
+ def _create_folder_path(self, url, ssn, foldername):
+ '''This is generic to server and volume folders.
+ '''
+ path = self._path_to_array(foldername)
+ folderpath = ''
+ instanceId = ''
+ # technically the first folder is the root so that is already created.
+ found = True
+ f = None
+ for folder in path:
+ folderpath = folderpath + folder
+ # if the last was found see if this part of the path exists too
+ if found:
+ listurl = url + '/GetList'
+ f = self._find_folder(listurl,
+ ssn,
+ folderpath)
+ if f is None:
+ found = False
+ # we didn't find it so create it
+ if found is False:
+ f = self._create_folder(url,
+ ssn,
+ instanceId,
+ folder)
+ # if we haven't found a folder or created it then leave
+ if f is None:
+ LOG.error(_LE('Unable to create folder path %s'),
+ folderpath)
+ break
+ # next part of the path will need this
+ instanceId = self._get_id(f)
+ folderpath = folderpath + '/'
+ return f
+
+ def _find_folder(self, url, ssn, foldername):
+ '''Most of the time the folder will already have been created so
+ we look for the end folder and check that the rest of the path is
+ right.
+
+ This is generic to server and volume folders.
+ '''
+ pf = PayloadFilter()
+ pf.append('scSerialNumber', ssn)
+ basename = os.path.basename(foldername)
+ pf.append('Name', basename)
+ # if we have any kind of path we add '/' to match the storage
+ # center's convention and throw it into the filters.
+ folderpath = os.path.dirname(foldername)
+ if folderpath != '':
+ folderpath += '/'
+ pf.append('folderPath', folderpath)
+ folder = None
+ r = self.client.post(url,
+ pf.payload)
+ if r.status_code == 200:
+ folder = self._get_result(r,
+ 'folderPath',
+ folderpath)
+ else:
+ LOG.debug('%(u)s error: %(c)d %(r)s',
+ {'u': url,
+ 'c': r.status_code,
+ 'r': r.reason})
+ return folder
+
+ def _create_volume_folder_path(self, ssn, foldername):
+ return self._create_folder_path('StorageCenter/ScVolumeFolder',
+ ssn,
+ foldername)
+
+ def _find_volume_folder(self, ssn, foldername):
+ return self._find_folder('StorageCenter/ScVolumeFolder/GetList',
+ ssn,
+ foldername)
+
+ def _init_volume(self, scvolume):
+ '''Maps the volume to a random server and immediately unmaps
+ it. This initializes the volume.
+
+ Don't wig out if this fails.
+ '''
+ pf = PayloadFilter()
+ pf.append('scSerialNumber', scvolume.get('scSerialNumber'), 'Equals')
+ r = self.client.post('StorageCenter/ScServer/GetList', pf.payload)
+ if r.status_code == 200:
+ scservers = self._get_json(r)
+ # Sort through the servers looking for one with connectivity.
+ for scserver in scservers:
+ if scserver.get('status', '').lower() != 'down':
+ # map to actually create the volume
+ self.map_volume(scvolume,
+ scserver)
+ self.unmap_volume(scvolume,
+ scserver)
+
+ def create_volume(self, name, size, ssn, volfolder):
+ '''This creates a new volume on the storage center. It
+ will create it in volfolder. If volfolder does not
+ exist it will create it. If it cannot create volfolder
+ the volume will be created in the root.
+ '''
+ scvolume = None
+ # find our folder
+ LOG.debug('Create Volume %(name)s %(ssn)s %(folder)s',
+ {'name': name,
+ 'ssn': ssn,
+ 'folder': volfolder})
+ folder = self._find_volume_folder(ssn,
+ volfolder)
+
+ # doesn't exist? make it
+ if folder is None:
+ folder = self._create_volume_folder_path(ssn,
+ volfolder)
+
+ # if we actually have a place to put our volume create it
+ if folder is None:
+ LOG.error(_LE('Unable to create folder %s'),
+ volfolder)
+
+ # Create the volume
+ payload = {}
+ payload['Name'] = name
+ payload['Notes'] = self.notes
+ payload['Size'] = '%d GB' % size
+ payload['StorageCenter'] = ssn
+ if folder is not None:
+ payload['VolumeFolder'] = self._get_id(folder)
+ r = self.client.post('StorageCenter/ScVolume',
+ payload)
+ if r.status_code == 201:
+ scvolume = self._get_json(r)
+ else:
+ LOG.error(_LE('ScVolume create error %(name)s: %(c)d %(r)s'),
+ {'name': name,
+ 'c': r.status_code,
+ 'r': r.reason})
+ if scvolume is not None:
+ LOG.info(_LI('Created volume %(index)d: %(name)s'),
+ {'index': scvolume['index'],
+ 'name': scvolume['name']})
+ return scvolume
+
+ def find_volume(self, ssn, name=None, instanceid=None):
+ '''search ssn for volume of name and/or instance id
+ '''
+ LOG.debug('finding volume %(sn)s : %(name)s : %(id)s',
+ {'sn': ssn,
+ 'name': name,
+ 'id': instanceid})
+ if name is None and instanceid is None:
+ return None
+ pf = PayloadFilter()
+ # we need at least a name and or an instance id. If we have
+ # that we can find a volume.
+ pf.append('scSerialNumber', ssn)
+ pf.append('Name', name)
+ pf.append('instanceId', instanceid)
+ r = self.client.post('StorageCenter/ScVolume/GetList',
+ pf.payload)
+ if r.status_code != 200:
+ LOG.debug('ScVolume GetList error %(i)s: %(c)d %(r)s',
+ {'i': instanceid,
+ 'c': r.status_code,
+ 'r': r.reason})
+ return self._first_result(r)
+
+ def delete_volume(self, ssn, name):
+ # find our volume
+ vol = self.find_volume(ssn, name, None)
+ if vol is not None:
+ r = self.client.delete('StorageCenter/ScVolume/%s'
+ % self._get_id(vol))
+ if r.status_code != 200:
+ raise exception.VolumeBackendAPIException(
+ _('Error deleting volume %(ssn)s: %(sn)s: %(c)d %(r)s') %
+ {'ssn': ssn,
+ 'sn': name,
+ 'c': r.status_code,
+ 'r': r.reason})
+ # json return should be true or false
+ return r.json()
+ LOG.warning(_LW('delete_volume: unable to find volume %s'),
+ name)
+ # If we can't find the volume then it is effectively gone.
+ return True
+
+ def _create_server_folder_path(self, ssn, foldername):
+ return self._create_folder_path('StorageCenter/ScServerFolder',
+ ssn,
+ foldername)
+
+ def _find_server_folder(self, ssn, foldername):
+ return self._find_folder('StorageCenter/ScServerFolder/GetList',
+ ssn,
+ foldername)
+
+ def _add_hba(self, scserver, wwnoriscsiname, isfc=False):
+ '''Adds an HBA to the scserver. The HBA will be added
+ even if it has not been seen by the storage center.
+ '''
+ payload = {}
+ if isfc is True:
+ payload['HbaPortType'] = 'FibreChannel'
+ else:
+ payload['HbaPortType'] = 'Iscsi'
+ payload['WwnOrIscsiName'] = wwnoriscsiname
+ payload['AllowManual'] = True
+ r = self.client.post('StorageCenter/ScPhysicalServer/%s/AddHba'
+ % self._get_id(scserver),
+ payload)
+ if r.status_code != 200:
+ LOG.error(_LE('AddHba error: %(i)s to %(s)s : %(c)d %(r)s'),
+ {'i': wwnoriscsiname,
+ 's': scserver['name'],
+ 'c': r.status_code,
+ 'r': r.reason})
+ return False
+ return True
+
+ # we do not know that we are red hat linux 6.x but that works
+ # best for red hat and ubuntu. So, there.
+ def _find_serveros(self, ssn, osname='Red Hat Linux 6.x'):
+ '''Returns the serveros instance id of the specified osname.
+ Required to create a server.
+ '''
+ pf = PayloadFilter()
+ pf.append('scSerialNumber', ssn)
+ r = self.client.post('StorageCenter/ScServerOperatingSystem/GetList',
+ pf.payload)
+ if r.status_code == 200:
+ oslist = r.json()
+ for srvos in oslist:
+ name = srvos.get('name', 'nope')
+ if name.lower() == osname.lower():
+ # found it return the id
+ return self._get_id(srvos)
+
+ LOG.warning(_LW('ScServerOperatingSystem GetList return: %(c)d %(r)s'),
+ {'c': r.status_code,
+ 'r': r.reason})
+ return None
+
+ def create_server_multiple_hbas(self, ssn, foldername, wwns):
+ '''Same as create_server except it can take a list of hbas. hbas
+ can be wwns or iqns.
+ '''
+ # add hbas
+ scserver = None
+ # our instance names
+ for wwn in wwns:
+ if scserver is None:
+ # Use the fist wwn to create the server.
+ scserver = self.create_server(ssn,
+ foldername,
+ wwn,
+ True)
+ else:
+ # add the wwn to our server
+ self._add_hba(scserver,
+ wwn,
+ True)
+ return scserver
+
+ def create_server(self, ssn, foldername, wwnoriscsiname, isfc=False):
+ '''creates a server on the the storage center ssn. Adds the first
+ HBA to it.
+ '''
+ scserver = None
+ payload = {}
+ payload['Name'] = 'Server_' + wwnoriscsiname
+ payload['StorageCenter'] = ssn
+ payload['Notes'] = self.notes
+ # we pick Red Hat Linux 6.x because it supports multipath and
+ # will attach luns to paths as they are found.
+ scserveros = self._find_serveros(ssn, 'Red Hat Linux 6.x')
+ if scserveros is not None:
+ payload['OperatingSystem'] = scserveros
+
+ # find our folder or make it
+ folder = self._find_server_folder(ssn,
+ foldername)
+ if folder is None:
+ folder = self._create_server_folder_path(ssn,
+ foldername)
+
+ # at this point it doesn't matter if the folder was created or not
+ # we just attempt to create the server. let it be in the root if
+ # the folder creation fails.
+ if folder is not None:
+ payload['ServerFolder'] = self._get_id(folder)
+
+ # create our server
+ r = self.client.post('StorageCenter/ScPhysicalServer',
+ payload)
+ if r.status_code != 201:
+ LOG.error(_LE('ScPhysicalServer create error: %(i)s: %(c)d %(r)s'),
+ {'i': wwnoriscsiname,
+ 'c': r.status_code,
+ 'r': r.reason})
+ else:
+ # server was created
+ scserver = self._first_result(r)
+
+ # add hba to our server
+ if scserver is not None:
+ if not self._add_hba(scserver,
+ wwnoriscsiname,
+ isfc):
+ LOG.error(_LE('Error adding HBA to server'))
+ # Can't have a server without an HBA
+ self._delete_server(scserver)
+ scserver = None
+ # success or failure is determined by the caller
+ return scserver
+
+ def find_server(self, ssn, instance_name):
+ '''Hunts for a server by looking for an HBA with the server's IQN
+ or wwn.
+
+ If found, the server the HBA is attached to, if any, is returned.
+ '''
+ scserver = None
+ # we search for our server by first finding our HBA
+ pf = PayloadFilter()
+ pf.append('scSerialNumber', ssn)
+ pf.append('instanceName', instance_name)
+ r = self.client.post('StorageCenter/ScServerHba/GetList',
+ pf.payload)
+ if r.status_code != 200:
+ LOG.debug('ScServerHba error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ else:
+ hba = self._first_result(r)
+ # once created hbas stay in the system. So it isn't enough
+ # that we found one it actually has to be attached to a
+ # server.
+ if hba is not None and hba.get('server') is not None:
+ pf = PayloadFilter()
+ pf.append('scSerialNumber', ssn)
+ pf.append('instanceId', self._get_id(hba['server']))
+ r = self.client.post('StorageCenter/ScServer/GetList',
+ pf.payload)
+ if r.status_code != 200:
+ LOG.error(_LE('ScServer error: %(c)d %(r)s'),
+ {'c': r.status_code,
+ 'r': r.reason})
+ else:
+ scserver = self._first_result(r)
+ if scserver is None:
+ LOG.debug('Server (%s) not found.',
+ instance_name)
+ return scserver
+
+ def _find_domain(self, cportid, domainip):
+ '''Returns the fault domain which a given controller port can
+ be seen by the server
+ '''
+ r = self.client.get('StorageCenter/ScControllerPort/%s/FaultDomainList'
+ % cportid)
+ if r.status_code == 200:
+ domains = r.json()
+ # wiffle through the domains looking for our
+ # configured ip
+ for domain in domains:
+ # if this is us we return the port
+ if domain.get('wellKnownIpAddress') == domainip:
+ return domain
+ else:
+ LOG.debug('FaultDomainList error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ LOG.error(_LE('Error getting FaultDomainList'))
+ return None
+
+ def _find_fc_initiators(self, scserver):
+ '''_find_fc_initiators
+
+ returns the server's fc HBA's wwns
+ '''
+ initiators = []
+ r = self.client.get('StorageCenter/ScServer/%s/HbaList'
+ % self._get_id(scserver))
+ if r.status_code == 200:
+ hbas = r.json()
+ for hba in hbas:
+ wwn = hba.get('instanceName')
+ if hba.get('portType') == 'FibreChannel' and\
+ wwn is not None:
+ initiators.append(wwn)
+ else:
+ LOG.debug('HbaList error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ LOG.error(_LE('Unable to find FC intitiators'))
+ return initiators
+
+ def get_volume_count(self, scserver):
+ r = self.client.get('StorageCenter/ScServer/%s/MappingList'
+ % self._get_id(scserver))
+ if r.status_code == 200:
+ mappings = r.json()
+ return len(mappings)
+ # Panic mildly but do not return 0.
+ return -1
+
+ def _find_mappings(self, scvolume):
+ '''find mappings
+
+ returns the volume's mappings
+ '''
+ mappings = []
+ if scvolume.get('active', False):
+ r = self.client.get('StorageCenter/ScVolume/%s/MappingList'
+ % self._get_id(scvolume))
+ if r.status_code == 200:
+ mappings = r.json()
+ else:
+ LOG.debug('MappingList error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ LOG.error(_LE('Unable to find volume mappings: %s'),
+ scvolume.get('name'))
+ else:
+ LOG.error(_LE('_find_mappings: volume is not active'))
+ return mappings
+
+ def _find_controller_port(self, cportid):
+ '''_find_controller_port
+
+ returns the controller port dict
+ '''
+ controllerport = None
+ r = self.client.get('StorageCenter/ScControllerPort/%s'
+ % cportid)
+ if r.status_code == 200:
+ controllerport = self._first_result(r)
+ else:
+ LOG.debug('ScControllerPort error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ LOG.error(_LE('Unable to find controller port: %s'),
+ cportid)
+ return controllerport
+
+ def find_wwns(self, scvolume, scserver):
+ '''returns the lun and wwns of the mapped volume'''
+ # our returnables
+ lun = None # our lun. We return the first lun.
+ wwns = [] # list of targets
+ itmap = {} # dict of initiators and the associated targets
+
+ # make sure we know our server's initiators. Only return
+ # mappings that contain HBA for this server.
+ initiators = self._find_fc_initiators(scserver)
+ # get our volume mappings
+ mappings = self._find_mappings(scvolume)
+ if len(mappings) > 0:
+ # we check each of our mappings. We want to return
+ # the mapping we have been configured to use.
+ for mapping in mappings:
+ # find the controller port for this mapping
+ cport = mapping.get('controllerPort')
+ controllerport = self._find_controller_port(
+ self._get_id(cport))
+ if controllerport is not None:
+ wwn = controllerport.get('WWN')
+ serverhba = mapping.get('serverHba')
+ if serverhba is not None:
+ hbaname = serverhba.get('instanceName')
+ if hbaname in initiators:
+ if itmap.get(hbaname) is None:
+ itmap[hbaname] = []
+ itmap[hbaname].append(wwn)
+ wwns.append(wwn)
+
+ mappinglun = mapping.get('lun')
+ if lun is None:
+ lun = mappinglun
+ elif lun != mappinglun:
+ LOG.warning(_LW('Inconsistent Luns.'))
+ else:
+ LOG.error(_LE('Find_wwns: Volume appears unmapped'))
+ LOG.debug(lun)
+ LOG.debug(wwns)
+ LOG.debug(itmap)
+ # TODO(tom_swanson): if we have nothing to return raise an exception
+ # here. We can't do anything with an unmapped volume. We shouldn't
+ # pretend we succeeded.
+ return lun, wwns, itmap
+
+ def find_iqn(self, scvolume, ip):
+ '''find_iqn
+
+ returns the iqn of the port pointed to by the openstack compute
+ node. This is to make sure that the compute node looks for the
+ volume on the active controller.
+ '''
+ iqn = None
+ # get our volume mappings
+ mappings = self._find_mappings(scvolume)
+ if len(mappings) > 0:
+ # we check each of our mappings. We want to return
+ # the mapping we have been configured to use.
+ for mapping in mappings:
+ # find the controller port for this mapping
+ cport = mapping.get('controllerPort')
+ cportid = self._get_id(cport)
+ domain = self._find_domain(cportid,
+ ip)
+ if domain is not None:
+ controllerport = self._find_controller_port(cportid)
+ if controllerport is not None:
+ iqn = controllerport.get('iscsiName')
+ break
+ else:
+ LOG.error(_LE('Find_iqn: Volume appears unmapped'))
+ # TODO(tom_swanson): if we have nothing to return raise an exception
+ # here. We can't do anything with an unmapped volume. We shouldn't
+ # pretend we succeeded.
+ return iqn
+
+ def map_volume(self, scvolume, scserver):
+ '''map_volume
+
+ The check for server existence is elsewhere; does not create the
+ server.
+ '''
+ # make sure we have what we think we have
+ serverid = self._get_id(scserver)
+ volumeid = self._get_id(scvolume)
+ if serverid is not None and volumeid is not None:
+ payload = {}
+ payload['server'] = serverid
+ advanced = {}
+ advanced['MapToDownServerHbas'] = True
+ payload['Advanced'] = advanced
+ r = self.client.post('StorageCenter/ScVolume/%s/MapToServer'
+ % volumeid,
+ payload)
+ if r.status_code == 200:
+ # we just return our mapping
+ return self._first_result(r)
+ # Should not be here.
+ LOG.debug('MapToServer error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ # error out
+ LOG.error(_LE('Unable to map %(vol)s to %(srv)s'),
+ {'vol': scvolume['name'],
+ 'srv': scserver['name']})
+ return None
+
+ def unmap_volume(self, scvolume, scserver):
+ '''unmap_volume
+
+ deletes all mappings to a server, not just the ones on the path
+ defined in cinder.conf.
+ '''
+ rtn = True
+ serverid = self._get_id(scserver)
+ volumeid = self._get_id(scvolume)
+ if serverid is not None and volumeid is not None:
+ r = self.client.get('StorageCenter/ScVolume/%s/MappingProfileList'
+ % volumeid)
+ if r.status_code == 200:
+ profiles = self._get_json(r)
+ for profile in profiles:
+ prosrv = profile.get('server')
+ if prosrv is not None and self._get_id(prosrv) == serverid:
+ r = self.client.delete(
+ 'StorageCenter/ScMappingProfile/%s'
+ % self._get_id(profile))
+ if (r.status_code != 200 or r.ok is False):
+ LOG.debug('ScMappingProfile error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ LOG.error(_LE('Unable to unmap Volume %s'),
+ volumeid)
+ # 1 failed unmap is as good as 100.
+ # Fail it and leave
+ rtn = False
+ break
+ LOG.debug('Volume %(v)s unmapped from %(s)s',
+ {'v': volumeid,
+ 's': serverid})
+ else:
+ LOG.debug('MappingProfileList error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ rtn = False
+ return rtn
+
+ def get_storage_usage(self, ssn):
+ '''get_storage_usage'''
+ storageusage = None
+ if ssn is not None:
+ r = self.client.get('StorageCenter/StorageCenter/%s/StorageUsage'
+ % ssn)
+ if r.status_code == 200:
+ storageusage = self._get_json(r)
+ else:
+ LOG.debug('StorageUsage error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+
+ return storageusage
+
+ def create_replay(self, scvolume, replayid, expire):
+ '''create_replay
+
+ expire is in minutes.
+ one could snap a volume before it has been activated, so activate
+ by mapping and unmapping to a random server and let them. This
+ should be a fail but the Tempest tests require it.
+ '''
+ replay = None
+ if scvolume is not None:
+ if (scvolume.get('active') is not True or
+ scvolume.get('replayAllowed') is not True):
+ self._init_volume(scvolume)
+ payload = {}
+ payload['description'] = replayid
+ payload['expireTime'] = expire
+ if expire == 0:
+ payload['doNotExpire'] = True
+ r = self.client.post('StorageCenter/ScVolume/%s/CreateReplay'
+ % self._get_id(scvolume),
+ payload)
+ if r.status_code != 200:
+ LOG.debug('CreateReplay error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ LOG.error(_LE('Error creating replay.'))
+ else:
+ replay = self._first_result(r)
+ return replay
+
+ def find_replay(self, scvolume, replayid):
+ '''find_replay
+
+ searches for the replay by replayid which we store in the
+ replay's description attribute
+ '''
+ replay = None
+ r = self.client.get('StorageCenter/ScVolume/%s/ReplayList'
+ % self._get_id(scvolume))
+ try:
+ content = r.json()
+ # this will be a list. If it isn't bail
+ if isinstance(content, list):
+ for r in content:
+ # The only place to save our information with the public
+ # api is the description field which isn't quite long
+ # enough. So we check that our description is pretty much
+ # the max length and we compare that to the start of
+ # the snapshot id.
+ description = r.get('description')
+ if len(description) >= 30 and \
+ replayid.startswith(description) is True and \
+ r.get('markedForExpiration') is not True:
+ replay = r
+ break
+ except Exception:
+ LOG.error(_LE('Invalid ReplayList return: %s'),
+ r)
+
+ if replay is None:
+ LOG.debug('Unable to find snapshot %s',
+ replayid)
+
+ return replay
+
+ def delete_replay(self, scvolume, replayid):
+ '''delete_replay
+
+ hunts down a replay by replayid string and expires it.
+
+ once marked for expiration we do not return the replay as
+ a snapshot.
+ '''
+ replay = self.find_replay(scvolume,
+ replayid)
+ if replay is not None:
+ r = self.client.post('StorageCenter/ScReplay/%s/Expire'
+ % self._get_id(replay),
+ {})
+ if r.status_code != 204:
+ LOG.debug('ScReplay Expire error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+ return False
+ # We either couldn't find it or expired it.
+ return True
+
+ def create_view_volume(self, volname, volfolder, screplay):
+ '''create_view_volume
+
+ creates a new volume named volname in the folder
+ volfolder from the screplay.
+ '''
+ # find our ssn and get our folder
+ ssn = screplay.get('scSerialNumber')
+ folder = self._find_volume_folder(ssn,
+ volfolder)
+
+ # doesn't exist? make it
+ if folder is None:
+ folder = self._create_volume_folder_path(ssn,
+ volfolder)
+
+ # payload is just the volume name and folder if we have one.
+ payload = {}
+ payload['Name'] = volname
+ payload['Notes'] = self.notes
+ if folder is not None:
+ payload['VolumeFolder'] = self._get_id(folder)
+ r = self.client.post('StorageCenter/ScReplay/%s/CreateView'
+ % self._get_id(screplay),
+ payload)
+ volume = None
+ if r.status_code == 200:
+ volume = self._first_result(r)
+ else:
+ LOG.debug('ScReplay CreateView error: %(c)d %(r)s',
+ {'c': r.status_code,
+ 'r': r.reason})
+
+ if volume is None:
+ LOG.error(_LE('Unable to create volume %s from replay'),
+ volname)
+
+ return volume
+
+ def create_cloned_volume(self, volumename, volumefolder, scvolume):
+ '''create_cloned_volume
+
+ creates a temporary replay and then creates a
+ view volume from that.
+ '''
+ clone = None
+ replay = self.create_replay(scvolume,
+ 'Cinder Clone Replay',
+ 60)
+ if replay is not None:
+ clone = self.create_view_volume(volumename,
+ volumefolder,
+ replay)
+ else:
+ LOG.error(_LE('Error: unable to snap replay'))
+ return clone
+
+ def expand_volume(self, scvolume, newsize):
+ '''expand_volume'''
+ payload = {}
+ payload['NewSize'] = '%d GB' % newsize
+ r = self.client.post('StorageCenter/ScVolume/%s/ExpandToSize'
+ % self._get_id(scvolume),
+ payload)
+ if r.status_code == 200:
+ vol = self._get_json(r)
+ else:
+ LOG.error(_LE('Error expanding volume %(n)s: %(c)d %(r)s'),
+ {'n': scvolume['name'],
+ 'c': r.status_code,
+ 'r': r.reason})
+ if vol is not None:
+ LOG.debug('Volume expanded: %(i)s %(s)s',
+ {'i': vol['index'],
+ 's': vol['configuredSize']})
+ return vol
+
+ def _delete_server(self, scserver):
+ '''_delete_server
+
+ Just give it a shot. If it fails it doesn't matter to cinder.
+ '''
+ if scserver.get('deleteAllowed') is True:
+ r = self.client.delete('StorageCenter/ScServer/%s'
+ % self._get_id(scserver))
+ LOG.debug('ScServer %(i)s delete return: %(c)d %(r)s',
+ {'i': self._get_id(scserver),
+ 'c': r.status_code,
+ 'r': r.reason})
+ else:
+ LOG.debug('_delete_server: deleteAllowed is False.')
--- /dev/null
+# Copyright 2014 Dell Inc.
+#
+# 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.
+
+from oslo.config import cfg
+from oslo.utils import excutils
+
+from cinder import exception
+from cinder.i18n import _, _LE, _LW
+from cinder.openstack.common import log as logging
+from cinder.volume.drivers.dell import dell_storagecenter_api
+from cinder.volume.drivers.san import san
+
+
+common_opts = [
+ cfg.IntOpt('dell_sc_ssn',
+ default=64702,
+ help='Storage Center System Serial Number'),
+ cfg.IntOpt('dell_sc_api_port',
+ default=3033,
+ help='Dell API port'),
+ cfg.StrOpt('dell_sc_server_folder',
+ default='openstack',
+ help='Name of the server folder to use on the Storage Center'),
+ cfg.StrOpt('dell_sc_volume_folder',
+ default='openstack',
+ help='Name of the volume folder to use on the Storage Center')
+]
+
+LOG = logging.getLogger(__name__)
+
+CONF = cfg.CONF
+CONF.register_opts(common_opts)
+
+
+class DellCommonDriver(san.SanDriver):
+
+ def __init__(self, *args, **kwargs):
+ super(DellCommonDriver, self).__init__(*args, **kwargs)
+ self.configuration.append_config_values(common_opts)
+ self.backend_name =\
+ self.configuration.safe_get('volume_backend_name') or 'Dell'
+
+ def _bytes_to_gb(self, spacestring):
+ '''Space is returned in a string like ...
+ 7.38197504E8 Bytes
+ Need to split that apart and convert to GB.
+
+ returns gbs in int form
+ '''
+ try:
+ n = spacestring.split(' ', 1)
+ fgbs = float(n[0]) / 1073741824.0
+ igbs = int(fgbs)
+ return igbs
+ except Exception:
+ # If any of that blew up it isn't in the format we
+ # thought so eat our error and return None
+ return None
+
+ def do_setup(self, context):
+ '''One time driver setup.
+
+ Called once by the manager after the driver is loaded.
+ Sets up clients, check licenses, sets up protocol
+ specific helpers.
+ '''
+ self._client = dell_storagecenter_api.StorageCenterApiHelper(
+ self.configuration)
+
+ def check_for_setup_error(self):
+ '''Validates the configuration information.'''
+ with self._client.open_connection() as api:
+ ssn = self.configuration.safe_get('dell_sc_ssn')
+ api.find_sc(ssn)
+
+ def create_volume(self, volume):
+ '''Create a volume.'''
+ LOG.debug('create_volume')
+ scvolume = None
+ with self._client.open_connection() as api:
+ try:
+ # we use id as our name as it s unique
+ volume_name = volume['id']
+ volume_size = volume['size']
+ volume_folder = self.configuration.dell_sc_volume_folder
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ LOG.debug('cv: %(name)s : %(size)s : %(ssn)s',
+ {'name': volume_name,
+ 'size': volume_size,
+ 'ssn': ssn})
+ if ssn is not None:
+ scvolume = api.create_volume(volume_name,
+ volume_size,
+ ssn,
+ volume_folder)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to create volume %s'),
+ volume['name'])
+ if scvolume is None:
+ raise exception.VolumeBackendAPIException(
+ _('unable to create volume'))
+
+ def delete_volume(self, volume):
+ deleted = False
+ # we use id as our name as it s unique
+ volume_name = volume.get('id')
+ with self._client.open_connection() as api:
+ try:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ if ssn is not None:
+ deleted = api.delete_volume(ssn,
+ volume_name)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to delete volume %s'),
+ volume_name)
+
+ # if there was an error we will have raised an
+ # exception. If it failed to delete it is because
+ # the conditions to delete a volume were not met.
+ if deleted is False:
+ raise exception.VolumeIsBusy(volume_name=volume_name)
+
+ def create_snapshot(self, snapshot):
+ '''Create snapshot'''
+ # our volume name is the volume id
+ volume_name = snapshot.get('volume_id')
+ snapshot_id = snapshot.get('id')
+ with self._client.open_connection() as api:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ if ssn is not None:
+ scvolume = api.find_volume(ssn,
+ volume_name)
+ if scvolume is not None:
+ if api.create_replay(scvolume,
+ snapshot_id,
+ 0) is not None:
+ snapshot['status'] = 'available'
+ return
+ else:
+ LOG.warning(_LW('Unable to locate volume:%s'),
+ volume_name)
+
+ snapshot['status'] = 'error_creating'
+ raise exception.VolumeBackendAPIException(
+ _('Failed to create snapshot %s') %
+ snapshot_id)
+
+ def create_volume_from_snapshot(self, volume, snapshot):
+ '''Create new volume from other volume's snapshot on appliance.'''
+ scvolume = None
+ src_volume_name = snapshot.get('volume_id')
+ snapshot_id = snapshot.get('id')
+ volume_name = volume.get('id')
+ with self._client.open_connection() as api:
+ try:
+ volume_folder = self.configuration.dell_sc_volume_folder
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ srcvol = api.find_volume(ssn,
+ src_volume_name)
+ if srcvol is not None:
+ replay = api.find_replay(srcvol,
+ snapshot_id)
+ if replay is not None:
+ volume_name = volume.get('id')
+ scvolume = api.create_view_volume(volume_name,
+ volume_folder,
+ replay)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to create volume %s'),
+ volume_name)
+ if scvolume is not None:
+ LOG.debug('Volume %(n)s created from %(s)s',
+ {'n': volume_name,
+ 's': snapshot_id})
+ else:
+ raise exception.VolumeBackendAPIException(
+ _('Failed to create volume %s') % volume_name)
+
+ def create_cloned_volume(self, volume, src_vref):
+ '''Creates a clone of the specified volume.'''
+ LOG.debug('create_cloned_volume')
+ scvolume = None
+ src_volume_name = src_vref.get('id')
+ volume_name = volume.get('id')
+ with self._client.open_connection() as api:
+ try:
+ volume_folder = self.configuration.dell_sc_volume_folder
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ srcvol = api.find_volume(ssn,
+ src_volume_name)
+ if srcvol is not None:
+ scvolume = api.create_cloned_volume(volume_name,
+ volume_folder,
+ srcvol)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to create volume %s'),
+ volume_name)
+ if scvolume is not None:
+ LOG.debug('Volume %(n)s cloned from %(s)s',
+ {'n': volume_name,
+ 's': src_volume_name})
+ else:
+ raise exception.VolumeBackendAPIException(
+ _('Failed to create volume %s') % volume_name)
+
+ def delete_snapshot(self, snapshot):
+ '''delete_snapshot'''
+ volume_name = snapshot.get('volume_id')
+ snapshot_id = snapshot.get('id')
+ with self._client.open_connection() as api:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ if ssn is not None:
+ scvolume = api.find_volume(ssn,
+ volume_name)
+ if scvolume is not None:
+ if api.delete_replay(scvolume,
+ snapshot_id):
+ return
+ # if we are here things went poorly.
+ snapshot['status'] = 'error_deleting'
+ raise exception.VolumeBackendAPIException(
+ _('Failed to delete snapshot %s') % snapshot_id)
+
+ def create_export(self, context, volume):
+ '''Create an export of a volume.
+
+ The volume exists on creation and will be visible on
+ initialize connection. So nothing to do here.
+ '''
+ pass
+
+ def ensure_export(self, context, volume):
+ '''Ensure an export of a volume.
+
+ Per the eqlx driver we just make sure that the volume actually
+ exists where we think it does.
+ '''
+ scvolume = None
+ volume_name = volume.get('id')
+ with self._client.open_connection() as api:
+ try:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ if ssn is not None:
+ scvolume = api.find_volume(ssn,
+ volume_name)
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to ensure export of volume %s'),
+ volume_name)
+ if scvolume is None:
+ raise exception.VolumeBackendAPIException(
+ _('unable to find volume %s') % volume_name)
+
+ def remove_export(self, context, volume):
+ '''Remove an export of a volume.
+
+ We do nothing here to match the nothing we do in create export. Again
+ we do everything in initialize and terminate connection.
+ '''
+ pass
+
+ def extend_volume(self, volume, new_size):
+ '''Extend the size of the volume.'''
+ volume_name = volume.get('id')
+ if volume is not None:
+ with self._client.open_connection() as api:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ if ssn is not None:
+ scvolume = api.find_volume(ssn,
+ volume_name)
+ if api.expand_volume(scvolume, new_size) is not None:
+ return
+ # If we are here nothing good happened.
+ raise exception.VolumeBackendAPIException(
+ _('Unable to extend volume %s') % volume_name)
+
+ def get_volume_stats(self, refresh=False):
+ '''Get volume status.
+
+ If 'refresh' is True, run update the stats first.
+ '''
+ if refresh:
+ self._update_volume_stats()
+
+ return self._stats
+
+ def _update_volume_stats(self):
+ '''Retrieve stats info from volume group.'''
+ with self._client.open_connection() as api:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ storageusage = api.get_storage_usage(ssn)
+
+ # all of this is basically static for now
+ data = {}
+ data['volume_backend_name'] = self.backend_name
+ data['vendor_name'] = 'Dell'
+ data['driver_version'] = self.VERSION
+ data['storage_protocol'] = 'iSCSI'
+ data['reserved_percentage'] = 0
+ # in theory if storageusage is None then we should have
+ # blown up getting it. If not just report inifinite.
+ if storageusage is not None:
+ totalcapacity = storageusage.get('availableSpace')
+ totalcapacitygb = self._bytes_to_gb(totalcapacity)
+ data['total_capacity_gb'] = totalcapacitygb
+ freespace = storageusage.get('freeSpace')
+ freespacegb = self._bytes_to_gb(freespace)
+ data['free_capacity_gb'] = freespacegb
+ if data.get('total_capacity_gb') is None:
+ data['total_capacity_gb'] = 'unavailable'
+ if data.get('free_capacity_gb') is None:
+ data['free_capacity_gb'] = 'unavailable'
+ data['QoS_support'] = False
+ self._stats = data
+ LOG.debug('Total cap %(t)s Free cap %(f)s',
+ {'t': totalcapacitygb,
+ 'f': freespacegb})
--- /dev/null
+# Copyright 2014 Dell Inc.
+#
+# 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.
+
+'''Volume driver for Dell Storage Center.'''
+
+from oslo.utils import excutils
+
+from cinder import exception
+from cinder.i18n import _, _LE
+from cinder.openstack.common import log as logging
+from cinder.volume import driver
+from cinder.volume.drivers.dell import dell_storagecenter_common
+from cinder.zonemanager import utils as fczm_utils
+
+LOG = logging.getLogger(__name__)
+
+
+class DellStorageCenterFCDriver(driver.FibreChannelDriver,
+ dell_storagecenter_common.DellCommonDriver):
+
+ '''Implements commands for Dell EqualLogic SAN ISCSI management.
+
+ To enable the driver add the following line to the cinder configuration:
+ volume_driver=cinder.volume.drivers.dell.DellStorageCenterFCDriver
+ '''
+
+ VERSION = '1.0.1'
+
+ def __init__(self, *args, **kwargs):
+ super(DellStorageCenterFCDriver, self).__init__(*args, **kwargs)
+ self.backend_name =\
+ self.configuration.safe_get('volume_backend_name') or 'Dell-FC'
+
+ @fczm_utils.AddFCZone
+ def initialize_connection(self, volume, connector):
+ '''Initializes the connection and returns connection info.
+
+ Assign any created volume to a compute node/host so that it can be
+ used from that host.
+
+ The driver returns a driver_volume_type of 'fibre_channel'.
+ The target_wwn can be a single entry or a list of wwns that
+ correspond to the list of remote wwn(s) that will export the volume.
+ '''
+
+ # We use id to name the volume name as it is a
+ # known unique name.
+ volume_name = volume.get('id')
+ LOG.debug('Initialize connection: %s', volume_name)
+ with self._client.open_connection() as api:
+ try:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ # Find our server.
+ wwpns = connector.get('wwpns')
+ for wwn in wwpns:
+ scserver = api.find_server(ssn,
+ wwn)
+ if scserver is not None:
+ break
+
+ # No? Create it.
+ if scserver is None:
+ server_folder = self.configuration.dell_sc_server_folder
+ scserver = api.create_server_multiple_hbas(ssn,
+ server_folder,
+ wwpns)
+ # Find the volume on the storage center.
+ scvolume = api.find_volume(ssn,
+ volume_name)
+ if scserver is not None and scvolume is not None:
+ mapping = api.map_volume(scvolume,
+ scserver)
+ if mapping is not None:
+ # Since we just mapped our volume we had best update
+ # our sc volume object.
+ scvolume = api.find_volume(ssn,
+ volume_name)
+ lun, targets, init_targ_map = api.find_wwns(scvolume,
+ scserver)
+ if lun is not None and len(targets) > 0:
+ data = {'driver_volume_type': 'fibre_channel',
+ 'data': {'target_lun': lun,
+ 'target_discovered': True,
+ 'target_wwn': targets,
+ 'initiator_target_map':
+ init_targ_map}}
+ LOG.debug('Return FC data:')
+ LOG.debug(data)
+ return data
+ LOG.error(_LE('Lun mapping returned null!'))
+
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to initialize connection '))
+
+ # We get here because our mapping is none so blow up.
+ raise exception.VolumeBackendAPIException(
+ _('unable to map volume'))
+
+ @fczm_utils.RemoveFCZone
+ def terminate_connection(self, volume, connector, force=False, **kwargs):
+ # Get our volume name
+ volume_name = volume.get('id')
+ LOG.debug('Terminate connection: %s', volume_name)
+ with self._client.open_connection() as api:
+ try:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ wwpns = connector.get('wwpns')
+ for wwn in wwpns:
+ scserver = api.find_server(ssn,
+ wwn)
+ if scserver is not None:
+ break
+
+ # Find the volume on the storage center.
+ scvolume = api.find_volume(ssn,
+ volume_name)
+ # Get our target map so we can return it to free up a zone.
+ lun, targets, init_targ_map = api.find_wwns(scvolume,
+ scserver)
+ # If we have a server and a volume lets unmap them.
+ if (scserver is not None and
+ scvolume is not None and
+ api.unmap_volume(scvolume, scserver) is True):
+ LOG.debug('Connection terminated')
+ else:
+ raise exception.VolumeBackendAPIException(
+ _('Terminate connection failed'))
+
+ # basic return info...
+ info = {'driver_volume_type': 'fibre_channel',
+ 'data': {}}
+
+ # if not then we return the target map so that
+ # the zone can be freed up.
+ if api.get_volume_count(scserver) == 0:
+ info['data'] = {'target_wwn': targets,
+ 'initiator_target_map': init_targ_map}
+ return info
+
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to terminate connection'))
+ raise exception.VolumeBackendAPIException(
+ _('Terminate connection unable to connect to backend.'))
+
+ def get_volume_stats(self, refresh=False):
+ '''Get volume status.
+
+ If 'refresh' is True, run update the stats first.
+ '''
+ if refresh:
+ self._update_volume_stats()
+ # Update our protocol to the correct one.
+ self._stats['storage_protocol'] = 'FC'
+
+ return self._stats
--- /dev/null
+# Copyright 2014 Dell Inc.
+#
+# 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.
+
+'''Volume driver for Dell Storage Center.'''
+
+from oslo.utils import excutils
+
+from cinder import exception
+from cinder.i18n import _, _LE
+from cinder.openstack.common import log as logging
+from cinder.volume.drivers.dell import dell_storagecenter_common
+from cinder.volume.drivers import san
+
+LOG = logging.getLogger(__name__)
+
+
+class DellStorageCenterISCSIDriver(san.SanISCSIDriver,
+ dell_storagecenter_common.DellCommonDriver):
+
+ '''Implements commands for Dell StorageCenter ISCSI management.
+
+ To enable the driver add the following line to the cinder configuration:
+ volume_driver=cinder.volume.drivers.dell.DellStorageCenterISCSIDriver
+ '''
+
+ VERSION = '1.0.1'
+
+ def __init__(self, *args, **kwargs):
+ super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs)
+ self.backend_name =\
+ self.configuration.safe_get('volume_backend_name') or 'Dell-iSCSI'
+
+ def initialize_connection(self, volume, connector):
+ # We use id to name the volume name as it is a
+ # known unique name.
+ volume_name = volume.get('id')
+ initiator_name = connector.get('initiator')
+ LOG.debug('initialize_ connection: %(n)s:%(i)s',
+ {'n': volume_name,
+ 'i': initiator_name})
+
+ with self._client.open_connection() as api:
+ try:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ # Find our server.
+ server = api.find_server(ssn,
+ initiator_name)
+ # No? Create it.
+ if server is None:
+ server_folder = self.configuration.dell_sc_server_folder
+ server = api.create_server(ssn,
+ server_folder,
+ initiator_name)
+ # Find the volume on the storage center.
+ scvolume = api.find_volume(ssn,
+ volume_name)
+
+ # if we have a server and a volume lets bring them together.
+ if server is not None and scvolume is not None:
+ mapping = api.map_volume(scvolume,
+ server)
+ if mapping is not None:
+ # Since we just mapped our volume we had best update
+ # our sc volume object.
+ scvolume = api.find_volume(ssn,
+ volume_name)
+ ip = self.configuration.iscsi_ip_address
+ port = self.configuration.iscsi_port
+ iqn = api.find_iqn(scvolume,
+ ip)
+ if iqn is None:
+ LOG.error(_LE('Volume mapped to invalid path.'))
+ else:
+ properties = {}
+ properties['target_discovered'] = False
+ properties['target_lun'] = mapping['lunUsed'][0]
+ if mapping['readOnly'] is True:
+ properties['access_mode'] = 'ro'
+ else:
+ properties['access_mode'] = 'rw'
+ properties['target_portal'] = ip + ':' + str(port)
+ properties['target_iqn'] = iqn
+
+ LOG.debug(properties)
+ return {'driver_volume_type': 'iscsi',
+ 'data': properties
+ }
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to initialize connection '
+ ' %(i)s %(n)s'),
+ {'i': initiator_name,
+ 'n': volume_name})
+
+ # We get here because our mapping is none or we have no valid iqn to
+ # return so blow up.
+ raise exception.VolumeBackendAPIException(
+ _('Unable to map volume'))
+
+ def terminate_connection(self, volume, connector, force=False, **kwargs):
+ # Grab some initial info.
+ initiator_name = connector.get('initiator')
+ volume_name = volume.get('id')
+ LOG.debug('Terminate connection: %(n)s:%(i)s',
+ {'n': volume_name,
+ 'i': initiator_name})
+ with self._client.open_connection() as api:
+ try:
+ ssn = api.find_sc(self.configuration.dell_sc_ssn)
+ scserver = api.find_server(ssn,
+ initiator_name)
+ # Find the volume on the storage center.
+ scvolume = api.find_volume(ssn,
+ volume_name)
+
+ # If we have a server and a volume lets pull them apart.
+ if (scserver is not None and
+ scvolume is not None and
+ api.unmap_volume(scvolume, scserver) is True):
+ LOG.debug('Connection terminated')
+ return
+ except Exception:
+ with excutils.save_and_reraise_exception():
+ LOG.error(_LE('Failed to terminate connection '
+ '%(i)s %(n)s'),
+ {'i': initiator_name,
+ 'n': volume_name})
+ raise exception.VolumeBackendAPIException(
+ _('Terminate connection failed'))