]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add manage/unmanage volume support for Nimble
authorSonia Ghanekar <sonia.ghanekar@nimblestorage.com>
Wed, 26 Aug 2015 17:08:53 +0000 (10:08 -0700)
committerSonia Ghanekar <sonia.ghanekar@nimblestorage.com>
Wed, 26 Aug 2015 17:08:53 +0000 (10:08 -0700)
This patch adds support for volume manage/unmanage for a Nimble
ISCSI driver. It uses an additional field 'agent-type' on the
backend for distinguishing volumes managed by the Nimble driver.
While managing an existing volume it sets the agent-type to
Openstack and while unmanaging sets it back to None.

Since this field was not used in the earlier versions, in order
to be backward compatible, during the initialization of the driver,
the 'agent-type' is updated to Openstack for all the existing
Nimble volumes.

Implements: blueprint nimble-add-volume-manage-unmanage-support
Change-Id: Ia960f2a2911288409977324cfc287c216b60b589

cinder/tests/unit/test_nimble.py
cinder/volume/drivers/nimble.py

index f32a73459afc9115af6e6f3451eeccf2877ac3d5..ceda31bbafa288b951aff753747bdc452a994d28 100755 (executable)
@@ -19,6 +19,7 @@ import mock
 from oslo_config import cfg
 
 from cinder import exception
+from cinder.objects import volume as obj_volume
 from cinder import test
 from cinder.volume.drivers import nimble
 from cinder.volume import volume_types
@@ -109,9 +110,30 @@ FAKE_IGROUP_LIST_RESPONSE = {
         {'initiator-list': [{'name': 'test-initiator1'}],
          'name': 'test-igrp2'}]}
 
-FAKE_GET_VOL_INFO_RESPONSE = {'err-list': {'err-list':
-                                           [{'code': 0}]},
-                              'vol': {'target-name': 'iqn.test'}}
+FAKE_GET_VOL_INFO_RESPONSE = {
+    'err-list': {'err-list': [{'code': 0}]},
+    'vol': {'target-name': 'iqn.test',
+            'name': 'test_vol',
+            'agent-type': 1,
+            'online': False}}
+
+FAKE_GET_VOL_INFO_ONLINE = {
+    'err-list': {'err-list': [{'code': 0}]},
+    'vol': {'target-name': 'iqn.test',
+            'name': 'test_vol',
+            'agent-type': 1,
+            'online': True}}
+
+FAKE_GET_VOL_INFO_ERROR = {
+    'err-list': {'err-list': [{'code': 2}]},
+    'vol': {'target-name': 'iqn.test'}}
+
+FAKE_GET_VOL_INFO_RESPONSE_WITH_SET_AGENT_TYPE = {
+    'err-list': {'err-list': [{'code': 0}]},
+    'vol': {'target-name': 'iqn.test',
+            'name': 'test_vol',
+            'agent-type': 5}}
+
 
 FAKE_TYPE_ID = 12345
 
@@ -171,6 +193,8 @@ class NimbleDriverLoginTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_do_setup_positive(self):
@@ -191,6 +215,8 @@ class NimbleDriverLoginTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_expire_session_id(self):
@@ -225,6 +251,12 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @mock.patch.object(volume_types, 'get_volume_type_extra_specs',
+                       mock.Mock(type_id=FAKE_TYPE_ID, return_value={
+                                 'nimble:perfpol-name': 'default',
+                                 'nimble:encryption': 'yes'}))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_create_volume_positive(self):
@@ -250,11 +282,13 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
                          'online': True, 'pool-name': 'default',
                          'size': 1073741824, 'quota': 1073741824,
                          'perfpol-name': 'default', 'description': '',
-                         'encryptionAttr': {'cipher': 3}},
+                         'agent-type': 5, 'encryptionAttr': {'cipher': 3}},
                 'sid': 'a9b9aba7'})
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @mock.patch.object(volume_types, 'get_volume_type_extra_specs',
                        mock.Mock(type_id=FAKE_TYPE_ID, return_value={
                                  'nimble:perfpol-name': 'default',
@@ -289,11 +323,13 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
                          'online': True, 'pool-name': 'default',
                          'size': 1073741824, 'quota': 1073741824,
                          'perfpol-name': 'default', 'description': '',
-                         'encryptionAttr': {'cipher': 2}},
+                         'agent-type': 5, 'encryptionAttr': {'cipher': 2}},
                 'sid': 'a9b9aba7'})
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @mock.patch.object(volume_types, 'get_volume_type_extra_specs',
                        mock.Mock(type_id=FAKE_TYPE_ID, return_value={
                                  'nimble:perfpol-name': 'VMware ESX',
@@ -328,11 +364,13 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
                          'online': True, 'pool-name': 'default',
                          'size': 1073741824, 'quota': 1073741824,
                          'perfpol-name': 'VMware ESX', 'description': '',
-                         'encryptionAttr': {'cipher': 3}},
+                         'agent-type': 5, 'encryptionAttr': {'cipher': 3}},
                 'sid': 'a9b9aba7'})
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_create_volume_negative(self):
@@ -349,6 +387,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_create_volume_encryption_negative(self):
@@ -365,6 +405,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_create_volume_perfpolicy_negative(self):
@@ -381,6 +423,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_delete_volume(self):
@@ -402,6 +446,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_extend_volume(self):
@@ -420,6 +466,8 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*', False))
     @mock.patch(NIMBLE_RANDOM)
@@ -455,19 +503,174 @@ class NimbleDriverVolumeTestCase(NimbleDriverBaseTestCase):
                              'reserve': 5368709120,
                              'online': True,
                              'warn-level': 4294967296,
-                             'perfpol-name': 'default'},
+                             'perfpol-name': 'default',
+                             'agent-type': 5},
                     'name': 'testvolume',
                     'sid': 'a9b9aba7'})]
         self.mock_client_service.assert_has_calls(expected_calls)
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
+        'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
+    def test_manage_volume_positive(self):
+        self.mock_client_service.service.getNetConfig.return_value = (
+            FAKE_POSITIVE_NETCONFIG_RESPONSE)
+        self.mock_client_service.service.getVolInfo.return_value = (
+            FAKE_GET_VOL_INFO_RESPONSE)
+        self.mock_client_service.service.onlineVol.return_value = (
+            FAKE_GENERIC_POSITIVE_RESPONSE)
+        self.mock_client_service.service.editVol.return_value = (
+            FAKE_CREATE_VOLUME_POSITIVE_RESPONSE)
+        self.assertEqual({
+            'provider_location': '172.18.108.21:3260 iqn.test 0',
+            'provider_auth': None},
+            self.driver.manage_existing({'name': 'volume-abcdef'},
+                                        {'source-name': 'test-vol'}))
+        expected_calls = [
+            mock.call.service.editVol(
+                request={
+                    'attr': {
+                        'name': 'volume-abcdef', 'agent-type': 5},
+                    'mask': 262145,
+                    'name': 'test-vol',
+                    'sid': 'a9b9aba7'}),
+            mock.call.service.onlineVol(
+                request={'online': True,
+                         'name': 'volume-abcdef',
+                         'sid': 'a9b9aba7'}
+            )
+        ]
+        self.mock_client_service.assert_has_calls(expected_calls)
+
+    @mock.patch(NIMBLE_URLLIB2)
+    @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
+        'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
+    def test_manage_volume_which_is_online(self):
+        self.mock_client_service.service.getNetConfig.return_value = (
+            FAKE_POSITIVE_NETCONFIG_RESPONSE)
+        self.mock_client_service.service.getVolInfo.return_value = (
+            FAKE_GET_VOL_INFO_ONLINE)
+        self.assertRaises(
+            exception.InvalidVolume,
+            self.driver.manage_existing,
+            {'name': 'volume-abcdef'},
+            {'source-name': 'test-vol'})
+
+    @mock.patch(NIMBLE_URLLIB2)
+    @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
+        'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
+    def test_manage_volume_with_improper_ref(self):
+        self.assertRaises(
+            exception.ManageExistingInvalidReference,
+            self.driver.manage_existing,
+            {'name': 'volume-abcdef'},
+            {'source-id': 'test-vol'})
+
+    @mock.patch(NIMBLE_URLLIB2)
+    @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
+        'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
+    def test_manage_volume_with_nonexistant_volume(self):
+        self.mock_client_service.service.getVolInfo.return_value = (
+            FAKE_GET_VOL_INFO_ERROR)
+        self.assertRaises(
+            exception.VolumeBackendAPIException,
+            self.driver.manage_existing,
+            {'name': 'volume-abcdef'},
+            {'source-name': 'test-vol'})
+
+    @mock.patch(NIMBLE_URLLIB2)
+    @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
+        'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
+    def test_manage_volume_with_wrong_agent_type(self):
+        self.mock_client_service.service.getVolInfo.return_value = (
+            FAKE_GET_VOL_INFO_RESPONSE_WITH_SET_AGENT_TYPE)
+        self.assertRaises(
+            exception.ManageExistingAlreadyManaged,
+            self.driver.manage_existing,
+            {'id': 'abcdef', 'name': 'volume-abcdef'},
+            {'source-name': 'test-vol'})
+
+    @mock.patch(NIMBLE_URLLIB2)
+    @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
+        'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
+    def test_unmanage_volume_positive(self):
+        self.mock_client_service.service.getVolInfo.return_value = (
+            FAKE_GET_VOL_INFO_RESPONSE_WITH_SET_AGENT_TYPE)
+        self.mock_client_service.service.editVol.return_value = (
+            FAKE_CREATE_VOLUME_POSITIVE_RESPONSE)
+        self.driver.unmanage({'name': 'volume-abcdef'})
+        expected_calls = [
+            mock.call.service.editVol(
+                request={'attr': {'agent-type': 1},
+                         'mask': 262144,
+                         'name': 'volume-abcdef',
+                         'sid': 'a9b9aba7'}),
+            mock.call.service.onlineVol(
+                request={'online': False,
+                         'name': 'volume-abcdef',
+                         'sid': 'a9b9aba7'}
+            )
+        ]
+        self.mock_client_service.assert_has_calls(expected_calls)
+
+    @mock.patch(NIMBLE_URLLIB2)
+    @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
+        'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
+    def test_unmanage_with_invalid_volume(self):
+        self.mock_client_service.service.getVolInfo.return_value = (
+            FAKE_GET_VOL_INFO_ERROR)
+        self.assertRaises(
+            exception.VolumeBackendAPIException,
+            self.driver.unmanage,
+            {'name': 'volume-abcdef'}
+        )
+
+    @mock.patch(NIMBLE_URLLIB2)
+    @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
+    @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
+        'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
+    def test_unmanage_with_invalid_agent_type(self):
+        self.mock_client_service.service.getVolInfo.return_value = (
+            FAKE_GET_VOL_INFO_RESPONSE)
+        self.assertRaises(
+            exception.InvalidVolume,
+            self.driver.unmanage,
+            {'name': 'volume-abcdef'}
+        )
+
+    @mock.patch(NIMBLE_URLLIB2)
+    @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_get_volume_stats(self):
         self.mock_client_service.service.getGroupConfig.return_value = \
             FAKE_POSITIVE_GROUP_CONFIG_RESPONSE
-        expected_res = {'driver_version': '1.1.3',
+        expected_res = {'driver_version': '2.0.0',
                         'vendor_name': 'Nimble',
                         'volume_backend_name': 'NIMBLE',
                         'storage_protocol': 'iSCSI',
@@ -487,6 +690,8 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_create_snapshot(self):
@@ -507,6 +712,8 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_delete_snapshot(self):
@@ -530,6 +737,8 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_create_volume_from_snapshot(self):
@@ -557,7 +766,8 @@ class NimbleDriverSnapshotTestCase(NimbleDriverBaseTestCase):
                                   'online': True,
                                   'reserve': 0,
                                   'warn-level': 858993459,
-                                  'perfpol-name': 'default'},
+                                  'perfpol-name': 'default',
+                                  'agent-type': 5},
                          'name': 'testvolume',
                          'sid': 'a9b9aba7'}),
             mock.call.service.editVol(
@@ -578,6 +788,8 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_initialize_connection_igroup_exist(self):
@@ -617,6 +829,8 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     @mock.patch(NIMBLE_RANDOM)
@@ -659,6 +873,8 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_terminate_connection_positive(self):
@@ -684,6 +900,8 @@ class NimbleDriverConnectionTestCase(NimbleDriverBaseTestCase):
 
     @mock.patch(NIMBLE_URLLIB2)
     @mock.patch(NIMBLE_CLIENT)
+    @mock.patch.object(obj_volume.VolumeList, 'get_all',
+                       mock.Mock(return_value=[]))
     @NimbleDriverBaseTestCase.client_mock_decorator(create_configuration(
         'nimble', 'nimble_pass', '10.18.108.55', 'default', '*'))
     def test_terminate_connection_negative(self):
index 2c4eb4462444c5ed275465a06575bc45d827244a..50f9e8f20ed93a3e0fc6364f6ecb27eb63157343 100755 (executable)
@@ -32,12 +32,13 @@ from six.moves import urllib
 from suds import client
 
 from cinder import exception
-from cinder.i18n import _, _LE, _LI
+from cinder.i18n import _, _LE, _LI, _LW
+from cinder.objects import volume
 from cinder.volume.drivers.san import san
 from cinder.volume import volume_types
 
 
-DRIVER_VERSION = '1.1.3'
+DRIVER_VERSION = '2.0.0'
 AES_256_XTS_CIPHER = 2
 DEFAULT_CIPHER = 3
 EXTRA_SPEC_ENCRYPTION = 'nimble:encryption'
@@ -46,6 +47,10 @@ DEFAULT_PERF_POLICY_SETTING = 'default'
 DEFAULT_ENCRYPTION_SETTING = 'no'
 DEFAULT_SNAP_QUOTA = sys.maxsize
 VOL_EDIT_MASK = 4 + 16 + 32 + 64 + 256 + 512
+MANAGE_EDIT_MASK = 1 + 262144
+UNMANAGE_EDIT_MASK = 262144
+AGENT_TYPE_OPENSTACK = 5
+AGENT_TYPE_NONE = 1
 SOAP_PORT = 5391
 SM_ACL_APPLY_TO_BOTH = 3
 SM_ACL_CHAP_USER_ANY = '*'
@@ -83,10 +88,11 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
 
     Version history:
         1.0 - Initial driver
-        1.1.0 - Added Extra Spec Capability
         1.1.1 - Updated VERSION to Nimble driver version
         1.1.2 - Update snap-quota to unlimited
-        1.1.3 - Correct capacity reporting
+        2.0.0 - Added Extra Spec Capability
+                Correct capacity reporting
+                Added Manage/Unmanage volume support
     """
 
     VERSION = DRIVER_VERSION
@@ -142,6 +148,23 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
         else:
             raise NimbleDriverException(_('No suitable discovery ip found'))
 
+    def _update_existing_vols_agent_type(self, context):
+        LOG.debug("Updating existing volumes to have "
+                  "agent_type = 'OPENSTACK'")
+        backend_name = self.configuration.safe_get('volume_backend_name')
+        all_vols = volume.VolumeList.get_all(
+            context, None, None, None, None, {'status': 'available'})
+        for vol in all_vols:
+            if backend_name in vol.host:
+                try:
+                    self.APIExecutor.edit_vol(
+                        vol.name,
+                        UNMANAGE_EDIT_MASK,
+                        {'agent-type': AGENT_TYPE_OPENSTACK})
+                except NimbleAPIException:
+                    LOG.warning(_LW('Error updating agent-type for '
+                                    'volume %s.'), vol.name)
+
     def do_setup(self, context):
         """Setup the Nimble Cinder volume driver."""
         self._check_config()
@@ -156,6 +179,7 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
                           'Check san_ip, username, password'
                           ' and make sure the array version is compatible'))
             raise
+        self._update_existing_vols_agent_type(context)
 
     def _get_provider_location(self, volume_name):
         """Get volume iqn for initiator access."""
@@ -312,6 +336,99 @@ class NimbleISCSIDriver(san.SanISCSIDriver):
              'quota': vol_size,
              'snap-quota': DEFAULT_SNAP_QUOTA})
 
+    def _get_existing_volume_ref_name(self, existing_ref):
+        """Returns the volume name of an existing ref"""
+
+        vol_name = None
+        if 'source-name' in existing_ref:
+            vol_name = existing_ref['source-name']
+        else:
+            reason = _("Reference must contain source-name.")
+            raise exception.ManageExistingInvalidReference(
+                existing_ref=existing_ref,
+                reason=reason)
+
+        return vol_name
+
+    def manage_existing(self, volume, external_ref):
+        """Manage an existing nimble volume (import to cinder)"""
+
+        # Get the volume name from the external reference
+        target_vol_name = self._get_existing_volume_ref_name(external_ref)
+        LOG.debug('Entering manage_existing. '
+                  'Target_volume_name = %s', target_vol_name)
+
+        # Get vol info from the volume name obtained from the reference
+        vol_info = self.APIExecutor.get_vol_info(target_vol_name)
+
+        # Check if volume is already managed by Openstack
+        if vol_info['agent-type'] == AGENT_TYPE_OPENSTACK:
+            msg = (_('Volume %s is already managed by Openstack.')
+                   % target_vol_name)
+            raise exception.ManageExistingAlreadyManaged(
+                volume_ref=volume['id'])
+
+        # If agent-type is not None then raise exception
+        if vol_info['agent-type'] != AGENT_TYPE_NONE:
+            msg = (_('Volume should have agent-type set as None.'))
+            raise exception.InvalidVolume(reason=msg)
+
+        new_vol_name = volume['name']
+
+        if vol_info['online']:
+            msg = (_('Volume %s is online. Set volume to offline for '
+                     'managing using Openstack.') % target_vol_name)
+            raise exception.InvalidVolume(reason=msg)
+
+        # edit the volume
+        self.APIExecutor.edit_vol(target_vol_name,
+                                  MANAGE_EDIT_MASK,
+                                  {'name': new_vol_name,
+                                   'agent-type': AGENT_TYPE_OPENSTACK})
+
+        # make the volume online after rename
+        self.APIExecutor.online_vol(new_vol_name, True, ignore_list=[
+            'SM-enoent'])
+
+        return self._get_model_info(new_vol_name)
+
+    def manage_existing_get_size(self, volume, external_ref):
+        """Return size of an existing volume"""
+
+        LOG.debug('Volume name : %(name)s  External ref : %(ref)s',
+                  {'name': volume['name'], 'ref': external_ref})
+
+        target_vol_name = self._get_existing_volume_ref_name(external_ref)
+
+        # get vol info
+        vol_info = self.APIExecutor.get_vol_info(target_vol_name)
+
+        LOG.debug('Volume size : %(size)s  Volume-name : %(name)s',
+                  {'size': vol_info['size'], 'name': vol_info['name']})
+
+        return int(vol_info['size'] / units.Gi)
+
+    def unmanage(self, volume):
+        """Removes the specified volume from Cinder management."""
+
+        vol_name = volume['name']
+        LOG.info(_LI("Entering unmanage_volume volume = %s"), vol_name)
+
+        # check agent type
+        vol_info = self.APIExecutor.get_vol_info(vol_name)
+        if vol_info['agent-type'] != AGENT_TYPE_OPENSTACK:
+            msg = (_('Only volumes managed by Openstack can be unmanaged.'))
+            raise exception.InvalidVolume(reason=msg)
+
+        # update the agent-type to None
+        self.APIExecutor.edit_vol(vol_name,
+                                  UNMANAGE_EDIT_MASK,
+                                  {'agent-type': AGENT_TYPE_NONE})
+
+        # offline the volume
+        self.APIExecutor.online_vol(vol_name, False, ignore_list=[
+            'SM-enoent'])
+
     def _create_igroup_for_initiator(self, initiator_name):
         """Creates igroup for an initiator and returns the igroup name."""
         igrp_name = 'openstack-' + self._generate_random_string(12)
@@ -527,7 +644,8 @@ class NimbleAPIExecutor(object):
                   ' reserve=%(reserve)s in pool=%(pool)s'
                   ' description=%(description)s with Extra Specs'
                   ' perfpol-name=%(perfpol-name)s'
-                  ' encryption=%(encryption)s cipher=%(cipher)s',
+                  ' encryption=%(encryption)s cipher=%(cipher)s'
+                  ' agent-type=%(agent-type)s',
                   {'vol': volume['name'],
                    'size': volume_size,
                    'reserve': reserve,
@@ -535,7 +653,8 @@ class NimbleAPIExecutor(object):
                    'description': description,
                    'perfpol-name': perf_policy_name,
                    'encryption': encrypt,
-                   'cipher': cipher})
+                   'cipher': cipher,
+                   'agent-type': AGENT_TYPE_OPENSTACK})
 
         return self.client.service.createVol(
             request={'sid': self.sid,
@@ -548,6 +667,7 @@ class NimbleAPIExecutor(object):
                               'snap-quota': DEFAULT_SNAP_QUOTA,
                               'online': True,
                               'pool-name': pool_name,
+                              'agent-type': AGENT_TYPE_OPENSTACK,
                               'perfpol-name': perf_policy_name,
                               'encryptionAttr': {'cipher': cipher}}})
 
@@ -695,12 +815,13 @@ class NimbleAPIExecutor(object):
         reserve_size = snap_size * units.Gi if reserve else 0
         LOG.info(_LI('Cloning volume from snapshot volume=%(vol)s '
                      'snapshot=%(snap)s clone=%(clone)s snap_size=%(size)s'
-                     'reserve=%(reserve)s'),
+                     'reserve=%(reserve)s' 'agent-type=%(agent-type)s'),
                  {'vol': volume_name,
                   'snap': snap_name,
                   'clone': clone_name,
                   'size': snap_size,
-                  'reserve': reserve})
+                  'reserve': reserve,
+                  'agent-type': AGENT_TYPE_OPENSTACK})
         clone_size = snap_size * units.Gi
         return self.client.service.cloneVol(
             request={'sid': self.sid,
@@ -711,7 +832,8 @@ class NimbleAPIExecutor(object):
                               'warn-level': int(clone_size * WARN_LEVEL),
                               'quota': clone_size,
                               'snap-quota': DEFAULT_SNAP_QUOTA,
-                              'online': True},
+                              'online': True,
+                              'agent-type': AGENT_TYPE_OPENSTACK},
                      'snap-name': snap_name})
 
     @_connection_checker