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
This commit is contained in:
Monty Taylor
2019-08-06 09:02:33 -04:00
parent 57b634b0e0
commit 95bf14908e
7 changed files with 118 additions and 8 deletions

View File

@@ -14,7 +14,7 @@ jmespath==0.9.0
jsonpatch==1.16 jsonpatch==1.16
jsonpointer==1.13 jsonpointer==1.13
jsonschema==2.6.0 jsonschema==2.6.0
keystoneauth1==3.14.0 keystoneauth1==3.15.0
linecache2==1.0.0 linecache2==1.0.0
mock==2.0.0 mock==2.0.0
mox3==0.20.0 mox3==0.20.0

View File

@@ -33,6 +33,7 @@ from openstack.cloud import _object_store
from openstack.cloud import meta from openstack.cloud import meta
from openstack.cloud import _utils from openstack.cloud import _utils
import openstack.config import openstack.config
from openstack.config import cloud_region as cloud_region_mod
from openstack import proxy from openstack import proxy
DEFAULT_SERVER_AGE = 5 DEFAULT_SERVER_AGE = 5
@@ -226,16 +227,16 @@ class _OpenStackCloudMixin(object):
for key, value in kwargs.items(): for key, value in kwargs.items():
params['auth'][key] = value 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 # Attach the discovery cache from the old session so we won't
# double discover. # 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 # Override the cloud name so that logging/location work right
cloud_config._name = self.name cloud_region._name = self.name
cloud_config.config['profile'] = self.name cloud_region.config['profile'] = self.name
# Use self.__class__ so that we return whatever this if, like if it's # Use self.__class__ so that we return whatever this if, like if it's
# a subclass in the case of shade wrapping sdk. # 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): def connect_as_project(self, project):
"""Make a new OpenStackCloud object with a new project. """Make a new OpenStackCloud object with a new project.
@@ -267,6 +268,53 @@ class _OpenStackCloudMixin(object):
auth['project_name'] = project auth['project_name'] = project
return self.connect_as(**auth) 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): def _make_cache(self, cache_class, expiration_time, arguments):
return dogpile.cache.make_region( return dogpile.cache.make_region(
function_key_generator=self._make_cache_key function_key_generator=self._make_cache_key

View File

@@ -272,6 +272,7 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta,
rate_limit=None, rate_limit=None,
oslo_conf=None, oslo_conf=None,
service_types=None, service_types=None,
global_request_id=None,
**kwargs): **kwargs):
"""Create a connection to a cloud. """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). other service types will be disabled (will error if used).
**Currently only supported in conjunction with the ``oslo_conf`` **Currently only supported in conjunction with the ``oslo_conf``
kwarg.** 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 :param kwargs: If a config is not provided, the rest of the parameters
provided are assumed to be arguments to be passed to the provided are assumed to be arguments to be passed to the
CloudRegion constructor. CloudRegion constructor.
@@ -363,6 +365,7 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta,
self._session = None self._session = None
self._proxies = {} self._proxies = {}
self.__pool_executor = None self.__pool_executor = None
self._global_request_id = global_request_id
self.use_direct_get = use_direct_get self.use_direct_get = use_direct_get
self.strict_mode = strict self.strict_mode = strict
# Call the _*CloudMixin constructors while we work on # Call the _*CloudMixin constructors while we work on
@@ -479,6 +482,9 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta,
if self.__pool_executor: if self.__pool_executor:
self.__pool_executor.shutdown() self.__pool_executor.shutdown()
def set_global_request_id(self, global_request_id):
self._global_request_id = global_request_id
def __enter__(self): def __enter__(self):
return self return self

View File

@@ -144,10 +144,17 @@ class Proxy(adapter.Adapter):
def request( def request(
self, url, method, error_message=None, 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( response = super(Proxy, self).request(
url, method, url, method,
connect_retries=connect_retries, raise_exc=False, connect_retries=connect_retries, raise_exc=False,
global_request_id=global_request_id,
**kwargs) **kwargs)
for h in response.history: for h in response.history:
self._report_stats(h) self._report_stats(h)

View File

@@ -106,6 +106,44 @@ class TestShade(base.TestCase):
r = self.cloud.get_image('doesNotExist') r = self.cloud.get_image('doesNotExist')
self.assertIsNone(r) 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): def test_get_server(self):
server1 = fakes.make_fake_server('123', 'mickey') server1 = fakes.make_fake_server('123', 'mickey')
server2 = fakes.make_fake_server('345', 'mouse') server2 = fakes.make_fake_server('345', 'mouse')

View File

@@ -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.

View File

@@ -8,7 +8,7 @@ requestsexceptions>=1.2.0 # Apache-2.0
jsonpatch!=1.20,>=1.16 # BSD jsonpatch!=1.20,>=1.16 # BSD
six>=1.10.0 # MIT six>=1.10.0 # MIT
os-service-types>=1.7.0 # Apache-2.0 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 munch>=2.1.0 # MIT
decorator>=3.4.0 # BSD decorator>=3.4.0 # BSD