]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add volume RPC API v2.0
authorMichał Dulko <michal.dulko@intel.com>
Wed, 17 Feb 2016 11:57:10 +0000 (12:57 +0100)
committerMichał Dulko <michal.dulko@intel.com>
Fri, 11 Mar 2016 08:45:40 +0000 (09:45 +0100)
This patch creates volume RPC API version 2.0, while retaining
compatibility in rpcapi and manager for 1.x, allowing for continuous
deployment scenarios.

This should be merged just before the Mitaka release.

UpgradeImpact - Deployments doing continous deployment should not
upgrade into Newton before doing an upgrade which includes all the
Mitaka's RPC API version bump commits (scheduler, volume, backup).

Change-Id: Ic7df6fdc1ce8ab0b3782161a6b6769f45883dba6
Related-Blueprint: rpc-object-compatibility

cinder/tests/unit/test_volume_rpcapi.py
cinder/volume/manager.py
cinder/volume/rpcapi.py

index ac94866a6ccf0cee33e722c91ffcc29e4bdc8928..04e83d04b4f1b3e41464c33d22fb6839f6702a35 100644 (file)
@@ -19,7 +19,6 @@ import copy
 
 import mock
 from oslo_config import cfg
-import oslo_messaging as messaging
 from oslo_serialization import jsonutils
 
 from cinder import context
@@ -222,25 +221,52 @@ class VolumeRpcAPITestCase(test.TestCase):
             else:
                 self.assertEqual(expected_msg[kwarg], value)
 
-    def test_create_consistencygroup(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_create_consistencygroup(self, mock_can_send_version):
+        self._test_volume_api('create_consistencygroup', rpc_method='cast',
+                              group=self.fake_cg, host='fake_host1',
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('create_consistencygroup', rpc_method='cast',
                               group=self.fake_cg, host='fake_host1',
                               version='1.26')
 
-    def test_delete_consistencygroup(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_delete_consistencygroup(self, mock_can_send_version):
+        self._test_volume_api('delete_consistencygroup', rpc_method='cast',
+                              group=self.fake_cg, version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('delete_consistencygroup', rpc_method='cast',
                               group=self.fake_cg, version='1.26')
 
-    def test_update_consistencygroup(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_update_consistencygroup(self, mock_can_send_version):
+        self._test_volume_api('update_consistencygroup', rpc_method='cast',
+                              group=self.fake_cg, add_volumes=['vol1'],
+                              remove_volumes=['vol2'], version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('update_consistencygroup', rpc_method='cast',
                               group=self.fake_cg, add_volumes=['vol1'],
                               remove_volumes=['vol2'], version='1.26')
 
-    def test_create_cgsnapshot(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_create_cgsnapshot(self, mock_can_send_version):
+        self._test_volume_api('create_cgsnapshot', rpc_method='cast',
+                              cgsnapshot=self.fake_cgsnap, version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('create_cgsnapshot', rpc_method='cast',
                               cgsnapshot=self.fake_cgsnap, version='1.31')
 
-    def test_delete_cgsnapshot(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_delete_cgsnapshot(self, mock_can_send_version):
+        self._test_volume_api('delete_cgsnapshot', rpc_method='cast',
+                              cgsnapshot=self.fake_cgsnap, version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('delete_cgsnapshot', rpc_method='cast',
                               cgsnapshot=self.fake_cgsnap, version='1.31')
 
@@ -254,8 +280,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               request_spec='fake_request_spec',
                               filter_properties='fake_properties',
                               allow_reschedule=True,
-                              version='1.32')
-        can_send_version.assert_called_once_with('1.32')
+                              version='2.0')
+        can_send_version.assert_called_once_with('2.0')
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=False)
@@ -269,7 +295,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               filter_properties='fake_properties',
                               allow_reschedule=True,
                               version='1.24')
-        can_send_version.assert_called_once_with('1.32')
+        can_send_version.assert_has_calls([mock.call('2.0'),
+                                           mock.call('1.32')])
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=True)
@@ -282,8 +309,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               request_spec=request_spec,
                               filter_properties='fake_properties',
                               allow_reschedule=True,
-                              version='1.32')
-        can_send_version.assert_called_once_with('1.32')
+                              version='2.0')
+        can_send_version.assert_called_once_with('2.0')
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=True)
@@ -293,8 +320,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               volume=self.fake_volume_obj,
                               unmanage_only=False,
                               cascade=False,
-                              version='1.40')
-        can_send_version.assert_any_call('1.40')
+                              version='2.0')
+        can_send_version.assert_called_once_with('2.0')
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=False)
@@ -304,7 +331,9 @@ class VolumeRpcAPITestCase(test.TestCase):
                               volume=self.fake_volume_obj,
                               unmanage_only=False,
                               version='1.15')
-        can_send_version.assert_any_call('1.33')
+        can_send_version.assert_has_calls([mock.call('2.0'),
+                                           mock.call('1.40'),
+                                           mock.call('1.33')])
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=True)
@@ -314,18 +343,34 @@ class VolumeRpcAPITestCase(test.TestCase):
                               volume=self.fake_volume_obj,
                               unmanage_only=False,
                               cascade=True,
-                              version='1.40')
-        can_send_version.assert_any_call('1.33')
-        can_send_version.assert_any_call('1.40')
+                              version='2.0')
+        can_send_version.assert_any_call('2.0')
 
-    def test_create_snapshot(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_create_snapshot(self, mock_can_send_version):
+        self._test_volume_api('create_snapshot',
+                              rpc_method='cast',
+                              volume=self.fake_volume,
+                              snapshot=self.fake_snapshot,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('create_snapshot',
                               rpc_method='cast',
                               volume=self.fake_volume,
                               snapshot=self.fake_snapshot,
                               version='1.20')
 
-    def test_delete_snapshot(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_delete_snapshot(self, mock_can_send_version):
+        self._test_volume_api('delete_snapshot',
+                              rpc_method='cast',
+                              snapshot=self.fake_snapshot,
+                              host='fake_host',
+                              unmanage_only=False,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('delete_snapshot',
                               rpc_method='cast',
                               snapshot=self.fake_snapshot,
@@ -333,7 +378,16 @@ class VolumeRpcAPITestCase(test.TestCase):
                               unmanage_only=False,
                               version='1.20')
 
-    def test_delete_snapshot_with_unmanage_only(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_delete_snapshot_with_unmanage_only(self, mock_can_send_version):
+        self._test_volume_api('delete_snapshot',
+                              rpc_method='cast',
+                              snapshot=self.fake_snapshot,
+                              host='fake_host',
+                              unmanage_only=True,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('delete_snapshot',
                               rpc_method='cast',
                               snapshot=self.fake_snapshot,
@@ -341,7 +395,18 @@ class VolumeRpcAPITestCase(test.TestCase):
                               unmanage_only=True,
                               version='1.20')
 
-    def test_attach_volume_to_instance(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_attach_volume_to_instance(self, mock_can_send_version):
+        self._test_volume_api('attach_volume',
+                              rpc_method='call',
+                              volume=self.fake_volume,
+                              instance_uuid='fake_uuid',
+                              host_name=None,
+                              mountpoint='fake_mountpoint',
+                              mode='ro',
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('attach_volume',
                               rpc_method='call',
                               volume=self.fake_volume,
@@ -351,7 +416,18 @@ class VolumeRpcAPITestCase(test.TestCase):
                               mode='ro',
                               version='1.11')
 
-    def test_attach_volume_to_host(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_attach_volume_to_host(self, mock_can_send_version):
+        self._test_volume_api('attach_volume',
+                              rpc_method='call',
+                              volume=self.fake_volume,
+                              instance_uuid=None,
+                              host_name='fake_host',
+                              mountpoint='fake_mountpoint',
+                              mode='rw',
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('attach_volume',
                               rpc_method='call',
                               volume=self.fake_volume,
@@ -361,14 +437,32 @@ class VolumeRpcAPITestCase(test.TestCase):
                               mode='rw',
                               version='1.11')
 
-    def test_detach_volume(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_detach_volume(self, mock_can_send_version):
+        self._test_volume_api('detach_volume',
+                              rpc_method='call',
+                              volume=self.fake_volume,
+                              attachment_id='fake_uuid',
+                              version="2.0")
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('detach_volume',
                               rpc_method='call',
                               volume=self.fake_volume,
                               attachment_id='fake_uuid',
                               version="1.20")
 
-    def test_copy_volume_to_image(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_copy_volume_to_image(self, mock_can_send_version):
+        self._test_volume_api('copy_volume_to_image',
+                              rpc_method='cast',
+                              volume=self.fake_volume,
+                              image_meta={'id': 'fake_image_id',
+                                          'container_format': 'fake_type',
+                                          'disk_format': 'fake_type'},
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('copy_volume_to_image',
                               rpc_method='cast',
                               volume=self.fake_volume,
@@ -377,14 +471,31 @@ class VolumeRpcAPITestCase(test.TestCase):
                                           'disk_format': 'fake_type'},
                               version='1.3')
 
-    def test_initialize_connection(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_initialize_connection(self, mock_can_send_version):
+        self._test_volume_api('initialize_connection',
+                              rpc_method='call',
+                              volume=self.fake_volume,
+                              connector='fake_connector',
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('initialize_connection',
                               rpc_method='call',
                               volume=self.fake_volume,
                               connector='fake_connector',
                               version='1.0')
 
-    def test_terminate_connection(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_terminate_connection(self, mock_can_send_version):
+        self._test_volume_api('terminate_connection',
+                              rpc_method='call',
+                              volume=self.fake_volume,
+                              connector='fake_connector',
+                              force=False,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('terminate_connection',
                               rpc_method='call',
                               volume=self.fake_volume,
@@ -392,7 +503,18 @@ class VolumeRpcAPITestCase(test.TestCase):
                               force=False,
                               version='1.0')
 
-    def test_accept_transfer(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_accept_transfer(self, mock_can_send_version):
+        self._test_volume_api('accept_transfer',
+                              rpc_method='call',
+                              volume=self.fake_volume,
+                              new_user='e5565fd0-06c8-11e3-'
+                                       '8ffd-0800200c9b77',
+                              new_project='e4465fd0-06c8-11e3'
+                                          '-8ffd-0800200c9a66',
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('accept_transfer',
                               rpc_method='call',
                               volume=self.fake_volume,
@@ -410,8 +532,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               volume=self.fake_volume_obj,
                               new_size=1,
                               reservations=self.fake_reservations,
-                              version='1.35')
-        can_send_version.assert_called_once_with('1.35')
+                              version='2.0')
+        can_send_version.assert_called_once_with('2.0')
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=False)
@@ -422,7 +544,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               new_size=1,
                               reservations=self.fake_reservations,
                               version='1.14')
-        can_send_version.assert_called_once_with('1.35')
+        can_send_version.assert_has_calls([mock.call('2.0'),
+                                           mock.call('1.35')])
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=True)
@@ -437,8 +560,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               volume=self.fake_volume_obj,
                               dest_host=dest_host,
                               force_host_copy=True,
-                              version='1.36')
-        can_send_version.assert_called_once_with('1.36')
+                              version='2.0')
+        can_send_version.assert_called_once_with('2.0')
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=False)
@@ -454,7 +577,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               dest_host=dest_host,
                               force_host_copy=True,
                               version='1.8')
-        can_send_version.assert_called_once_with('1.36')
+        can_send_version.assert_has_calls([mock.call('2.0'),
+                                           mock.call('1.36')])
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=True)
@@ -464,8 +588,8 @@ class VolumeRpcAPITestCase(test.TestCase):
                               volume=self.fake_volume_obj,
                               new_volume=self.fake_volume_obj,
                               error=False,
-                              version='1.36')
-        can_send_version.assert_called_once_with('1.36')
+                              version='2.0')
+        can_send_version.assert_called_once_with('2.0')
 
     @mock.patch('oslo_messaging.RPCClient.can_send_version',
                 return_value=False)
@@ -476,12 +600,33 @@ class VolumeRpcAPITestCase(test.TestCase):
                               new_volume=self.fake_volume_obj,
                               error=False,
                               version='1.10')
-        can_send_version.assert_called_once_with('1.36')
+        can_send_version.assert_has_calls([mock.call('2.0'),
+                                           mock.call('1.36')])
 
-    @mock.patch('oslo_messaging.RPCClient.can_send_version',
-                return_value=True)
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
     @mock.patch('cinder.quota.DbQuotaDriver.rollback')
     def test_retype(self, rollback, can_send_version):
+        class FakeHost(object):
+            def __init__(self):
+                self.host = 'host'
+                self.capabilities = {}
+        dest_host = FakeHost()
+        self._test_volume_api('retype',
+                              rpc_method='cast',
+                              volume=self.fake_volume_obj,
+                              new_type_id='fake',
+                              dest_host=dest_host,
+                              migration_policy='never',
+                              reservations=self.fake_reservations,
+                              old_reservations=self.fake_reservations,
+                              version='2.0')
+        rollback.assert_not_called()
+        can_send_version.assert_called_once_with('2.0')
+
+    @mock.patch('oslo_messaging.RPCClient.can_send_version',
+                side_effect=[False, True])
+    @mock.patch('cinder.quota.DbQuotaDriver.rollback')
+    def test_retype_137(self, rollback, can_send_version):
         class FakeHost(object):
             def __init__(self):
                 self.host = 'host'
@@ -497,62 +642,71 @@ class VolumeRpcAPITestCase(test.TestCase):
                               old_reservations=self.fake_reservations,
                               version='1.37')
         rollback.assert_not_called()
-        can_send_version.assert_called_once_with('1.37')
+        can_send_version.assert_any_call('2.0')
+        can_send_version.assert_any_call('1.37')
 
     @mock.patch('cinder.quota.DbQuotaDriver.rollback')
-    def test_retype_version_134(self, rollback):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version',
+                side_effect=[False, False, True])
+    def test_retype_version_134(self, can_send_version, rollback):
         class FakeHost(object):
             def __init__(self):
                 self.host = 'host'
                 self.capabilities = {}
         dest_host = FakeHost()
-        with mock.patch.object(messaging.RPCClient,
-                               'can_send_version',
-                               side_effect=[False, True]) as can_send_version:
-            self._test_volume_api('retype',
-                                  rpc_method='cast',
-                                  volume=self.fake_volume_obj,
-                                  new_type_id='fake',
-                                  dest_host=dest_host,
-                                  migration_policy='never',
-                                  reservations=self.fake_reservations,
-                                  old_reservations=self.fake_reservations,
-                                  version='1.34')
+        self._test_volume_api('retype',
+                              rpc_method='cast',
+                              volume=self.fake_volume_obj,
+                              new_type_id='fake',
+                              dest_host=dest_host,
+                              migration_policy='never',
+                              reservations=self.fake_reservations,
+                              old_reservations=self.fake_reservations,
+                              version='1.34')
         self.assertTrue(rollback.called)
+        can_send_version.assert_any_call('2.0')
         can_send_version.assert_any_call('1.37')
         can_send_version.assert_any_call('1.34')
 
     @mock.patch('cinder.quota.DbQuotaDriver.rollback')
-    def test_retype_version_112(self, rollback):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version',
+                side_effect=[False, False, False])
+    def test_retype_version_112(self, can_send_version, rollback):
         class FakeHost(object):
             def __init__(self):
                 self.host = 'host'
                 self.capabilities = {}
         dest_host = FakeHost()
-        with mock.patch.object(messaging.RPCClient,
-                               'can_send_version',
-                               side_effect=[False, False]) as can_send_version:
-            self._test_volume_api('retype',
-                                  rpc_method='cast',
-                                  volume=self.fake_volume_obj,
-                                  new_type_id='fake',
-                                  dest_host=dest_host,
-                                  migration_policy='never',
-                                  reservations=self.fake_reservations,
-                                  old_reservations=self.fake_reservations,
-                                  version='1.12')
-            self.assertTrue(rollback.called)
-            can_send_version.assert_any_call('1.37')
-            can_send_version.assert_any_call('1.34')
-
-    def test_manage_existing(self):
+        self._test_volume_api('retype',
+                              rpc_method='cast',
+                              volume=self.fake_volume_obj,
+                              new_type_id='fake',
+                              dest_host=dest_host,
+                              migration_policy='never',
+                              reservations=self.fake_reservations,
+                              old_reservations=self.fake_reservations,
+                              version='1.12')
+        self.assertTrue(rollback.called)
+        can_send_version.assert_any_call('1.37')
+        can_send_version.assert_any_call('1.34')
+
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_manage_existing(self, mock_can_send_version):
+        self._test_volume_api('manage_existing',
+                              rpc_method='cast',
+                              volume=self.fake_volume,
+                              ref={'lv_name': 'foo'},
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('manage_existing',
                               rpc_method='cast',
                               volume=self.fake_volume,
                               ref={'lv_name': 'foo'},
                               version='1.15')
 
-    def test_manage_existing_snapshot(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_manage_existing_snapshot(self, mock_can_send_version):
         volume_update = {'host': 'fake_host'}
         snpshot = {
             'id': fake.snapshot_id,
@@ -566,6 +720,14 @@ class VolumeRpcAPITestCase(test.TestCase):
             'expected_attrs': ['volume'], }
         my_fake_snapshot_obj = fake_snapshot.fake_snapshot_obj(self.context,
                                                                **snpshot)
+        self._test_volume_api('manage_existing_snapshot',
+                              rpc_method='cast',
+                              snapshot=my_fake_snapshot_obj,
+                              ref='foo',
+                              host='fake_host',
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('manage_existing_snapshot',
                               rpc_method='cast',
                               snapshot=my_fake_snapshot_obj,
@@ -573,33 +735,74 @@ class VolumeRpcAPITestCase(test.TestCase):
                               host='fake_host',
                               version='1.28')
 
-    def test_promote_replica(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_promote_replica(self, mock_can_send_version):
+        self._test_volume_api('promote_replica',
+                              rpc_method='cast',
+                              volume=self.fake_volume,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('promote_replica',
                               rpc_method='cast',
                               volume=self.fake_volume,
                               version='1.17')
 
-    def test_reenable_replica(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_reenable_replica(self, mock_can_send_version):
+        self._test_volume_api('reenable_replication',
+                              rpc_method='cast',
+                              volume=self.fake_volume,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('reenable_replication',
                               rpc_method='cast',
                               volume=self.fake_volume,
                               version='1.17')
 
-    def test_freeze_host(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_freeze_host(self, mock_can_send_version):
+        self._test_volume_api('freeze_host', rpc_method='call',
+                              host='fake_host', version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('freeze_host', rpc_method='call',
                               host='fake_host', version='1.39')
 
-    def test_thaw_host(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_thaw_host(self, mock_can_send_version):
+        self._test_volume_api('thaw_host', rpc_method='call', host='fake_host',
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('thaw_host', rpc_method='call', host='fake_host',
                               version='1.39')
 
-    def test_failover_host(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_failover_host(self, mock_can_send_version):
+        self._test_volume_api('failover_host', rpc_method='cast',
+                              host='fake_host',
+                              secondary_backend_id='fake_backend',
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('failover_host', rpc_method='cast',
                               host='fake_host',
                               secondary_backend_id='fake_backend',
                               version='1.39')
 
-    def test_create_consistencygroup_from_src_cgsnapshot(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_create_consistencygroup_from_src_cgsnapshot(
+            self, mock_can_send_version):
+        self._test_volume_api('create_consistencygroup_from_src',
+                              rpc_method='cast',
+                              group=self.fake_cg,
+                              cgsnapshot=self.fake_cgsnap,
+                              source_cg=None,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('create_consistencygroup_from_src',
                               rpc_method='cast',
                               group=self.fake_cg,
@@ -607,7 +810,16 @@ class VolumeRpcAPITestCase(test.TestCase):
                               source_cg=None,
                               version='1.31')
 
-    def test_create_consistencygroup_from_src_cg(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_create_consistencygroup_from_src_cg(self, mock_can_send_version):
+        self._test_volume_api('create_consistencygroup_from_src',
+                              rpc_method='cast',
+                              group=self.fake_cg2,
+                              cgsnapshot=None,
+                              source_cg=self.fake_src_cg,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('create_consistencygroup_from_src',
                               rpc_method='cast',
                               group=self.fake_cg2,
@@ -615,14 +827,29 @@ class VolumeRpcAPITestCase(test.TestCase):
                               source_cg=self.fake_src_cg,
                               version='1.31')
 
-    def test_get_capabilities(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_get_capabilities(self, mock_can_send_version):
+        self._test_volume_api('get_capabilities',
+                              rpc_method='call',
+                              host='fake_host',
+                              discover=True,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('get_capabilities',
                               rpc_method='call',
                               host='fake_host',
                               discover=True,
                               version='1.29')
 
-    def test_remove_export(self):
+    @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True)
+    def test_remove_export(self, mock_can_send_version):
+        self._test_volume_api('remove_export',
+                              rpc_method='cast',
+                              volume=self.fake_volume,
+                              version='2.0')
+
+        mock_can_send_version.return_value = False
         self._test_volume_api('remove_export',
                               rpc_method='cast',
                               volume=self.fake_volume,
index ff89cf757e58d2f65b5a2d9faef369749c830ca9..0a67b3629f41bbef4a986c36fe6c6c06bcfe4a50 100644 (file)
@@ -233,6 +233,7 @@ class VolumeManager(manager.SchedulerDependentManager):
         # update_service_capabilities needs service_name to be volume
         super(VolumeManager, self).__init__(service_name='volume',
                                             *args, **kwargs)
+        self.additional_endpoints.append(_VolumeV2Proxy(self))
         self.configuration = config.Configuration(volume_manager_opts,
                                                   config_group=service_name)
         self.stats = {}
@@ -3468,3 +3469,148 @@ class VolumeManager(manager.SchedulerDependentManager):
     def secure_file_operations_enabled(self, ctxt, volume):
         secure_enabled = self.driver.secure_file_operations_enabled()
         return secure_enabled
+
+
+# TODO(dulek): This goes away immediately in Newton and is just present in
+# Mitaka so that we can receive v1.x and v2.0 messages
+class _VolumeV2Proxy(object):
+
+    target = messaging.Target(version='2.0')
+
+    def __init__(self, manager):
+        self.manager = manager
+
+    def create_volume(self, context, volume_id, request_spec=None,
+                      filter_properties=None, allow_reschedule=True,
+                      volume=None):
+        return self.manager.create_volume(
+            context, volume_id, request_spec=request_spec,
+            filter_properties=filter_properties,
+            allow_reschedule=allow_reschedule, volume=volume)
+
+    def delete_volume(self, context, volume_id, unmanage_only=False,
+                      volume=None, cascade=False):
+        return self.manager.delete_volume(
+            context, volume_id, unmanage_only=unmanage_only, volume=volume,
+            cascade=cascade)
+
+    def create_snapshot(self, context, volume_id, snapshot):
+        return self.manager.create_snapshot(context, volume_id, snapshot)
+
+    def delete_snapshot(self, context, snapshot, unmanage_only=False):
+        return self.manager.delete_snapshot(context, snapshot,
+                                            unmanage_only=unmanage_only)
+
+    def attach_volume(self, context, volume_id, instance_uuid, host_name,
+                      mountpoint, mode):
+        return self.manager.attach_volume(context, volume_id, instance_uuid,
+                                          host_name, mountpoint, mode)
+
+    def detach_volume(self, context, volume_id, attachment_id=None):
+        return self.manager.detach_volume(context, volume_id,
+                                          attachment_id=attachment_id)
+
+    def copy_volume_to_image(self, context, volume_id, image_meta):
+        return self.manager.copy_volume_to_image(context, volume_id,
+                                                 image_meta)
+
+    def initialize_connection(self, context, volume_id, connector):
+        return self.manager.initialize_connection(context, volume_id,
+                                                  connector)
+
+    def terminate_connection(self, context, volume_id, connector, force=False):
+        return self.manager.terminate_connection(context, volume_id, connector,
+                                                 force=force)
+
+    def remove_export(self, context, volume_id):
+        return self.manager.remove_export(context, volume_id)
+
+    def accept_transfer(self, context, volume_id, new_user, new_project):
+        return self.manager.accept_transfer(context, volume_id, new_user,
+                                            new_project)
+
+    def migrate_volume_completion(self, ctxt, volume_id, new_volume_id,
+                                  error=False, volume=None, new_volume=None):
+        return self.manager.migrate_volume_completion(
+            ctxt, volume_id, new_volume_id, error=error, volume=volume,
+            new_volume=new_volume)
+
+    def migrate_volume(self, ctxt, volume_id, host, force_host_copy=False,
+                       new_type_id=None, volume=None):
+        return self.manager.migrate_volume(
+            ctxt, volume_id, host, force_host_copy=force_host_copy,
+            new_type_id=new_type_id, volume=volume)
+
+    def publish_service_capabilities(self, context):
+        return self.manager.publish_service_capabilities(context)
+
+    def extend_volume(self, context, volume_id, new_size, reservations,
+                      volume=None):
+        return self.manager.extend_volume(context, volume_id, new_size,
+                                          reservations, volume=volume)
+
+    def retype(self, ctxt, volume_id, new_type_id, host,
+               migration_policy='never', reservations=None,
+               volume=None, old_reservations=None):
+        return self.manager.retype(ctxt, volume_id, new_type_id, host,
+                                   migration_policy=migration_policy,
+                                   reservations=reservations, volume=volume,
+                                   old_reservations=old_reservations)
+
+    def manage_existing(self, ctxt, volume_id, ref=None):
+        return self.manager.manage_existing(ctxt, volume_id, ref=ref)
+
+    def promote_replica(self, ctxt, volume_id):
+        return self.manager.promote_replica(ctxt, volume_id)
+
+    def reenable_replication(self, ctxt, volume_id):
+        return self.manager.reenable_replication(ctxt, volume_id)
+
+    def create_consistencygroup(self, context, group):
+        return self.manager.create_consistencygroup(context, group)
+
+    def create_consistencygroup_from_src(self, context, group,
+                                         cgsnapshot=None, source_cg=None):
+        return self.manager.create_consistencygroup_from_src(
+            context, group, cgsnapshot=cgsnapshot, source_cg=source_cg)
+
+    def delete_consistencygroup(self, context, group):
+        return self.manager.delete_consistencygroup(context, group)
+
+    def update_consistencygroup(self, context, group, add_volumes=None,
+                                remove_volumes=None):
+        return self.manager.update_consistencygroup(
+            context, group, add_volumes=add_volumes,
+            remove_volumes=remove_volumes)
+
+    def create_cgsnapshot(self, context, cgsnapshot):
+        return self.manager.create_cgsnapshot(context, cgsnapshot)
+
+    def delete_cgsnapshot(self, context, cgsnapshot):
+        return self.manager.delete_cgsnapshot(context, cgsnapshot)
+
+    def update_migrated_volume(self, ctxt, volume, new_volume, volume_status):
+        return self.manager.update_migrated_volume(ctxt, volume, new_volume,
+                                                   volume_status)
+
+    def failover_host(self, context, secondary_backend_id=None):
+        return self.manager.failover_host(
+            context, secondary_backend_id=secondary_backend_id)
+
+    def freeze_host(self, context):
+        return self.manager.freeze_host(context)
+
+    def thaw_host(self, context):
+        return self.manager.thaw_host(context)
+
+    def manage_existing_snapshot(self, ctxt, snapshot, ref=None):
+        return self.manager.manage_exisiting_snapshot(ctxt, snapshot, ref=ref)
+
+    def get_capabilities(self, context, discover):
+        return self.manager.get_capabilities(context, discover)
+
+    def get_backup_device(self, ctxt, backup):
+        return self.manager.get_backup_device(ctxt, backup)
+
+    def secure_file_operations_enabled(self, ctxt, volume):
+        return self.manager.secure_file_operations_enabled(ctxt, volume)
index 9ea2fd5aa14caa0ea047338cf4b04274f723bb31..38db2e5ff1f94b7bee15fd6d7561323c6b2ebfef 100644 (file)
@@ -93,29 +93,44 @@ class VolumeAPI(rpc.RPCAPI):
                secure_file_operations_enabled()
         1.39 - Update replication methods to reflect new backend rep strategy
         1.40 - Add cascade option to delete_volume().
+
+        ... Mitaka supports messaging version 1.40. Any changes to existing
+        methods in 1.x after that point should be done so that they can handle
+        the version_cap being set to 1.40.
+
+        2.0  - Remove 1.x compatibility
     """
 
     RPC_API_VERSION = '1.40'
     TOPIC = CONF.volume_topic
     BINARY = 'cinder-volume'
 
+    def _compat_ver(self, current, legacy):
+        if self.client.can_send_version(current):
+            return current
+        else:
+            return legacy
+
     def _get_cctxt(self, host, version):
         new_host = utils.get_volume_rpc_host(host)
         return self.client.prepare(server=new_host, version=version)
 
     def create_consistencygroup(self, ctxt, group, host):
-        cctxt = self._get_cctxt(host, '1.26')
+        version = self._compat_ver('2.0', '1.26')
+        cctxt = self._get_cctxt(host, version)
         cctxt.cast(ctxt, 'create_consistencygroup',
                    group=group)
 
     def delete_consistencygroup(self, ctxt, group):
-        cctxt = self._get_cctxt(group.host, '1.26')
+        version = self._compat_ver('2.0', '1.26')
+        cctxt = self._get_cctxt(group.host, version)
         cctxt.cast(ctxt, 'delete_consistencygroup',
                    group=group)
 
     def update_consistencygroup(self, ctxt, group, add_volumes=None,
                                 remove_volumes=None):
-        cctxt = self._get_cctxt(group.host, '1.26')
+        version = self._compat_ver('2.0', '1.26')
+        cctxt = self._get_cctxt(group.host, version)
         cctxt.cast(ctxt, 'update_consistencygroup',
                    group=group,
                    add_volumes=add_volumes,
@@ -123,18 +138,21 @@ class VolumeAPI(rpc.RPCAPI):
 
     def create_consistencygroup_from_src(self, ctxt, group, cgsnapshot=None,
                                          source_cg=None):
-        cctxt = self._get_cctxt(group.host, '1.31')
+        version = self._compat_ver('2.0', '1.31')
+        cctxt = self._get_cctxt(group.host, version)
         cctxt.cast(ctxt, 'create_consistencygroup_from_src',
                    group=group,
                    cgsnapshot=cgsnapshot,
                    source_cg=source_cg)
 
     def create_cgsnapshot(self, ctxt, cgsnapshot):
-        cctxt = self._get_cctxt(cgsnapshot.consistencygroup.host, '1.31')
+        version = self._compat_ver('2.0', '1.31')
+        cctxt = self._get_cctxt(cgsnapshot.consistencygroup.host, version)
         cctxt.cast(ctxt, 'create_cgsnapshot', cgsnapshot=cgsnapshot)
 
     def delete_cgsnapshot(self, ctxt, cgsnapshot):
-        cctxt = self._get_cctxt(cgsnapshot.consistencygroup.host, '1.31')
+        version = self._compat_ver('2.0', '1.31')
+        cctxt = self._get_cctxt(cgsnapshot.consistencygroup.host, version)
         cctxt.cast(ctxt, 'delete_cgsnapshot', cgsnapshot=cgsnapshot)
 
     def create_volume(self, ctxt, volume, host, request_spec,
@@ -143,7 +161,10 @@ class VolumeAPI(rpc.RPCAPI):
         msg_args = {'volume_id': volume.id, 'request_spec': request_spec_p,
                     'filter_properties': filter_properties,
                     'allow_reschedule': allow_reschedule}
-        if self.client.can_send_version('1.32'):
+        if self.client.can_send_version('2.0'):
+            version = '2.0'
+            msg_args['volume'] = volume
+        elif self.client.can_send_version('1.32'):
             version = '1.32'
             msg_args['volume'] = volume
         else:
@@ -158,35 +179,42 @@ class VolumeAPI(rpc.RPCAPI):
 
         version = '1.15'
 
-        if self.client.can_send_version('1.33'):
-            version = '1.33'
+        if self.client.can_send_version('2.0'):
+            version = '2.0'
             msg_args['volume'] = volume
-
-        if self.client.can_send_version('1.40'):
+            if cascade:
+                msg_args['cascade'] = cascade
+        elif self.client.can_send_version('1.40'):
             version = '1.40'
+            msg_args['volume'] = volume
             if cascade:
                 msg_args['cascade'] = cascade
         elif cascade:
             msg = _('Cascade option is not supported.')
             raise exception.Invalid(reason=msg)
+        elif self.client.can_send_version('1.33'):
+            version = '1.33'
+            msg_args['volume'] = volume
 
         cctxt = self._get_cctxt(volume.host, version)
         cctxt.cast(ctxt, 'delete_volume', **msg_args)
 
     def create_snapshot(self, ctxt, volume, snapshot):
-        cctxt = self._get_cctxt(volume['host'], version='1.20')
+        version = self._compat_ver('2.0', '1.20')
+        cctxt = self._get_cctxt(volume['host'], version=version)
         cctxt.cast(ctxt, 'create_snapshot', volume_id=volume['id'],
                    snapshot=snapshot)
 
     def delete_snapshot(self, ctxt, snapshot, host, unmanage_only=False):
-        cctxt = self._get_cctxt(host, version='1.20')
+        version = self._compat_ver('2.0', '1.20')
+        cctxt = self._get_cctxt(host, version=version)
         cctxt.cast(ctxt, 'delete_snapshot', snapshot=snapshot,
                    unmanage_only=unmanage_only)
 
     def attach_volume(self, ctxt, volume, instance_uuid, host_name,
                       mountpoint, mode):
-
-        cctxt = self._get_cctxt(volume['host'], '1.11')
+        version = self._compat_ver('2.0', '1.11')
+        cctxt = self._get_cctxt(volume['host'], version)
         return cctxt.call(ctxt, 'attach_volume',
                           volume_id=volume['id'],
                           instance_uuid=instance_uuid,
@@ -195,44 +223,53 @@ class VolumeAPI(rpc.RPCAPI):
                           mode=mode)
 
     def detach_volume(self, ctxt, volume, attachment_id):
-        cctxt = self._get_cctxt(volume['host'], '1.20')
+        version = self._compat_ver('2.0', '1.20')
+        cctxt = self._get_cctxt(volume['host'], version)
         return cctxt.call(ctxt, 'detach_volume', volume_id=volume['id'],
                           attachment_id=attachment_id)
 
     def copy_volume_to_image(self, ctxt, volume, image_meta):
-        cctxt = self._get_cctxt(volume['host'], '1.3')
+        version = self._compat_ver('2.0', '1.3')
+        cctxt = self._get_cctxt(volume['host'], version)
         cctxt.cast(ctxt, 'copy_volume_to_image', volume_id=volume['id'],
                    image_meta=image_meta)
 
     def initialize_connection(self, ctxt, volume, connector):
-        cctxt = self._get_cctxt(volume['host'], version='1.0')
+        version = self._compat_ver('2.0', '1.0')
+        cctxt = self._get_cctxt(volume['host'], version=version)
         return cctxt.call(ctxt, 'initialize_connection',
                           volume_id=volume['id'],
                           connector=connector)
 
     def terminate_connection(self, ctxt, volume, connector, force=False):
-        cctxt = self._get_cctxt(volume['host'], version='1.0')
+        version = self._compat_ver('2.0', '1.0')
+        cctxt = self._get_cctxt(volume['host'], version=version)
         return cctxt.call(ctxt, 'terminate_connection', volume_id=volume['id'],
                           connector=connector, force=force)
 
     def remove_export(self, ctxt, volume):
-        cctxt = self._get_cctxt(volume['host'], '1.30')
+        version = self._compat_ver('2.0', '1.30')
+        cctxt = self._get_cctxt(volume['host'], version)
         cctxt.cast(ctxt, 'remove_export', volume_id=volume['id'])
 
     def publish_service_capabilities(self, ctxt):
-        cctxt = self.client.prepare(fanout=True, version='1.2')
+        version = self._compat_ver('2.0', '1.2')
+        cctxt = self.client.prepare(fanout=True, version=version)
         cctxt.cast(ctxt, 'publish_service_capabilities')
 
     def accept_transfer(self, ctxt, volume, new_user, new_project):
-        cctxt = self._get_cctxt(volume['host'], '1.9')
+        version = self._compat_ver('2.0', '1.9')
+        cctxt = self._get_cctxt(volume['host'], version)
         return cctxt.call(ctxt, 'accept_transfer', volume_id=volume['id'],
                           new_user=new_user, new_project=new_project)
 
     def extend_volume(self, ctxt, volume, new_size, reservations):
-
         msg_args = {'volume_id': volume.id, 'new_size': new_size,
                     'reservations': reservations}
-        if self.client.can_send_version('1.35'):
+        if self.client.can_send_version('2.0'):
+            version = '2.0'
+            msg_args['volume'] = volume
+        elif self.client.can_send_version('1.35'):
             version = '1.35'
             msg_args['volume'] = volume
         else:
@@ -247,7 +284,10 @@ class VolumeAPI(rpc.RPCAPI):
 
         msg_args = {'volume_id': volume.id, 'host': host_p,
                     'force_host_copy': force_host_copy}
-        if self.client.can_send_version('1.36'):
+        if self.client.can_send_version('2.0'):
+            version = '2.0'
+            msg_args['volume'] = volume
+        elif self.client.can_send_version('1.36'):
             version = '1.36'
             msg_args['volume'] = volume
         else:
@@ -260,7 +300,11 @@ class VolumeAPI(rpc.RPCAPI):
 
         msg_args = {'volume_id': volume.id, 'new_volume_id': new_volume.id,
                     'error': error}
-        if self.client.can_send_version('1.36'):
+        if self.client.can_send_version('2.0'):
+            version = '2.0'
+            msg_args['volume'] = volume
+            msg_args['new_volume'] = new_volume
+        elif self.client.can_send_version('1.36'):
             version = '1.36'
             msg_args['volume'] = volume
             msg_args['new_volume'] = new_volume
@@ -278,36 +322,44 @@ class VolumeAPI(rpc.RPCAPI):
         msg_args = {'volume_id': volume.id, 'new_type_id': new_type_id,
                     'host': host_p, 'migration_policy': migration_policy,
                     'reservations': reservations}
-        if self.client.can_send_version('1.37'):
+        if self.client.can_send_version('2.0'):
+            version = '2.0'
+            msg_args.update(volume=volume, old_reservations=old_reservations)
+        elif self.client.can_send_version('1.37'):
             version = '1.37'
             msg_args.update(volume=volume, old_reservations=old_reservations)
+        elif self.client.can_send_version('1.34'):
+            if old_reservations is not None:
+                QUOTAS.rollback(ctxt, old_reservations)
+            version = '1.34'
+            msg_args['volume'] = volume
         else:
             if old_reservations is not None:
                 QUOTAS.rollback(ctxt, old_reservations)
-            if self.client.can_send_version('1.34'):
-                version = '1.34'
-                msg_args['volume'] = volume
-            else:
-                version = '1.12'
+            version = '1.12'
 
         cctxt = self._get_cctxt(volume.host, version)
         cctxt.cast(ctxt, 'retype', **msg_args)
 
     def manage_existing(self, ctxt, volume, ref):
-        cctxt = self._get_cctxt(volume['host'], '1.15')
+        version = self._compat_ver('2.0', '1.15')
+        cctxt = self._get_cctxt(volume['host'], version)
         cctxt.cast(ctxt, 'manage_existing', volume_id=volume['id'], ref=ref)
 
     def promote_replica(self, ctxt, volume):
-        cctxt = self._get_cctxt(volume['host'], '1.17')
+        version = self._compat_ver('2.0', '1.17')
+        cctxt = self._get_cctxt(volume['host'], version)
         cctxt.cast(ctxt, 'promote_replica', volume_id=volume['id'])
 
     def reenable_replication(self, ctxt, volume):
-        cctxt = self._get_cctxt(volume['host'], '1.17')
+        version = self._compat_ver('2.0', '1.17')
+        cctxt = self._get_cctxt(volume['host'], version)
         cctxt.cast(ctxt, 'reenable_replication', volume_id=volume['id'])
 
     def update_migrated_volume(self, ctxt, volume, new_volume,
                                original_volume_status):
-        cctxt = self._get_cctxt(new_volume['host'], '1.36')
+        version = self._compat_ver('2.0', '1.36')
+        cctxt = self._get_cctxt(new_volume['host'], version)
         cctxt.call(ctxt,
                    'update_migrated_volume',
                    volume=volume,
@@ -316,29 +368,34 @@ class VolumeAPI(rpc.RPCAPI):
 
     def freeze_host(self, ctxt, host):
         """Set backend host to frozen."""
-        cctxt = self._get_cctxt(host, '1.39')
+        version = self._compat_ver('2.0', '1.39')
+        cctxt = self._get_cctxt(host, version)
         return cctxt.call(ctxt, 'freeze_host')
 
     def thaw_host(self, ctxt, host):
         """Clear the frozen setting on a backend host."""
-        cctxt = self._get_cctxt(host, '1.39')
+        version = self._compat_ver('2.0', '1.39')
+        cctxt = self._get_cctxt(host, version)
         return cctxt.call(ctxt, 'thaw_host')
 
     def failover_host(self, ctxt, host,
                       secondary_backend_id=None):
         """Failover host to the specified backend_id (secondary). """
-        cctxt = self._get_cctxt(host, '1.39')
+        version = self._compat_ver('2.0', '1.39')
+        cctxt = self._get_cctxt(host, version)
         cctxt.cast(ctxt, 'failover_host',
                    secondary_backend_id=secondary_backend_id)
 
     def manage_existing_snapshot(self, ctxt, snapshot, ref, host):
-        cctxt = self._get_cctxt(host, '1.28')
+        version = self._compat_ver('2.0', '1.28')
+        cctxt = self._get_cctxt(host, version)
         cctxt.cast(ctxt, 'manage_existing_snapshot',
                    snapshot=snapshot,
                    ref=ref)
 
     def get_capabilities(self, ctxt, host, discover):
-        cctxt = self._get_cctxt(host, '1.29')
+        version = self._compat_ver('2.0', '1.29')
+        cctxt = self._get_cctxt(host, version)
         return cctxt.call(ctxt, 'get_capabilities', discover=discover)
 
     def get_backup_device(self, ctxt, backup, volume):