From 2394cff0631944a9259bfe04925e444d9f817758 Mon Sep 17 00:00:00 2001 From: Jamie Lennox Date: Mon, 9 May 2016 11:11:55 +1000 Subject: Provide a way to deprecate policy values If we want to move all the services over to a standard policy enforcement dict we need a way to start notifying deployers when the policy enforcement files are using deprecated values. Instead of returning a dictionary return an object that acts like a dictionary but emits a DeprecationWarning whenever a deprecated policy enforcement value is read from it. Change-Id: I4b2fda188bbccfd491556cc5631e5c4a76314492 --- oslo_context/context.py | 75 +++++++++++++++++++++++++++++++++++--- oslo_context/tests/test_context.py | 30 ++++++++++++++- 2 files changed, 98 insertions(+), 7 deletions(-) (limited to 'oslo_context') diff --git a/oslo_context/context.py b/oslo_context/context.py index 1a2ce21..481b18c 100644 --- a/oslo_context/context.py +++ b/oslo_context/context.py @@ -26,9 +26,11 @@ context or provide additional information in their specific WSGI pipeline or logging context. """ +import collections import itertools import threading import uuid +import warnings from positional import positional @@ -60,6 +62,62 @@ def generate_request_id(): return 'req-%s' % uuid.uuid4() +class _DeprecatedPolicyValues(collections.MutableMapping): + """A Dictionary that manages current and deprecated policy values. + + Anything added to this dictionary after initial creation is considered a + deprecated key that we are trying to move services away from. Accessing + these values as oslo.policy will do will trigger a DeprecationWarning. + """ + + def __init__(self, data): + self._data = data + self._deprecated = {} + + def __getitem__(self, k): + try: + return self._data[k] + except KeyError: + pass + + try: + val = self._deprecated[k] + except KeyError: + pass + else: + warnings.warn('Policy enforcement is depending on the value of ' + '%s. This key is deprecated. Please update your ' + 'policy file to use the standard policy values.' % k, + DeprecationWarning) + return val + + raise KeyError(k) + + def __setitem__(self, k, v): + self._deprecated[k] = v + + def __delitem__(self, k): + del self._deprecated[k] + + def __iter__(self): + return iter(self._dict) + + def __len__(self): + return len(self._dict) + + def __str__(self): + return self._dict.__str__() + + def __repr__(self): + return self._dict.__repr__() + + @property + def _dict(self): + d = self._deprecated.copy() + d.update(self._data) + return d + + class RequestContext(object): """Helper class to represent useful information about a request context. @@ -128,12 +186,17 @@ class RequestContext(object): with either deprecated values or additional attributes used by that service specific policy. """ - return {'user_id': self.user, - 'user_domain_id': self.user_domain, - 'project_id': self.tenant, - 'project_domain_id': self.project_domain, - 'roles': self.roles, - 'is_admin_project': self.is_admin_project} + # NOTE(jamielennox): We need a way to allow projects to provide old + # deprecated policy values that trigger a warning when used in favour + # of our standard ones. This object acts like a dict but only values + # from oslo.policy don't show a warning. + return _DeprecatedPolicyValues({ + 'user_id': self.user, + 'user_domain_id': self.user_domain, + 'project_id': self.tenant, + 'project_domain_id': self.project_domain, + 'roles': self.roles, + 'is_admin_project': self.is_admin_project}) def to_dict(self): """Return a dictionary of context attributes.""" diff --git a/oslo_context/tests/test_context.py b/oslo_context/tests/test_context.py index 956c1e5..9a32fac 100644 --- a/oslo_context/tests/test_context.py +++ b/oslo_context/tests/test_context.py @@ -471,7 +471,6 @@ class ContextTest(test_base.BaseTestCase): 'is_admin_project': True}, ctx.to_policy_values()) - # is_admin_project False gets passed through ctx = context.RequestContext(user=user, user_domain=user_domain, tenant=tenant, @@ -493,3 +492,32 @@ class ContextTest(test_base.BaseTestCase): self.assertEqual(1, len(self.warnings.log)) self.assertIn('__init__ takes at most 1 positional', str(self.warnings.log[0].message)) + + def test_policy_deprecations(self): + user = uuid.uuid4().hex + user_domain = uuid.uuid4().hex + tenant = uuid.uuid4().hex + project_domain = uuid.uuid4().hex + roles = [uuid.uuid4().hex, uuid.uuid4().hex, uuid.uuid4().hex] + + ctx = context.RequestContext(user=user, + user_domain=user_domain, + tenant=tenant, + project_domain=project_domain, + roles=roles) + + policy = ctx.to_policy_values() + key = uuid.uuid4().hex + val = uuid.uuid4().hex + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + + # no warning triggered by adding key to dict + policy[key] = val + self.assertEqual(0, len(w)) + + # warning triggered by fetching key from dict + self.assertIs(val, policy[key]) + self.assertEqual(1, len(w)) + self.assertIn(key, str(w[0].message)) -- cgit v1.2.1