]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add Support for Dell Storage Center
authorTom Swanson <tom_swanson@dell.com>
Wed, 3 Dec 2014 19:24:39 +0000 (13:24 -0600)
committerTom Swanson <tom_swanson@dell.com>
Mon, 15 Dec 2014 18:00:47 +0000 (12:00 -0600)
This driver implements cinder FC and iSCSI volume drivers.
The file dell_storagecenter_api.py is called to interface with the
Dell Storage Center backend via a REST interface.
Dell_storagecenter_common.py is the base class implementation with
dell_storagecenter_fc.py and dell_storagecenter_iscsi.py being providing
fc and iscsi specific support respectivly.

https://bugs.launchpad.net/cinder/+bug/1398951

Implements: blueprint dell-storage-center-block-storage-driver
Change-Id: Ic483e54b40349ede20e078c6406f5bab8d7d7cc2

cinder/tests/test_dellfc.py [new file with mode: 0644]
cinder/tests/test_dellsc.py [new file with mode: 0644]
cinder/volume/drivers/dell/__init__.py [new file with mode: 0644]
cinder/volume/drivers/dell/dell_storagecenter_api.py [new file with mode: 0644]
cinder/volume/drivers/dell/dell_storagecenter_common.py [new file with mode: 0644]
cinder/volume/drivers/dell/dell_storagecenter_fc.py [new file with mode: 0644]
cinder/volume/drivers/dell/dell_storagecenter_iscsi.py [new file with mode: 0644]

diff --git a/cinder/tests/test_dellfc.py b/cinder/tests/test_dellfc.py
new file mode 100644 (file)
index 0000000..7a653ee
--- /dev/null
@@ -0,0 +1,543 @@
+#    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
diff --git a/cinder/tests/test_dellsc.py b/cinder/tests/test_dellsc.py
new file mode 100644 (file)
index 0000000..82e0462
--- /dev/null
@@ -0,0 +1,917 @@
+#    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
diff --git a/cinder/volume/drivers/dell/__init__.py b/cinder/volume/drivers/dell/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/cinder/volume/drivers/dell/dell_storagecenter_api.py b/cinder/volume/drivers/dell/dell_storagecenter_api.py
new file mode 100644 (file)
index 0000000..7e0060f
--- /dev/null
@@ -0,0 +1,1046 @@
+#    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.')
diff --git a/cinder/volume/drivers/dell/dell_storagecenter_common.py b/cinder/volume/drivers/dell/dell_storagecenter_common.py
new file mode 100644 (file)
index 0000000..70e3811
--- /dev/null
@@ -0,0 +1,332 @@
+#    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})
diff --git a/cinder/volume/drivers/dell/dell_storagecenter_fc.py b/cinder/volume/drivers/dell/dell_storagecenter_fc.py
new file mode 100644 (file)
index 0000000..c1ce399
--- /dev/null
@@ -0,0 +1,168 @@
+#    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
diff --git a/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py b/cinder/volume/drivers/dell/dell_storagecenter_iscsi.py
new file mode 100644 (file)
index 0000000..658bc5b
--- /dev/null
@@ -0,0 +1,140 @@
+#    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'))