From 95bf14908e528c9817a73d5e854061d632fa9f3a Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 6 Aug 2019 09:02:33 -0400 Subject: [PATCH] Add support for global_request_id Added support for setting global_request_id on a Connection. If done, this will cause all requests sent to send the request id header to the OpenStack services. Since Connection can otherwise be used multi-threaded, add a method global_request that returns a new Connection based on the old Connection but on which the new global_request_id has been set. Since a Connection can be used as a context manager, this also means the global_request method can be used in with statements. Change-Id: I70964cdd79741703c0b9b911b3b2f27c248130f0 --- lower-constraints.txt | 2 +- openstack/cloud/openstackcloud.py | 58 +++++++++++++++++-- openstack/connection.py | 6 ++ openstack/proxy.py | 9 ++- openstack/tests/unit/cloud/test_shade.py | 38 ++++++++++++ .../global-request-id-d7c0736f43929165.yaml | 11 ++++ requirements.txt | 2 +- 7 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/global-request-id-d7c0736f43929165.yaml diff --git a/lower-constraints.txt b/lower-constraints.txt index 60a322a5c..675cfea56 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -14,7 +14,7 @@ jmespath==0.9.0 jsonpatch==1.16 jsonpointer==1.13 jsonschema==2.6.0 -keystoneauth1==3.14.0 +keystoneauth1==3.15.0 linecache2==1.0.0 mock==2.0.0 mox3==0.20.0 diff --git a/openstack/cloud/openstackcloud.py b/openstack/cloud/openstackcloud.py index 62e4bcc16..235ddc624 100755 --- a/openstack/cloud/openstackcloud.py +++ b/openstack/cloud/openstackcloud.py @@ -33,6 +33,7 @@ from openstack.cloud import _object_store from openstack.cloud import meta from openstack.cloud import _utils import openstack.config +from openstack.config import cloud_region as cloud_region_mod from openstack import proxy DEFAULT_SERVER_AGE = 5 @@ -226,16 +227,16 @@ class _OpenStackCloudMixin(object): for key, value in kwargs.items(): params['auth'][key] = value - cloud_config = config.get_one(**params) + cloud_region = config.get_one(**params) # Attach the discovery cache from the old session so we won't # double discover. - cloud_config._discovery_cache = self.session._discovery_cache + cloud_region._discovery_cache = self.session._discovery_cache # Override the cloud name so that logging/location work right - cloud_config._name = self.name - cloud_config.config['profile'] = self.name + cloud_region._name = self.name + cloud_region.config['profile'] = self.name # Use self.__class__ so that we return whatever this if, like if it's # a subclass in the case of shade wrapping sdk. - return self.__class__(config=cloud_config) + return self.__class__(config=cloud_region) def connect_as_project(self, project): """Make a new OpenStackCloud object with a new project. @@ -267,6 +268,53 @@ class _OpenStackCloudMixin(object): auth['project_name'] = project return self.connect_as(**auth) + def global_request(self, global_request_id): + """Make a new Connection object with a global request id set. + + Take the existing settings from the current Connection and construct a + new Connection object with the global_request_id overridden. + + .. code-block:: python + + from oslo_context import context + cloud = openstack.connect(cloud='example') + # Work normally + servers = cloud.list_servers() + cloud2 = cloud.global_request(context.generate_request_id()) + # cloud2 sends all requests with global_request_id set + servers = cloud2.list_servers() + + Additionally, this can be used as a context manager: + + .. code-block:: python + + from oslo_context import context + c = openstack.connect(cloud='example') + # Work normally + servers = c.list_servers() + with c.global_request(context.generate_request_id()) as c2: + # c2 sends all requests with global_request_id set + servers = c2.list_servers() + + :param global_request_id: The `global_request_id` to send. + """ + params = copy.deepcopy(self.config.config) + cloud_region = cloud_region_mod.from_session( + session=self.session, + app_name=self.config._app_name, + app_version=self.config._app_version, + discovery_cache=self.session._discovery_cache, + **params) + + # Override the cloud name so that logging/location work right + cloud_region._name = self.name + cloud_region.config['profile'] = self.name + # Use self.__class__ so that we return whatever this is, like if it's + # a subclass in the case of shade wrapping sdk. + new_conn = self.__class__(config=cloud_region) + new_conn.set_global_request_id(global_request_id) + return new_conn + def _make_cache(self, cache_class, expiration_time, arguments): return dogpile.cache.make_region( function_key_generator=self._make_cache_key diff --git a/openstack/connection.py b/openstack/connection.py index e91ecc7a8..5916b1868 100644 --- a/openstack/connection.py +++ b/openstack/connection.py @@ -272,6 +272,7 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta, rate_limit=None, oslo_conf=None, service_types=None, + global_request_id=None, **kwargs): """Create a connection to a cloud. @@ -328,6 +329,7 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta, other service types will be disabled (will error if used). **Currently only supported in conjunction with the ``oslo_conf`` kwarg.** + :param global_request_id: A Request-id to send with all interactions. :param kwargs: If a config is not provided, the rest of the parameters provided are assumed to be arguments to be passed to the CloudRegion constructor. @@ -363,6 +365,7 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta, self._session = None self._proxies = {} self.__pool_executor = None + self._global_request_id = global_request_id self.use_direct_get = use_direct_get self.strict_mode = strict # Call the _*CloudMixin constructors while we work on @@ -479,6 +482,9 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta, if self.__pool_executor: self.__pool_executor.shutdown() + def set_global_request_id(self, global_request_id): + self._global_request_id = global_request_id + def __enter__(self): return self diff --git a/openstack/proxy.py b/openstack/proxy.py index 9260222ae..924354baf 100644 --- a/openstack/proxy.py +++ b/openstack/proxy.py @@ -144,10 +144,17 @@ class Proxy(adapter.Adapter): def request( self, url, method, error_message=None, - raise_exc=False, connect_retries=1, *args, **kwargs): + raise_exc=False, connect_retries=1, + global_request_id=None, *args, **kwargs): + if not global_request_id: + conn = self._get_connection() + if conn: + # Per-request setting should take precedence + global_request_id = conn._global_request_id response = super(Proxy, self).request( url, method, connect_retries=connect_retries, raise_exc=False, + global_request_id=global_request_id, **kwargs) for h in response.history: self._report_stats(h) diff --git a/openstack/tests/unit/cloud/test_shade.py b/openstack/tests/unit/cloud/test_shade.py index 1cd777df3..bd16d684a 100644 --- a/openstack/tests/unit/cloud/test_shade.py +++ b/openstack/tests/unit/cloud/test_shade.py @@ -106,6 +106,44 @@ class TestShade(base.TestCase): r = self.cloud.get_image('doesNotExist') self.assertIsNone(r) + def test_global_request_id(self): + request_id = uuid.uuid4().hex + self.register_uris([ + self.get_nova_discovery_mock_dict(), + dict( + method='GET', + uri=self.get_mock_url( + 'compute', 'public', append=['servers', 'detail']), + json={'servers': []}, + validate=dict( + headers={'X-Openstack-Request-Id': request_id}), + ), + ]) + + cloud2 = self.cloud.global_request(request_id) + self.assertEqual([], cloud2.list_servers()) + + self.assert_calls() + + def test_global_request_id_context(self): + request_id = uuid.uuid4().hex + self.register_uris([ + self.get_nova_discovery_mock_dict(), + dict( + method='GET', + uri=self.get_mock_url( + 'compute', 'public', append=['servers', 'detail']), + json={'servers': []}, + validate=dict( + headers={'X-Openstack-Request-Id': request_id}), + ), + ]) + + with self.cloud.global_request(request_id) as c2: + self.assertEqual([], c2.list_servers()) + + self.assert_calls() + def test_get_server(self): server1 = fakes.make_fake_server('123', 'mickey') server2 = fakes.make_fake_server('345', 'mouse') diff --git a/releasenotes/notes/global-request-id-d7c0736f43929165.yaml b/releasenotes/notes/global-request-id-d7c0736f43929165.yaml new file mode 100644 index 000000000..b2677a0c8 --- /dev/null +++ b/releasenotes/notes/global-request-id-d7c0736f43929165.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Added support for setting ``global_request_id`` on a ``Connection``. + If done, this will cause all requests sent to send the request id + header to the OpenStack services. Since ``Connection`` can otherwise + be used multi-threaded, add a method ``global_request`` that returns + a new ``Connection`` based on the old ``Connection`` but on which + the new ``global_request_id`` has been set. Since a ``Connection`` + can be used as a context manager, this also means the ``global_request`` + method can be used in ``with`` statements. diff --git a/requirements.txt b/requirements.txt index 42da24b3c..7985b04bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,7 +8,7 @@ requestsexceptions>=1.2.0 # Apache-2.0 jsonpatch!=1.20,>=1.16 # BSD six>=1.10.0 # MIT os-service-types>=1.7.0 # Apache-2.0 -keystoneauth1>=3.14.0 # Apache-2.0 +keystoneauth1>=3.15.0 # Apache-2.0 munch>=2.1.0 # MIT decorator>=3.4.0 # BSD