From 241e8418ebb777d460c15a013f71f7e352c8cfdf Mon Sep 17 00:00:00 2001 From: =?utf8?q?Micha=C5=82=20Dulko?= Date: Mon, 22 Feb 2016 14:06:37 +0100 Subject: [PATCH] Add backup RPC API v2.0 This patch creates backup 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). Related-Blueprint: rpc-object-compatibility Change-Id: I0c66e4126901e6d60054d093cf87d43f4b9df666 --- cinder/backup/manager.py | 34 ++++++++++ cinder/backup/rpcapi.py | 33 +++++++--- cinder/tests/unit/backup/test_rpcapi.py | 82 +++++++++++++++++++++++-- 3 files changed, 136 insertions(+), 13 deletions(-) diff --git a/cinder/backup/manager.py b/cinder/backup/manager.py index f29b1424c..02d2a88b6 100644 --- a/cinder/backup/manager.py +++ b/cinder/backup/manager.py @@ -93,6 +93,7 @@ class BackupManager(manager.SchedulerDependentManager): self.volume_rpcapi = volume_rpcapi.VolumeAPI() super(BackupManager, self).__init__(service_name='backup', *args, **kwargs) + self.additional_endpoints.append(_BackupV2Proxy(self)) @property def driver_name(self): @@ -855,3 +856,36 @@ class BackupManager(manager.SchedulerDependentManager): rpcapi = self.volume_rpcapi rpcapi.terminate_connection(context, volume, properties, force=force) rpcapi.remove_export(context, volume) + + +# 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 _BackupV2Proxy(object): + + target = messaging.Target(version='2.0') + + def __init__(self, manager): + self.manager = manager + + def create_backup(self, context, backup): + return self.manager.create_backup(context, backup) + + def restore_backup(self, context, backup, volume_id): + return self.manager.restore_backup(context, backup, volume_id) + + def delete_backup(self, context, backup): + return self.manager.delete_backup(context, backup) + + def export_record(self, context, backup): + return self.manager.export_record(context, backup) + + def import_record(self, context, backup, backup_service, backup_url, + backup_hosts): + return self.manager.import_record(context, backup, backup_service, + backup_url, backup_hosts) + + def reset_status(self, context, backup, status): + return self.manager.reset_status(context, backup, status) + + def check_support_to_force_delete(self, context): + return self.manager.check_support_to_force_delete(context) diff --git a/cinder/backup/rpcapi.py b/cinder/backup/rpcapi.py index 245cc07b8..20f70c3d1 100644 --- a/cinder/backup/rpcapi.py +++ b/cinder/backup/rpcapi.py @@ -38,26 +38,41 @@ class BackupAPI(rpc.RPCAPI): 1.2 - A version that got in by mistake (without breaking anything). 1.3 - Dummy version bump to mark start of having cinder-backup service decoupled from cinder-volume. + + ... Mitaka supports messaging 1.3. Any changes to existing methods in + 1.x after this point should be done so that they can handle version cap + set to 1.3. + + 2.0 - Remove 1.x compatibility """ RPC_API_VERSION = '1.3' TOPIC = CONF.backup_topic BINARY = 'cinder-backup' + def _compat_ver(self, current, legacy): + if self.client.can_send_version(current): + return current + else: + return legacy + def create_backup(self, ctxt, backup): LOG.debug("create_backup in rpcapi backup_id %s", backup.id) - cctxt = self.client.prepare(server=backup.host, version='1.1') + version = self._compat_ver('2.0', '1.1') + cctxt = self.client.prepare(server=backup.host, version=version) cctxt.cast(ctxt, 'create_backup', backup=backup) def restore_backup(self, ctxt, volume_host, backup, volume_id): LOG.debug("restore_backup in rpcapi backup_id %s", backup.id) - cctxt = self.client.prepare(server=volume_host, version='1.1') + version = self._compat_ver('2.0', '1.1') + cctxt = self.client.prepare(server=volume_host, version=version) cctxt.cast(ctxt, 'restore_backup', backup=backup, volume_id=volume_id) def delete_backup(self, ctxt, backup): LOG.debug("delete_backup rpcapi backup_id %s", backup.id) - cctxt = self.client.prepare(server=backup.host, version='1.1') + version = self._compat_ver('2.0', '1.1') + cctxt = self.client.prepare(server=backup.host, version=version) cctxt.cast(ctxt, 'delete_backup', backup=backup) def export_record(self, ctxt, backup): @@ -65,7 +80,8 @@ class BackupAPI(rpc.RPCAPI): "on host %(host)s.", {'id': backup.id, 'host': backup.host}) - cctxt = self.client.prepare(server=backup.host, version='1.1') + version = self._compat_ver('2.0', '1.1') + cctxt = self.client.prepare(server=backup.host, version=version) return cctxt.call(ctxt, 'export_record', backup=backup) def import_record(self, @@ -80,7 +96,8 @@ class BackupAPI(rpc.RPCAPI): {'id': backup.id, 'host': host, 'url': backup_url}) - cctxt = self.client.prepare(server=host, version='1.1') + version = self._compat_ver('2.0', '1.1') + cctxt = self.client.prepare(server=host, version=version) cctxt.cast(ctxt, 'import_record', backup=backup, backup_service=backup_service, @@ -92,11 +109,13 @@ class BackupAPI(rpc.RPCAPI): "on host %(host)s.", {'id': backup.id, 'host': backup.host}) - cctxt = self.client.prepare(server=backup.host, version='1.1') + version = self._compat_ver('2.0', '1.1') + cctxt = self.client.prepare(server=backup.host, version=version) return cctxt.cast(ctxt, 'reset_status', backup=backup, status=status) def check_support_to_force_delete(self, ctxt, host): LOG.debug("Check if backup driver supports force delete " "on host %(host)s.", {'host': host}) - cctxt = self.client.prepare(server=host, version='1.1') + version = self._compat_ver('2.0', '1.1') + cctxt = self.client.prepare(server=host, version=version) return cctxt.call(ctxt, 'check_support_to_force_delete') diff --git a/cinder/tests/unit/backup/test_rpcapi.py b/cinder/tests/unit/backup/test_rpcapi.py index d85c2710a..13ccd3ca3 100644 --- a/cinder/tests/unit/backup/test_rpcapi.py +++ b/cinder/tests/unit/backup/test_rpcapi.py @@ -80,14 +80,32 @@ class BackupRpcAPITestCase(test.TestCase): else: self.assertEqual(expected_msg[kwarg], value) - def test_create_backup(self): + @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True) + def test_create_backup(self, can_send_version): + self._test_backup_api('create_backup', + rpc_method='cast', + server=self.fake_backup_obj.host, + backup=self.fake_backup_obj, + version='2.0') + + can_send_version.return_value = False self._test_backup_api('create_backup', rpc_method='cast', server=self.fake_backup_obj.host, backup=self.fake_backup_obj, version='1.1') - def test_restore_backup(self): + @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True) + def test_restore_backup(self, can_send_version): + self._test_backup_api('restore_backup', + rpc_method='cast', + server='fake_volume_host', + volume_host='fake_volume_host', + backup=self.fake_backup_obj, + volume_id='fake_volume_id', + version='2.0') + + can_send_version.return_value = False self._test_backup_api('restore_backup', rpc_method='cast', server='fake_volume_host', @@ -96,21 +114,49 @@ class BackupRpcAPITestCase(test.TestCase): volume_id=fake.volume_id, version='1.1') - def test_delete_backup(self): + @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True) + def test_delete_backup(self, can_send_version): + self._test_backup_api('delete_backup', + rpc_method='cast', + server=self.fake_backup_obj.host, + backup=self.fake_backup_obj, + version='2.0') + + can_send_version.return_value = False self._test_backup_api('delete_backup', rpc_method='cast', server=self.fake_backup_obj.host, backup=self.fake_backup_obj, version='1.1') - def test_export_record(self): + @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True) + def test_export_record(self, can_send_version): + self._test_backup_api('export_record', + rpc_method='call', + server=self.fake_backup_obj.host, + backup=self.fake_backup_obj, + version='2.0') + + can_send_version.return_value = False self._test_backup_api('export_record', rpc_method='call', server=self.fake_backup_obj.host, backup=self.fake_backup_obj, version='1.1') - def test_import_record(self): + @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True) + def test_import_record(self, can_send_version): + self._test_backup_api('import_record', + rpc_method='cast', + server='fake_volume_host', + host='fake_volume_host', + backup=self.fake_backup_obj, + backup_service='fake_service', + backup_url='fake_url', + backup_hosts=['fake_host1', 'fake_host2'], + version='2.0') + + can_send_version.return_value = False self._test_backup_api('import_record', rpc_method='cast', server='fake_volume_host', @@ -121,10 +167,34 @@ class BackupRpcAPITestCase(test.TestCase): backup_hosts=['fake_host1', 'fake_host2'], version='1.1') - def test_reset_status(self): + @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True) + def test_reset_status(self, can_send_version): + self._test_backup_api('reset_status', + rpc_method='cast', + server=self.fake_backup_obj.host, + backup=self.fake_backup_obj, + status='error', + version='2.0') + + can_send_version.return_value = False self._test_backup_api('reset_status', rpc_method='cast', server=self.fake_backup_obj.host, backup=self.fake_backup_obj, status='error', version='1.1') + + @mock.patch('oslo_messaging.RPCClient.can_send_version', return_value=True) + def test_check_support_to_force_delete(self, can_send_version): + self._test_backup_api('check_support_to_force_delete', + rpc_method='call', + server='fake_volume_host', + host='fake_volume_host', + version='2.0') + + can_send_version.return_value = False + self._test_backup_api('check_support_to_force_delete', + rpc_method='call', + server='fake_volume_host', + host='fake_volume_host', + version='1.1') -- 2.45.2