Fix compatibility with decorator < 4.0.x

Decorator library didn't add the decorate method until v4.0, so current
code doesn't work with minimum version we have in our requirements
(v3.4.0).

The easiest fix would be to bump our requirements version, but as the
bug states this would create problems for RHEL/CentOS 7 and Ubuntu
Trusty.

Another option would be to use the `decorator` method instead, but this
has been deprecated in v4.0, and although the code is still there it
isn't a good idea to use it on v4.0 as it could go away at any moment.

So this patch adds compatibility code to work with both versions, taking
into account that `decorator` and `decorate` arguments are reversed from
one method to the other.

Change-Id: Ia263b4aacb22a8270ccd46a006252a4bcfd80ebc
Closes-Bug: #1639279
This commit is contained in:
Gorka Eguileor 2016-11-04 17:23:47 +01:00
parent ea0fcc9858
commit 188abdac40
2 changed files with 93 additions and 1 deletions

View File

@ -159,6 +159,10 @@ class CinderCleanableObject(base.CinderPersistentObject):
service_id=self.worker.service_id)
self.worker = None
# NOTE(geguileo): To be compatible with decorate v3.4.x and v4.0.x
decorate = staticmethod(getattr(decorator, 'decorate',
lambda f, w: decorator.decorator(w, f)))
@staticmethod
def set_workers(*decorator_args):
"""Decorator that adds worker DB rows for cleanable versioned objects.
@ -200,7 +204,7 @@ class CinderCleanableObject(base.CinderPersistentObject):
except Exception:
pass
return result
return decorator.decorate(f, wrapper)
return CinderCleanableObject.decorate(f, wrapper)
# If we don't have optional decorator arguments the argument in
# decorator_args is the function we have to decorate

View File

@ -13,6 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import inspect
import mock
from cinder import context
@ -285,3 +287,89 @@ class TestCleanable(test_objects.BaseObjectsTestCase):
id=mock.sentinel.id)
backup.unset_worker()
self.assertFalse(destroy_mock.called)
@mock.patch('cinder.db.worker_update', autospec=True)
@mock.patch('cinder.db.worker_get', autospec=True)
def test_set_workers_no_arguments(self, mock_get, mock_update):
"""Test set workers decorator without arguments."""
@Backup.set_workers
def my_function(arg1, arg2, kwarg1=None, kwarg2=True):
return arg1, arg2, kwarg1, kwarg2
# Decorator with no args must preserve the method's signature
self.assertEqual('my_function', my_function.__name__)
call_args = inspect.getcallargs(
my_function, mock.sentinel.arg1, mock.sentinel.arg2,
mock.sentinel.kwargs1, kwarg2=mock.sentinel.kwarg2)
expected = {'arg1': mock.sentinel.arg1,
'arg2': mock.sentinel.arg2,
'kwarg1': mock.sentinel.kwargs1,
'kwarg2': mock.sentinel.kwarg2}
self.assertDictEqual(expected, call_args)
service.Service.service_id = mock.sentinel.service_id
mock_get.return_value.cleaning = False
backup = Backup(_context=self.context, status='cleanable',
id=mock.sentinel.id)
backup2 = Backup(_context=self.context, status='non-cleanable',
id=mock.sentinel.id2)
res = my_function(backup, backup2)
self.assertEqual((backup, backup2, None, True), res)
mock_get.assert_called_once_with(self.context, resource_type='Backup',
resource_id=mock.sentinel.id)
worker = mock_get.return_value
mock_update.assert_called_once_with(
self.context, worker.id,
filters={'service_id': worker.service_id,
'status': worker.status,
'updated_at': worker.updated_at},
service_id=mock.sentinel.service_id,
status='cleanable',
orm_worker=worker)
self.assertEqual(worker, backup.worker)
@mock.patch('cinder.db.worker_update', autospec=True)
@mock.patch('cinder.db.worker_get', autospec=True)
def test_set_workers_with_arguments(self, mock_get, mock_update):
"""Test set workers decorator with an argument."""
@Backup.set_workers('arg2', 'kwarg1')
def my_function(arg1, arg2, kwarg1=None, kwarg2=True):
return arg1, arg2, kwarg1, kwarg2
# Decorator with args must preserve the method's signature
self.assertEqual('my_function', my_function.__name__)
call_args = inspect.getcallargs(
my_function, mock.sentinel.arg1, mock.sentinel.arg2,
mock.sentinel.kwargs1, kwarg2=mock.sentinel.kwarg2)
expected = {'arg1': mock.sentinel.arg1,
'arg2': mock.sentinel.arg2,
'kwarg1': mock.sentinel.kwargs1,
'kwarg2': mock.sentinel.kwarg2}
self.assertDictEqual(expected, call_args)
service.Service.service_id = mock.sentinel.service_id
mock_get.return_value.cleaning = False
backup = Backup(_context=self.context, status='cleanable',
id=mock.sentinel.id)
backup2 = Backup(_context=self.context, status='non-cleanable',
id=mock.sentinel.id2)
backup3 = Backup(_context=self.context, status='cleanable',
id=mock.sentinel.id3)
res = my_function(backup, backup2, backup3)
self.assertEqual((backup, backup2, backup3, True), res)
mock_get.assert_called_once_with(self.context, resource_type='Backup',
resource_id=mock.sentinel.id3)
worker = mock_get.return_value
mock_update.assert_called_once_with(
self.context, worker.id,
filters={'service_id': worker.service_id,
'status': worker.status,
'updated_at': worker.updated_at},
service_id=mock.sentinel.service_id,
status='cleanable',
orm_worker=worker)
self.assertEqual(worker, backup3.worker)