]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Dell SC: Add support for ManageableVD
authorTom Swanson <tom_swanson@dell.com>
Wed, 8 Jul 2015 15:46:09 +0000 (10:46 -0500)
committerTom Swanson <tom_swanson@dell.com>
Wed, 8 Jul 2015 17:46:55 +0000 (12:46 -0500)
Added support for the ManageableVD volume driver class. This also
meant the driver changed from inheriting the volumedriver class to
inheriting the assorted *VD classes.

A few things about our ManageableVD support. We only allow management
of a volume if it is sized in multiples of 1GB. We only allow
management of volumes that are not currently mapped to a server on the
Storage Center. id-types of source-id (DeviceId on the Storage Center)
and source-name (Volume Name on the Storage Center) are supported.

Change-Id: Iecc93ce1654111e1292cfc1931d1eda4abd08595

cinder/tests/unit/test_dellsc.py
cinder/tests/unit/test_dellscapi.py
cinder/volume/drivers/dell/dell_storagecenter_api.py
cinder/volume/drivers/dell/dell_storagecenter_common.py
cinder/volume/drivers/dell/dell_storagecenter_fc.py
cinder/volume/drivers/dell/dell_storagecenter_iscsi.py

index a99fdd60b5b29de20906055d2e26fd3f23e55a0f..c3f1cc1e52ad3cdff15a06e7a029f6b17fbe2c71 100644 (file)
@@ -1451,3 +1451,119 @@ class DellSCSanISCSIDriverTestCase(test.TestCase):
             cgsnap['consistencygroup_id'])
         mock_delete_cg_replay.assert_called_once_with(self.SCRPLAYPROFILE,
                                                       cgsnap['id'])
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       'manage_existing')
+    def test_manage_existing(self,
+                             mock_manage_existing,
+                             mock_close_connection,
+                             mock_open_connection,
+                             mock_init):
+        # Very little to do in this one.  The call is sent
+        # straight down.
+        volume = {'id': 'guid'}
+        existing_ref = {'source-name': 'imavolumename'}
+        self.driver.manage_existing(volume, existing_ref)
+        mock_manage_existing.assert_called_once_with(volume['id'],
+                                                     existing_ref)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       'manage_existing')
+    def test_manage_existing_id(self,
+                                mock_manage_existing,
+                                mock_close_connection,
+                                mock_open_connection,
+                                mock_init):
+        # Very little to do in this one.  The call is sent
+        # straight down.
+        volume = {'id': 'guid'}
+        existing_ref = {'source-id': 'imadeviceid'}
+        self.driver.manage_existing(volume, existing_ref)
+        mock_manage_existing.assert_called_once_with(volume['id'],
+                                                     existing_ref)
+
+    def test_manage_existing_bad_ref(self,
+                                     mock_close_connection,
+                                     mock_open_connection,
+                                     mock_init):
+        volume = {'id': 'guid'}
+        existing_ref = {'banana-name': 'imavolumename'}
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing,
+                          volume,
+                          existing_ref)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       'get_unmanaged_volume_size',
+                       return_value=4)
+    def test_manage_existing_get_size(self,
+                                      mock_get_unmanaged_volume_size,
+                                      mock_close_connection,
+                                      mock_open_connection,
+                                      mock_init):
+        # Almost nothing to test here.  Just that we call our function.
+        volume = {'id': 'guid'}
+        existing_ref = {'source-name': 'imavolumename'}
+        res = self.driver.manage_existing_get_size(volume, existing_ref)
+        mock_get_unmanaged_volume_size.assert_called_once_with(existing_ref)
+        # The above is 4GB and change.
+        self.assertEqual(4, res)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       'get_unmanaged_volume_size',
+                       return_value=4)
+    def test_manage_existing_get_size_id(self,
+                                         mock_get_unmanaged_volume_size,
+                                         mock_close_connection,
+                                         mock_open_connection,
+                                         mock_init):
+        # Almost nothing to test here.  Just that we call our function.
+        volume = {'id': 'guid'}
+        existing_ref = {'source-id': 'imadeviceid'}
+        res = self.driver.manage_existing_get_size(volume, existing_ref)
+        mock_get_unmanaged_volume_size.assert_called_once_with(existing_ref)
+        # The above is 4GB and change.
+        self.assertEqual(4, res)
+
+    def test_manage_existing_get_size_bad_ref(self,
+                                              mock_close_connection,
+                                              mock_open_connection,
+                                              mock_init):
+        volume = {'id': 'guid'}
+        existing_ref = {'banana-name': 'imavolumename'}
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.driver.manage_existing_get_size,
+                          volume,
+                          existing_ref)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       'find_volume',
+                       return_value=VOLUME)
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       'unmanage')
+    def test_unmanage(self,
+                      mock_unmanage,
+                      mock_find_volume,
+                      mock_close_connection,
+                      mock_open_connection,
+                      mock_init):
+        volume = {'id': 'guid'}
+        self.driver.unmanage(volume)
+        mock_find_volume.assert_called_once_with(volume['id'])
+        mock_unmanage.assert_called_once_with(self.VOLUME)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       'find_volume',
+                       return_value=None)
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       'unmanage')
+    def test_unmanage_volume_not_found(self,
+                                       mock_unmanage,
+                                       mock_find_volume,
+                                       mock_close_connection,
+                                       mock_open_connection,
+                                       mock_init):
+        volume = {'id': 'guid'}
+        self.driver.unmanage(volume)
+        mock_find_volume.assert_called_once_with(volume['id'])
+        self.assertFalse(mock_unmanage.called)
index 6fc071a4507825e3a1b32f1bbbc46e0ff83a21d5..345d9ab5df0046e01921eb758de7d93ad456b52e 100644 (file)
@@ -2105,7 +2105,7 @@ class DellSCSanAPITestCase(test.TestCase):
                                                mock_open_connection,
                                                mock_init):
         # Test case to find volume in the configured volume folder
-        res = self.scapi._get_volume_list(self.volume_name, True)
+        res = self.scapi._get_volume_list(self.volume_name, None, True)
         self.assertTrue(mock_post.called)
         self.assertTrue(mock_get_json.called)
         self.assertEqual(self.VOLUME_LIST, res, 'Unexpected volume list')
@@ -2123,17 +2123,17 @@ class DellSCSanAPITestCase(test.TestCase):
                                        mock_open_connection,
                                        mock_init):
         # Test case to find volume anywhere in the configured SC
-        res = self.scapi._get_volume_list(self.volume_name, False)
+        res = self.scapi._get_volume_list(self.volume_name, None, False)
         self.assertTrue(mock_post.called)
         self.assertTrue(mock_get_json.called)
         self.assertEqual(self.VOLUME_LIST, res, 'Unexpected volume list')
 
-    def test__get_volume_list_no_name(self,
-                                      mock_close_connection,
-                                      mock_open_connection,
-                                      mock_init):
-        # Test case specified volume name is None
-        res = self.scapi._get_volume_list(None, True)
+    def test_get_volume_list_no_name_no_id(self,
+                                           mock_close_connection,
+                                           mock_open_connection,
+                                           mock_init):
+        # Test case specified volume name is None and device id is None.
+        res = self.scapi._get_volume_list(None, None, True)
         self.assertIsNone(res, 'None expected')
 
     @mock.patch.object(dell_storagecenter_api.HttpClient,
@@ -2145,7 +2145,7 @@ class DellSCSanAPITestCase(test.TestCase):
                                       mock_open_connection,
                                       mock_init):
         # Test case to find volume in the configured volume folder
-        res = self.scapi._get_volume_list(self.volume_name, True)
+        res = self.scapi._get_volume_list(self.volume_name, None, True)
         self.assertTrue(mock_post.called)
         self.assertIsNone(res, 'None expected')
 
@@ -4689,6 +4689,380 @@ class DellSCSanAPITestCase(test.TestCase):
         self.assertTrue(mock_find_replay.called)
         self.assertTrue(res)
 
+    def test_size_to_gb(self,
+                        mock_close_connection,
+                        mock_open_connection,
+                        mock_init):
+        gb, rem = self.scapi._size_to_gb('1.073741824E9 Byte')
+        self.assertEqual(1, gb)
+        self.assertEqual(0, rem)
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.scapi._size_to_gb,
+                          'banana')
+        gb, rem = self.scapi._size_to_gb('1.073741924E9 Byte')
+        self.assertEqual(1, gb)
+        self.assertEqual(100, rem)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{'configuredSize':
+                                      '1.073741824E9 Bytes'}])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_size_to_gb',
+                       return_value=(1, 0))
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_find_mappings',
+                       return_value=[])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_find_volume_folder',
+                       return_value={'id': '1'})
+    @mock.patch.object(dell_storagecenter_api.HttpClient,
+                       'put',
+                       return_value=RESPONSE_200)
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_id')
+    def test_manage_existing(self,
+                             mock_get_id,
+                             mock_put,
+                             mock_find_volume_folder,
+                             mock_find_mappings,
+                             mock_size_to_gb,
+                             mock_get_volume_list,
+                             mock_close_connection,
+                             mock_open_connection,
+                             mock_init):
+        newname = 'guid'
+        existing = {'source-name': 'scvolname'}
+        # First call is foldername, second is vollist. This is reflected
+        # in the payload.
+        mock_get_id.side_effect = ['1', '100']
+        expected_url = 'StorageCenter/ScVolume/100'
+        expected_payload = {'Name': newname,
+                            'VolumeFolder': '1'}
+        self.scapi.manage_existing(newname, existing)
+        mock_get_volume_list.asert_called_once_with(existing, False)
+        self.assertTrue(mock_get_id.called)
+        mock_put.assert_called_once_with(expected_url, expected_payload)
+        self.assertTrue(mock_find_volume_folder.called)
+        self.assertTrue(mock_find_mappings.called)
+        self.assertTrue(mock_size_to_gb.called)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{'configuredSize':
+                                      '1.073741824E9 Bytes'}])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_size_to_gb',
+                       return_value=(1, 0))
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_find_mappings',
+                       return_value=[])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_find_volume_folder',
+                       return_value=None)
+    @mock.patch.object(dell_storagecenter_api.HttpClient,
+                       'put',
+                       return_value=RESPONSE_200)
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_id',
+                       return_value='100')
+    def test_manage_existing_folder_not_found(self,
+                                              mock_get_id,
+                                              mock_put,
+                                              mock_find_volume_folder,
+                                              mock_find_mappings,
+                                              mock_size_to_gb,
+                                              mock_get_volume_list,
+                                              mock_close_connection,
+                                              mock_open_connection,
+                                              mock_init):
+        # Same as above only we don't have a volume folder.
+        newname = 'guid'
+        existing = {'source-name': 'scvolname'}
+        expected_url = 'StorageCenter/ScVolume/100'
+        expected_payload = {'Name': newname}
+        self.scapi.manage_existing(newname, existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+        mock_put.assert_called_once_with(expected_url, expected_payload)
+        self.assertTrue(mock_get_id.called)
+        self.assertTrue(mock_find_volume_folder.called)
+        self.assertTrue(mock_find_mappings.called)
+        self.assertTrue(mock_size_to_gb.called)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[])
+    def test_manage_existing_vol_not_found(self,
+                                           mock_get_volume_list,
+                                           mock_close_connection,
+                                           mock_open_connection,
+                                           mock_init):
+
+        # Same as above only we don't have a volume folder.
+        newname = 'guid'
+        existing = {'source-name': 'scvolname'}
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.scapi.manage_existing,
+                          newname,
+                          existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{}, {}, {}])
+    def test_manage_existing_vol_multiple_found(self,
+                                                mock_get_volume_list,
+                                                mock_close_connection,
+                                                mock_open_connection,
+                                                mock_init):
+
+        # Same as above only we don't have a volume folder.
+        newname = 'guid'
+        existing = {'source-name': 'scvolname'}
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.scapi.manage_existing,
+                          newname,
+                          existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{'configuredSize':
+                                      '1.073741924E9 Bytes'}])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_size_to_gb',
+                       return_value=(1, 100))
+    def test_manage_existing_bad_size(self,
+                                      mock_size_to_gb,
+                                      mock_get_volume_list,
+                                      mock_close_connection,
+                                      mock_open_connection,
+                                      mock_init):
+
+        # Same as above only we don't have a volume folder.
+        newname = 'guid'
+        existing = {'source-name': 'scvolname'}
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.scapi.manage_existing,
+                          newname,
+                          existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+        self.assertTrue(mock_size_to_gb.called)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{'configuredSize':
+                                      '1.073741824E9 Bytes'}])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_size_to_gb',
+                       return_value=(1, 0))
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_find_mappings',
+                       return_value=[{}, {}])
+    def test_manage_existing_already_mapped(self,
+                                            mock_find_mappings,
+                                            mock_size_to_gb,
+                                            mock_get_volume_list,
+                                            mock_close_connection,
+                                            mock_open_connection,
+                                            mock_init):
+
+        newname = 'guid'
+        existing = {'source-name': 'scvolname'}
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.scapi.manage_existing,
+                          newname,
+                          existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+        self.assertTrue(mock_find_mappings.called)
+        self.assertTrue(mock_size_to_gb.called)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{'configuredSize':
+                                      '1.073741824E9 Bytes'}])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_size_to_gb',
+                       return_value=(1, 0))
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_find_mappings',
+                       return_value=[])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_find_volume_folder',
+                       return_value=None)
+    @mock.patch.object(dell_storagecenter_api.HttpClient,
+                       'put',
+                       return_value=RESPONSE_400)
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_id',
+                       return_value='100')
+    def test_manage_existing_rename_fail(self,
+                                         mock_get_id,
+                                         mock_put,
+                                         mock_find_volume_folder,
+                                         mock_find_mappings,
+                                         mock_size_to_gb,
+                                         mock_get_volume_list,
+                                         mock_close_connection,
+                                         mock_open_connection,
+                                         mock_init):
+        # We fail on the _find_volume_folder to make this easier.
+        newname = 'guid'
+        existing = {'source-name': 'scvolname'}
+        expected_url = 'StorageCenter/ScVolume/100'
+        expected_payload = {'Name': newname}
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.scapi.manage_existing,
+                          newname,
+                          existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+        self.assertTrue(mock_get_id.called)
+        mock_put.assert_called_once_with(expected_url, expected_payload)
+        self.assertTrue(mock_find_volume_folder.called)
+        self.assertTrue(mock_find_mappings.called)
+        self.assertTrue(mock_size_to_gb.called)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{'configuredSize':
+                                      '1.073741824E9 Bytes'}])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_size_to_gb',
+                       return_value=(1, 0))
+    def test_get_unmanaged_volume_size(self,
+                                       mock_size_to_gb,
+                                       mock_get_volume_list,
+                                       mock_close_connection,
+                                       mock_open_connection,
+                                       mock_init):
+        existing = {'source-name': 'scvolname'}
+        res = self.scapi.get_unmanaged_volume_size(existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+        self.assertTrue(mock_size_to_gb.called)
+        self.assertEqual(1, res)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[])
+    def test_get_unmanaged_volume_size_not_found(self,
+                                                 mock_get_volume_list,
+                                                 mock_close_connection,
+                                                 mock_open_connection,
+                                                 mock_init):
+        existing = {'source-name': 'scvolname'}
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.scapi.get_unmanaged_volume_size,
+                          existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{}, {}, {}])
+    def test_get_unmanaged_volume_size_many_found(self,
+                                                  mock_get_volume_list,
+                                                  mock_close_connection,
+                                                  mock_open_connection,
+                                                  mock_init):
+        existing = {'source-name': 'scvolname'}
+        self.assertRaises(exception.ManageExistingInvalidReference,
+                          self.scapi.get_unmanaged_volume_size,
+                          existing)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_volume_list',
+                       return_value=[{'configuredSize':
+                                      '1.073741924E9 Bytes'}])
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_size_to_gb',
+                       return_value=(1, 100))
+    def test_get_unmanaged_volume_size_bad_size(self,
+                                                mock_size_to_gb,
+                                                mock_get_volume_list,
+                                                mock_close_connection,
+                                                mock_open_connection,
+                                                mock_init):
+        existing = {'source-name': 'scvolname'}
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.scapi.get_unmanaged_volume_size,
+                          existing)
+        self.assertTrue(mock_size_to_gb.called)
+        mock_get_volume_list.asert_called_once_with(
+            existing.get('source-name'),
+            existing.get('source-id'),
+            False)
+
+    @mock.patch.object(dell_storagecenter_api.HttpClient,
+                       'put',
+                       return_value=RESPONSE_200)
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_id',
+                       return_value='100')
+    def test_unmanage(self,
+                      mock_get_id,
+                      mock_put,
+                      mock_close_connection,
+                      mock_open_connection,
+                      mock_init):
+        # Same as above only we don't have a volume folder.
+        scvolume = {'name': 'guid'}
+        expected_url = 'StorageCenter/ScVolume/100'
+        newname = 'Unmanaged_' + scvolume['name']
+        expected_payload = {'Name': newname}
+        self.scapi.unmanage(scvolume)
+        self.assertTrue(mock_get_id.called)
+        mock_put.assert_called_once_with(expected_url, expected_payload)
+
+    @mock.patch.object(dell_storagecenter_api.HttpClient,
+                       'put',
+                       return_value=RESPONSE_400)
+    @mock.patch.object(dell_storagecenter_api.StorageCenterApi,
+                       '_get_id',
+                       return_value='100')
+    def test_unmanage_fail(self,
+                           mock_get_id,
+                           mock_put,
+                           mock_close_connection,
+                           mock_open_connection,
+                           mock_init):
+        # Same as above only we don't have a volume folder.
+        scvolume = {'name': 'guid'}
+        expected_url = 'StorageCenter/ScVolume/100'
+        newname = 'Unmanaged_' + scvolume['name']
+        expected_payload = {'Name': newname}
+        self.assertRaises(exception.VolumeBackendAPIException,
+                          self.scapi.unmanage,
+                          scvolume)
+        self.assertTrue(mock_get_id.called)
+        mock_put.assert_called_once_with(expected_url, expected_payload)
+
 
 class DellSCSanAPIConnectionTestCase(test.TestCase):
 
index ddc38542ba160850a16434c57b2cdfcf21235c54..95352f3031b7a0a3d9234b0579869b30bc3f3e5a 100644 (file)
@@ -169,7 +169,7 @@ class StorageCenterApi(object):
     Handles calls to Dell Enterprise Manager (EM) via the REST API interface.
     '''
 
-    APIVERSION = '1.2.0'
+    APIVERSION = '2.0.1'
 
     def __init__(self, host, port, user, password, verify):
         '''This creates a connection to Dell Enterprise Manager.
@@ -605,36 +605,39 @@ class StorageCenterApi(object):
 
         return scvolume
 
-    def _get_volume_list(self, name, filterbyvfname=True):
+    def _get_volume_list(self, name, deviceid, filterbyvfname=True):
         '''Return the specified list of volumes.
 
         :param name: Volume name.
+        :param deviceid: Volume device ID on the SC backend.
         :param filterbyvfname:  If set to true then this filters by the preset
                                 folder name.
-        :return: Returns the scvolume or None.
+        :return: Returns the scvolume list or None.
         '''
         result = None
-        pf = PayloadFilter()
-        pf.append('scSerialNumber', self.ssn)
-        # We need a name to find a volume.
-        if name is not None:
-            pf.append('Name', name)
-        else:
-            return None
-        # set folderPath
-        if filterbyvfname:
-            vfname = (self.vfname if self.vfname.endswith('/')
-                      else self.vfname + '/')
-            pf.append('volumeFolderPath', vfname)
-        r = self.client.post('StorageCenter/ScVolume/GetList',
-                             pf.payload)
-        if r.status_code != 200:
-            LOG.debug('ScVolume GetList error %(name)s: %(code)d %(reason)s',
-                      {'name': name,
-                       'code': r.status_code,
-                       'reason': r.reason})
-        else:
-            result = self._get_json(r)
+        # We need a name or a device ID to find a volume.
+        if name or deviceid:
+            pf = PayloadFilter()
+            pf.append('scSerialNumber', self.ssn)
+            if name is not None:
+                pf.append('Name', name)
+            if deviceid is not None:
+                pf.append('DeviceId', deviceid)
+            # set folderPath
+            if filterbyvfname:
+                vfname = (self.vfname if self.vfname.endswith('/')
+                          else self.vfname + '/')
+                pf.append('volumeFolderPath', vfname)
+            r = self.client.post('StorageCenter/ScVolume/GetList',
+                                 pf.payload)
+            if r.status_code != 200:
+                LOG.debug('ScVolume GetList error '
+                          '%(name)s: %(code)d %(reason)s',
+                          {'name': name,
+                           'code': r.status_code,
+                           'reason': r.reason})
+            else:
+                result = self._get_json(r)
         # We return None if there was an error and a list if the command
         # succeeded. It might be an empty list.
         return result
@@ -661,6 +664,7 @@ class StorageCenterApi(object):
 
         # Look for our volume in our folder.
         vollist = self._get_volume_list(name,
+                                        None,
                                         True)
         # If an empty list was returned they probably moved the volumes or
         # changed the folder name so try again without the folder.
@@ -669,6 +673,7 @@ class StorageCenterApi(object):
                       {'n': name,
                        'v': self.vfname})
             vollist = self._get_volume_list(name,
+                                            None,
                                             False)
 
         # If multiple volumes of the same name are found we need to error.
@@ -1825,3 +1830,140 @@ class StorageCenterApi(object):
                 return False
         # We either couldn't find it or expired it.
         return True
+
+    def _size_to_gb(self, spacestring):
+        '''Splits a SC size string into GB and a remainder.
+
+        Space is returned in a string like ...
+        7.38197504E8 Bytes
+        Need to split that apart and convert to GB.
+
+        :param spacestring: SC size string.
+        :return: Size in GB and remainder in byte.
+        '''
+        try:
+            n = spacestring.split(' ', 1)
+            fgb = int(float(n[0]) // 1073741824)
+            frem = int(float(n[0]) % 1073741824)
+            return fgb, frem
+
+        except Exception:
+            # We received an invalid size string.  Blow up.
+            raise exception.VolumeBackendAPIException(
+                _('Error retrieving volume size'))
+
+    def manage_existing(self, newname, existing):
+        '''Finds the volume named existing and renames it.
+
+         This checks a few things. The volume has to exist.  There can
+         only be one volume by that name.  Since cinder manages volumes
+         by the GB it has to be defined on a GB boundry.
+
+         This renames existing to newname.  newname is the guid from
+         the cinder volume['id'].  The volume is moved to the defined
+         cinder volume folder.
+
+        :param newname: Name to rename the volume to.
+        :param existing: The existing volume dict..
+        :return: Nothing.
+        :raises: VolumeBackendAPIException, ManageExistingInvalidReference
+        '''
+        vollist = self._get_volume_list(existing.get('source-name'),
+                                        existing.get('source-id'),
+                                        False)
+        count = len(vollist)
+        # If we found one volume with that name we can work with it.
+        if count == 1:
+            # First thing to check is if the size is something we can
+            # work with.
+            sz, rem = self._size_to_gb(vollist[0]['configuredSize'])
+            if rem > 0:
+                raise exception.VolumeBackendAPIException(
+                    _('Volume size must multiple of 1 GB.'))
+
+            # We only want to grab detached volumes.
+            mappings = self._find_mappings(vollist[0])
+            if len(mappings) > 0:
+                raise exception.VolumeBackendAPIException(
+                    _('Volume is attached to a server.  (%s)') % existing)
+
+            # Find our folder
+            folder = self._find_volume_folder(True)
+
+            # If we actually have a place to put our volume create it
+            if folder is None:
+                LOG.warning(_LW('Unable to create folder %s'),
+                            self.vfname)
+
+            # Rename and move our volume.
+            payload = {}
+            payload['Name'] = newname
+            if folder:
+                payload['VolumeFolder'] = self._get_id(folder)
+
+            r = self.client.put('StorageCenter/ScVolume/%s' %
+                                self._get_id(vollist[0]),
+                                payload)
+            if r.status_code != 200:
+                LOG.error(_LE('ScVolume error on rename: %(code)d %(reason)s'),
+                          {'code': r.status_code,
+                           'reason': r.reason})
+                raise exception.VolumeBackendAPIException(
+                    _('Unable to manage volume %s') % existing)
+        elif count > 1:
+            raise exception.ManageExistingInvalidReference(
+                _('Volume not unique.  (%s)') % existing)
+        else:
+            raise exception.ManageExistingInvalidReference(
+                _('Volume not found.  (%s)') % existing)
+
+    def get_unmanaged_volume_size(self, existing):
+        '''Looks up the volume named existing and returns its size string.
+
+        :param existing: Existing volume dict.
+        :return: The SC configuredSize string.
+        :raises: ManageExistingInvalidReference
+        '''
+        vollist = self._get_volume_list(existing.get('source-name'),
+                                        existing.get('source-id'),
+                                        False)
+        count = len(vollist)
+        # If we found one volume with that name we can work with it.
+        if count == 1:
+            sz, rem = self._size_to_gb(vollist[0]['configuredSize'])
+            if rem > 0:
+                raise exception.VolumeBackendAPIException(
+                    _('Volume size must multiple of 1 GB.'))
+            return sz
+        elif count > 1:
+            raise exception.ManageExistingInvalidReference(
+                _('Volume not unique.  (%s)') % existing)
+        else:
+            raise exception.ManageExistingInvalidReference(
+                _('Volume not found.  (%s)') % existing)
+
+    def unmanage(self, scvolume):
+        '''Unmanage our volume.
+
+        We simply rename with with a prefix of 'Unmanaged_'.  That's it.
+
+        :param scvolume: The Dell SC volume object.
+        :return: Nothing.
+        :raises: VolumeBackendAPIException
+        '''
+        newname = 'Unmanaged_' + scvolume['name']
+        payload = {}
+        payload['Name'] = newname
+        r = self.client.put('StorageCenter/ScVolume/%s' %
+                            self._get_id(scvolume),
+                            payload)
+        if r.status_code == 200:
+            LOG.info(_LI('Volume %s unmanaged.'), scvolume['name'])
+        else:
+            LOG.error(_LE('ScVolume error on rename: %(code)d %(reason)s'),
+                      {'code': r.status_code,
+                       'reason': r.reason})
+            raise exception.VolumeBackendAPIException(
+                _('Unable to rename volume %(existing)s to %(newname)s') %
+                {'existing': scvolume['name'],
+                 'newname': newname})
index f200cacf8fa4823b9b6f2249271ed540b040c9ae..2c8ea40cf121fd91bd56cd91a3a169a5109284aa 100644 (file)
@@ -48,7 +48,9 @@ CONF = cfg.CONF
 CONF.register_opts(common_opts)
 
 
-class DellCommonDriver(driver.VolumeDriver):
+class DellCommonDriver(driver.ConsistencyGroupVD, driver.ManageableVD,
+                       driver.ExtendVD, driver.CloneableVD, driver.SnapshotVD,
+                       driver.BaseVD):
 
     def __init__(self, *args, **kwargs):
         super(DellCommonDriver, self).__init__(*args, **kwargs)
@@ -374,6 +376,7 @@ class DellCommonDriver(driver.VolumeDriver):
         # The world was horrible to us so we should error and leave.
         LOG.error(_LE('Unable to rename the logical volume for volume: %s'),
                   original_volume_name)
+
         return {'_name_id': new_volume['_name_id'] or new_volume['id']}
 
     def create_consistencygroup(self, context, group):
@@ -530,3 +533,77 @@ class DellCommonDriver(driver.VolumeDriver):
             model_update = {'status': 'deleted'}
 
             return model_update, snapshots
+
+    def manage_existing(self, volume, existing_ref):
+        """Brings an existing backend storage object under Cinder management.
+
+        existing_ref is passed straight through from the API request's
+        manage_existing_ref value, and it is up to the driver how this should
+        be interpreted.  It should be sufficient to identify a storage object
+        that the driver should somehow associate with the newly-created cinder
+        volume structure.
+
+        There are two ways to do this:
+
+        1. Rename the backend storage object so that it matches the,
+           volume['name'] which is how drivers traditionally map between a
+           cinder volume and the associated backend storage object.
+
+        2. Place some metadata on the volume, or somewhere in the backend, that
+           allows other driver requests (e.g. delete, clone, attach, detach...)
+           to locate the backend storage object when required.
+
+        If the existing_ref doesn't make sense, or doesn't refer to an existing
+        backend storage object, raise a ManageExistingInvalidReference
+        exception.
+
+        The volume may have a volume_type, and the driver can inspect that and
+        compare against the properties of the referenced backend storage
+        object.  If they are incompatible, raise a
+        ManageExistingVolumeTypeMismatch, specifying a reason for the failure.
+
+        :param volume:       Cinder volume to manage
+        :param existing_ref: Driver-specific information used to identify a
+        volume
+        """
+        if existing_ref.get('source-name') or existing_ref.get('source-id'):
+            with self._client.open_connection() as api:
+                api.manage_existing(volume['id'], existing_ref)
+        else:
+            raise exception.ManageExistingInvalidReference(
+                _('Must specify source-name or source-id. (%s)') %
+                existing_ref)
+
+    def manage_existing_get_size(self, volume, existing_ref):
+        """Return size of volume to be managed by manage_existing.
+
+        When calculating the size, round up to the next GB.
+
+        :param volume:       Cinder volume to manage
+        :param existing_ref: Driver-specific information used to identify a
+        volume
+        """
+        if existing_ref.get('source-name') or existing_ref.get('source-id'):
+            with self._client.open_connection() as api:
+                return api.get_unmanaged_volume_size(existing_ref)
+        else:
+            raise exception.ManageExistingInvalidReference(
+                _('Must specify source-name or source-id. (%s)') %
+                existing_ref)
+
+    def unmanage(self, volume):
+        """Removes the specified volume from Cinder management.
+
+        Does not delete the underlying backend storage object.
+
+        For most drivers, this will not need to do anything.  However, some
+        drivers might use this call as an opportunity to clean up any
+        Cinder-specific configuration that they have associated with the
+        backend storage object.
+
+        :param volume: Cinder volume to unmanage
+        """
+        with self._client.open_connection() as api:
+            scvolume = api.find_volume(volume['id'])
+            if scvolume:
+                api.unmanage(scvolume)
index fa727b1f1f7d5fe2694b6c7dddd75e297643b4d6..6944d5bd7d06f13dceae960f90e1e3ed09ab1a1a 100644 (file)
@@ -38,9 +38,12 @@ class DellStorageCenterFCDriver(dell_storagecenter_common.DellCommonDriver,
         1.0.0 - Initial driver
         1.1.0 - Added extra spec support for Storage Profile selection
         1.2.0 - Added consistency group support.
+        2.0.0 - Switched to inheriting functional objects rather than volume
+                driver.
+        2.1.0 - Added support for ManageableVD.
     '''
 
-    VERSION = '1.2.0'
+    VERSION = '2.1.0'
 
     def __init__(self, *args, **kwargs):
         super(DellStorageCenterFCDriver, self).__init__(*args, **kwargs)
index 4ee93974e1d42bbfdc10e89667fc26bcc84aa54a..f6b290ca90648ab89c77a3e6f8ab1d863a9d57e3 100644 (file)
@@ -36,9 +36,12 @@ class DellStorageCenterISCSIDriver(dell_storagecenter_common.DellCommonDriver,
         1.0.0 - Initial driver
         1.1.0 - Added extra spec support for Storage Profile selection
         1.2.0 - Added consistency group support.
+        2.0.0 - Switched to inheriting functional objects rather than volume
+                driver.
+        2.1.0 - Added support for ManageableVD.
     '''
 
-    VERSION = '1.2.0'
+    VERSION = '2.1.0'
 
     def __init__(self, *args, **kwargs):
         super(DellStorageCenterISCSIDriver, self).__init__(*args, **kwargs)