Browse Source

Merge "Add strict_proxies option for Connection"

tags/0.35.0^0
Zuul 1 month ago
parent
commit
2ae21b2321

+ 12
- 0
openstack/connection.py View File

@@ -273,6 +273,7 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta,
273 273
                  oslo_conf=None,
274 274
                  service_types=None,
275 275
                  global_request_id=None,
276
+                 strict_proxies=False,
276 277
                  **kwargs):
277 278
         """Create a connection to a cloud.
278 279
 
@@ -330,12 +331,23 @@ class Connection(six.with_metaclass(_meta.ConnectionMeta,
330 331
             **Currently only supported in conjunction with the ``oslo_conf``
331 332
             kwarg.**
332 333
         :param global_request_id: A Request-id to send with all interactions.
334
+        :param strict_proxies:
335
+            If True, check proxies on creation and raise
336
+            ServiceDiscoveryException if the service is unavailable.
337
+        :type strict_proxies: bool
338
+            Throw an ``openstack.exceptions.ServiceDiscoveryException`` if the
339
+            endpoint for a given service doesn't work. This is useful for
340
+            OpenStack services using sdk to talk to other OpenStack services
341
+            where it can be expected that the deployer config is correct and
342
+            errors should be reported immediately.
343
+            Default false.
333 344
         :param kwargs: If a config is not provided, the rest of the parameters
334 345
             provided are assumed to be arguments to be passed to the
335 346
             CloudRegion constructor.
336 347
         """
337 348
         self.config = config
338 349
         self._extra_services = {}
350
+        self._strict_proxies = strict_proxies
339 351
         if extra_services:
340 352
             for service in extra_services:
341 353
                 self._extra_services[service.service_type] = service

+ 4
- 0
openstack/exceptions.py View File

@@ -257,3 +257,7 @@ class TaskManagerStopped(SDKException):
257 257
 
258 258
 class ServiceDisabledException(ConfigException):
259 259
     """This service is disabled for reasons."""
260
+
261
+
262
+class ServiceDiscoveryException(SDKException):
263
+    """The service cannot be discovered."""

+ 33
- 4
openstack/service_description.py View File

@@ -17,6 +17,7 @@ import os_service_types
17 17
 
18 18
 from openstack import _log
19 19
 from openstack import exceptions
20
+from openstack import proxy as proxy_mod
20 21
 
21 22
 __all__ = [
22 23
     'ServiceDescription',
@@ -83,10 +84,34 @@ class ServiceDescription(object):
83 84
         if instance is None:
84 85
             return self
85 86
         if self.service_type not in instance._proxies:
86
-            instance._proxies[self.service_type] = self._make_proxy(instance)
87
-            instance._proxies[self.service_type]._connection = instance
87
+            proxy = self._make_proxy(instance)
88
+            if not isinstance(proxy, _ServiceDisabledProxyShim):
89
+                # The keystone proxy has a method called get_endpoint
90
+                # that is about managing keystone endpoints. This is
91
+                # unfortunate.
92
+                endpoint = proxy_mod.Proxy.get_endpoint(proxy)
93
+                if instance._strict_proxies:
94
+                    self._validate_proxy(proxy, endpoint)
95
+                proxy._connection = instance
96
+            instance._proxies[self.service_type] = proxy
88 97
         return instance._proxies[self.service_type]
89 98
 
99
+    def _validate_proxy(self, proxy, endpoint):
100
+        exc = None
101
+        service_url = getattr(proxy, 'skip_discovery', None)
102
+        try:
103
+            # Don't go too wild for e.g. swift
104
+            if service_url is None:
105
+                service_url = proxy.get_endpoint_data().service_url
106
+        except Exception as e:
107
+            exc = e
108
+        if exc or not endpoint or not service_url:
109
+            raise exceptions.ServiceDiscoveryException(
110
+                "Failed to create a working proxy for service {service_type}: "
111
+                "{message}".format(
112
+                    service_type=self.service_type,
113
+                    message=exc or "No valid endpoint was discoverable."))
114
+
90 115
     def _make_proxy(self, instance):
91 116
         """Create a Proxy for the service in question.
92 117
 
@@ -108,8 +133,6 @@ class ServiceDescription(object):
108 133
                 self.service_type,
109 134
                 allow_version_hack=True,
110 135
             )
111
-            # trigger EndpointNotFound exception if this is bogus
112
-            temp_client.get_endpoint()
113 136
             return temp_client
114 137
 
115 138
         # Check to see if we've got config that matches what we
@@ -173,6 +196,12 @@ class ServiceDescription(object):
173 196
                 return proxy_obj
174 197
 
175 198
             data = proxy_obj.get_endpoint_data()
199
+            if not data and instance._strict_proxies:
200
+                raise exceptions.ServiceDiscoveryException(
201
+                    "Failed to create a working proxy for service "
202
+                    "{service_type}: No endpoint data found.".format(
203
+                        service_type=self.service_type))
204
+
176 205
             # If we've gotten here with a proxy object it means we have
177 206
             # an endpoint_override in place. If the catalog_url and
178 207
             # service_url don't match, which can happen if there is a

+ 2
- 1
openstack/tests/unit/base.py View File

@@ -673,7 +673,8 @@ class TestCase(base.TestCase):
673 673
             if 'content-type' not in headers:
674 674
                 headers[u'content-type'] = 'application/json'
675 675
 
676
-            to_mock['headers'] = headers
676
+            if 'exc' not in to_mock:
677
+                to_mock['headers'] = headers
677 678
 
678 679
             self.calls += [
679 680
                 dict(

+ 90
- 1
openstack/tests/unit/config/test_from_conf.py View File

@@ -10,6 +10,7 @@
10 10
 # License for the specific language governing permissions and limitations
11 11
 # under the License.
12 12
 
13
+import requests.exceptions
13 14
 import uuid
14 15
 
15 16
 from keystoneauth1 import exceptions as ks_exc
@@ -31,7 +32,7 @@ class TestFromConf(base.TestCase):
31 32
             **from_conf_kwargs)
32 33
         self.assertEqual('from_conf.example.com', config.name)
33 34
 
34
-        return connection.Connection(config=config)
35
+        return connection.Connection(config=config, strict_proxies=True)
35 36
 
36 37
     def test_adapter_opts_set(self):
37 38
         """Adapter opts specified in the conf."""
@@ -75,6 +76,18 @@ class TestFromConf(base.TestCase):
75 76
         """Adapter opts are registered, but all defaulting in conf."""
76 77
         conn = self._get_conn()
77 78
 
79
+        server_id = str(uuid.uuid4())
80
+        server_name = self.getUniqueString('name')
81
+        fake_server = fakes.make_fake_server(server_id, server_name)
82
+
83
+        self.register_uris([
84
+            self.get_nova_discovery_mock_dict(),
85
+            dict(method='GET',
86
+                 uri=self.get_mock_url(
87
+                     'compute', 'public', append=['servers', 'detail']),
88
+                 json={'servers': [fake_server]}),
89
+        ])
90
+
78 91
         # Nova has empty adapter config, so these default
79 92
         adap = conn.compute
80 93
         self.assertIsNone(adap.region_name)
@@ -82,16 +95,41 @@ class TestFromConf(base.TestCase):
82 95
         self.assertEqual('public', adap.interface)
83 96
         self.assertIsNone(adap.endpoint_override)
84 97
 
98
+        s = next(adap.servers())
99
+        self.assertEqual(s.id, server_id)
100
+        self.assertEqual(s.name, server_name)
101
+        self.assert_calls()
102
+
103
+    def test_service_not_ready_catalog(self):
104
+        """Adapter opts are registered, but all defaulting in conf."""
105
+        conn = self._get_conn()
106
+
85 107
         server_id = str(uuid.uuid4())
86 108
         server_name = self.getUniqueString('name')
87 109
         fake_server = fakes.make_fake_server(server_id, server_name)
110
+
88 111
         self.register_uris([
112
+            dict(method='GET',
113
+                 uri='https://compute.example.com/v2.1/',
114
+                 exc=requests.exceptions.ConnectionError),
89 115
             self.get_nova_discovery_mock_dict(),
90 116
             dict(method='GET',
91 117
                  uri=self.get_mock_url(
92 118
                      'compute', 'public', append=['servers', 'detail']),
93 119
                  json={'servers': [fake_server]}),
94 120
         ])
121
+
122
+        self.assertRaises(
123
+            exceptions.ServiceDiscoveryException,
124
+            getattr, conn, 'compute')
125
+
126
+        # Nova has empty adapter config, so these default
127
+        adap = conn.compute
128
+        self.assertIsNone(adap.region_name)
129
+        self.assertEqual('compute', adap.service_type)
130
+        self.assertEqual('public', adap.interface)
131
+        self.assertIsNone(adap.endpoint_override)
132
+
95 133
         s = next(adap.servers())
96 134
         self.assertEqual(s.id, server_id)
97 135
         self.assertEqual(s.name, server_name)
@@ -119,6 +157,11 @@ class TestFromConf(base.TestCase):
119 157
             dict(method='GET',
120 158
                  uri='https://example.org:5050',
121 159
                  json=discovery),
160
+            # strict-proxies means we're going to fetch the discovery
161
+            # doc from the versioned endpoint to verify it works.
162
+            dict(method='GET',
163
+                 uri='https://example.org:5050/v1',
164
+                 json=discovery),
122 165
             dict(method='GET',
123 166
                  uri='https://example.org:5050/v1/introspection/abcd',
124 167
                  json=status),
@@ -131,6 +174,52 @@ class TestFromConf(base.TestCase):
131 174
 
132 175
         self.assertTrue(adap.get_introspection('abcd').is_finished)
133 176
 
177
+    def test_service_not_ready_endpoint_override(self):
178
+        conn = self._get_conn()
179
+
180
+        discovery = {
181
+            "versions": {
182
+                "values": [
183
+                    {"status": "stable",
184
+                     "id": "v1",
185
+                     "links": [{
186
+                         "href": "https://example.org:5050/v1",
187
+                         "rel": "self"}]
188
+                     }]
189
+            }
190
+        }
191
+        status = {
192
+            'finished': True,
193
+            'error': None
194
+        }
195
+        self.register_uris([
196
+            dict(method='GET',
197
+                 uri='https://example.org:5050',
198
+                 exc=requests.exceptions.ConnectTimeout),
199
+            dict(method='GET',
200
+                 uri='https://example.org:5050',
201
+                 json=discovery),
202
+            # strict-proxies means we're going to fetch the discovery
203
+            # doc from the versioned endpoint to verify it works.
204
+            dict(method='GET',
205
+                 uri='https://example.org:5050/v1',
206
+                 json=discovery),
207
+            dict(method='GET',
208
+                 uri='https://example.org:5050/v1/introspection/abcd',
209
+                 json=status),
210
+        ])
211
+
212
+        self.assertRaises(
213
+            exceptions.ServiceDiscoveryException,
214
+            getattr, conn, 'baremetal_introspection')
215
+
216
+        adap = conn.baremetal_introspection
217
+        self.assertEqual('baremetal-introspection', adap.service_type)
218
+        self.assertEqual('public', adap.interface)
219
+        self.assertEqual('https://example.org:5050/v1', adap.endpoint_override)
220
+
221
+        self.assertTrue(adap.get_introspection('abcd').is_finished)
222
+
134 223
     def assert_service_disabled(self, service_type, expected_reason,
135 224
                                 **from_conf_kwargs):
136 225
         conn = self._get_conn(**from_conf_kwargs)

+ 8
- 0
releasenotes/notes/strict-proxies-4a315f68f387ee89.yaml View File

@@ -0,0 +1,8 @@
1
+---
2
+features:
3
+  - |
4
+    Added new option for Connection, ``strict_proxies``. When set to ``True``,
5
+    Connection will throw a ``ServiceDiscoveryException`` if the endpoint for
6
+    a given service doesn't work. This is useful for OpenStack services using
7
+    sdk to talk to other OpenStack services where it can be expected that the
8
+    deployer config is correct and errors should be reported immediately.

Loading…
Cancel
Save