def url_for(self, **kwargs): kwargs.setdefault('interface', 'public') kwargs.setdefault('service_type', None) if kwargs['service_type'] == 'object-store': return self.storage_url # Although our "catalog" includes an identity entry, nothing that uses # url_for() (including `openstack endpoint list`) will know what to do # with it. Better to just raise the exception, cribbing error messages # from keystoneauth1/access/service_catalog.py if 'service_name' in kwargs and 'region_name' in kwargs: msg = ('%(interface)s endpoint for %(service_type)s service ' 'named %(service_name)s in %(region_name)s region not ' 'found' % kwargs) elif 'service_name' in kwargs: msg = ('%(interface)s endpoint for %(service_type)s service ' 'named %(service_name)s not found' % kwargs) elif 'region_name' in kwargs: msg = ('%(interface)s endpoint for %(service_type)s service ' 'in %(region_name)s region not found' % kwargs) else: msg = ('%(interface)s endpoint for %(service_type)s service ' 'not found' % kwargs) raise exceptions.EndpointNotFound(msg)
def url_for(self, service_type=None, interface='public', region_name=None, service_name=None, service_id=None, endpoint_id=None): """Fetch an endpoint from the service catalog. Fetch the specified endpoint from the service catalog for a particular endpoint attribute. If no attribute is given, return the first endpoint of the specified type. Valid endpoint types: `public` or `publicURL`, `internal` or `internalURL`, `admin` or 'adminURL` :param string service_type: Service type of the endpoint. :param string interface: Type of endpoint. :param string region_name: Region of the endpoint. :param string service_name: The assigned name of the service. :param string service_id: The identifier of a service. :param string endpoint_id: The identifier of an endpoint. """ if not self._catalog: raise exceptions.EmptyCatalog('The service catalog is empty.') urls = self.get_urls(service_type=service_type, interface=interface, region_name=region_name, service_name=service_name, service_id=service_id, endpoint_id=endpoint_id) try: return urls[0] except Exception: pass if service_name and region_name: msg = ('%(interface)s endpoint for %(service_type)s service ' 'named %(service_name)s in %(region_name)s region not ' 'found' % {'interface': interface, 'service_type': service_type, 'service_name': service_name, 'region_name': region_name}) elif service_name: msg = ('%(interface)s endpoint for %(service_type)s service ' 'named %(service_name)s not found' % {'interface': interface, 'service_type': service_type, 'service_name': service_name}) elif region_name: msg = ('%(interface)s endpoint for %(service_type)s service ' 'in %(region_name)s region not found' % {'interface': interface, 'service_type': service_type, 'region_name': region_name}) else: msg = ('%(interface)s endpoint for %(service_type)s service ' 'not found' % {'interface': interface, 'service_type': service_type}) raise exceptions.EndpointNotFound(msg)
def url_for(self, **kwargs): keystone_session = self.context.keystone_session def get_endpoint(): return keystone_session.get_endpoint(**kwargs) # NOTE(jamielennox): use the session defined by the keystoneclient # options as traditionally the token was always retrieved from # keystoneclient. try: kwargs.setdefault('interface', kwargs.pop('endpoint_type')) except KeyError: pass reg = self.context.region_name or cfg.CONF.region_name_for_services kwargs.setdefault('region_name', reg) url = None try: url = get_endpoint() except exceptions.EmptyCatalog: endpoint = keystone_session.get_endpoint( None, interface=plugin.AUTH_INTERFACE) token = keystone_session.get_token(None) token_obj = generic.Token(endpoint, token) auth_ref = token_obj.get_access(keystone_session) if auth_ref.has_service_catalog(): self.context.reload_auth_plugin() url = get_endpoint() # NOTE(jamielennox): raising exception maintains compatibility with # older keystoneclient service catalog searching. if url is None: raise exceptions.EndpointNotFound() return url
def test_check_cinder_exists(self, mock_url_for): mock_url_for.return_value = None self.assertTrue(cinder.check_cinder_exists()) mock_url_for.reset_mock() mock_url_for.side_effect = keystone_exceptions.EndpointNotFound() self.assertFalse(cinder.check_cinder_exists())
def test_endpoint_not_found(self): self.session.get_endpoint.side_effect = exceptions.EndpointNotFound() self.assertRaises(http.EndpointNotFound, self.get_client) self.session.get_endpoint.assert_called_once_with( service_type='baremetal-introspection', interface=None, region_name=None)
def test_get_service_url_internal_fail(self): session = mock.Mock() session.get_endpoint.side_effect = [ksexception.EndpointNotFound(), 'spam'] params = {'ham': 'eggs'} self.assertEqual('spam', keystone.get_service_url(session, **params)) session.get_endpoint.assert_has_calls([ mock.call(interface='internal', **params), mock.call(interface='public', **params)])
def _get_identity_endpoint_from_sc(context): # Search for the identity endpoint in the service catalog for service in context.service_catalog: if service.get('type') != 'identity': continue for endpoint in service['endpoints']: if (not CONF[NOVA_GROUP].region_name or endpoint.get('region') == CONF[NOVA_GROUP].region_name): return endpoint.get(CONF[NOVA_GROUP].interface + 'URL') raise ks_exc.EndpointNotFound()
def test_invalid_endpoint(self, get): """Test failure when no endpoint exists. Replicate in devstack: start devstack without placement engine, but create valid placement service user and specify it in auth section of [placement] in nova.conf. """ get.side_effect = ks_exc.EndpointNotFound() res = self.cmd._check_placement() self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code) self.assertIn('Placement API endpoint not found', res.details)
def test_wrong_version(self, get): """Test endpoint not found. EndpointNotFound will be made when the keystone v3 API is not found in the service catalog, or if the v2.0 endpoint was registered as the root endpoint. We treat this the same as 404. """ get.side_effect = kse.EndpointNotFound() self.assertRaises(webob.exc.HTTPBadRequest, identity.verify_project_id, mock.MagicMock(), "foo")
def get_endpoint(ksa_adapter): """Get the endpoint URL represented by a keystoneauth1 Adapter. This method is equivalent to what ksa_adapter.get_endpoint() should do, if it weren't for a panoply of bugs. :param ksa_adapter: keystoneauth1.adapter.Adapter, appropriately set up with an endpoint_override; or service_type, interface (list) and auth/service_catalog. :return: String endpoint URL. :raise EndpointNotFound: If endpoint discovery fails. """ # TODO(efried): This will be unnecessary once bug #1707993 is fixed. # (At least for the non-image case, until 1707995 is fixed.) if ksa_adapter.endpoint_override: return ksa_adapter.endpoint_override # TODO(efried): Remove this once bug #1707995 is fixed. if ksa_adapter.service_type == 'image': try: # LOG.warning(ksa_adapter.__dict__) return ksa_adapter.get_endpoint_data().catalog_url except AttributeError: # ksa_adapter.auth is a _ContextAuthPlugin, which doesn't have # get_endpoint_data. Fall through to using get_endpoint(). pass # TODO(efried): The remainder of this method reduces to # TODO(efried): return ksa_adapter.get_endpoint() # TODO(efried): once bug #1709118 is fixed. # NOTE(efried): Id9bd19cca68206fc64d23b0eaa95aa3e5b01b676 may also do the # trick, once it's in a ksa release. # The EndpointNotFound exception happens when _ContextAuthPlugin is in play # because its get_endpoint() method isn't yet set up to handle interface as # a list. (It could also happen with a real auth if the endpoint isn't # there; but that's covered below.) try: return ksa_adapter.get_endpoint() except ks_exc.EndpointNotFound: pass interfaces = list(ksa_adapter.interface) for interface in interfaces: ksa_adapter.interface = interface try: return ksa_adapter.get_endpoint() except ks_exc.EndpointNotFound: pass raise ks_exc.EndpointNotFound( "Could not find requested endpoint for any of the following " "interfaces: %s" % interfaces)
def test_ok_endpoint_not_found(self): self.session.get_endpoint.side_effect = exceptions.EndpointNotFound() res = self.get_client().request('get', '/foo/bar') self.assertIs(self.req.return_value, res) self.req.assert_called_once_with(self.base_url + '/foo/bar', 'get', raise_exc=False, headers=self.headers) self.session.get_endpoint.assert_called_once_with( service_type='baremetal-introspection', interface=None, region_name=None)
def test_missing_endpoint(self, req): """Test EndpointNotFound behavior. A missing endpoint entry should permanently disable the client. And make future calls to it not happen. """ req.side_effect = ks_exc.EndpointNotFound() self.client._get_resource_provider("fake") self.assertTrue(self.client._disabled) # reset the call count to demonstrate that future calls don't # work req.reset_mock() self.client._get_resource_provider("fake") req.assert_not_called()
def test_early_fail(self, get): get.side_effect = kse.EndpointNotFound() self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo"))
def request(self, url, method, json=None, original_ip=None, user_agent=None, redirect=None, authenticated=None, endpoint_filter=None, auth=None, requests_auth=None, raise_exc=True, allow_reauth=True, log=True, endpoint_override=None, connect_retries=0, logger=_logger, allow={}, client_name=None, client_version=None, microversion=None, microversion_service_type=None, **kwargs): """Send an HTTP request with the specified characteristics. Wrapper around `requests.Session.request` to handle tasks such as setting headers, JSON encoding/decoding, and error handling. Arguments that are not handled are passed through to the requests library. :param str url: Path or fully qualified URL of HTTP request. If only a path is provided then endpoint_filter must also be provided such that the base URL can be determined. If a fully qualified URL is provided then endpoint_filter will be ignored. :param str method: The http method to use. (e.g. 'GET', 'POST') :param str original_ip: Mark this request as forwarded for this ip. (optional) :param dict headers: Headers to be included in the request. (optional) :param json: Some data to be represented as JSON. (optional) :param str user_agent: A user_agent to use for the request. If present will override one present in headers. (optional) :param int/bool redirect: the maximum number of redirections that can be followed by a request. Either an integer for a specific count or True/False for forever/never. (optional) :param int connect_retries: the maximum number of retries that should be attempted for connection errors. (optional, defaults to 0 - never retry). :param bool authenticated: True if a token should be attached to this request, False if not or None for attach if an auth_plugin is available. (optional, defaults to None) :param dict endpoint_filter: Data to be provided to an auth plugin with which it should be able to determine an endpoint to use for this request. If not provided then URL is expected to be a fully qualified URL. (optional) :param str endpoint_override: The URL to use instead of looking up the endpoint in the auth plugin. This will be ignored if a fully qualified URL is provided but take priority over an endpoint_filter. This string may contain the values ``%(project_id)s`` and ``%(user_id)s`` to have those values replaced by the project_id/user_id of the current authentication. (optional) :param auth: The auth plugin to use when authenticating this request. This will override the plugin that is attached to the session (if any). (optional) :type auth: keystoneauth1.plugin.BaseAuthPlugin :param requests_auth: A requests library auth plugin that cannot be passed via kwarg because the `auth` kwarg collides with our own auth plugins. (optional) :type requests_auth: :py:class:`requests.auth.AuthBase` :param bool raise_exc: If True then raise an appropriate exception for failed HTTP requests. If False then return the request object. (optional, default True) :param bool allow_reauth: Allow fetching a new token and retrying the request on receiving a 401 Unauthorized response. (optional, default True) :param bool log: If True then log the request and response data to the debug log. (optional, default True) :param logger: The logger object to use to log request and responses. If not provided the keystoneauth1.session default logger will be used. :type logger: logging.Logger :param dict allow: Extra filters to pass when discovering API versions. (optional) :param microversion: Microversion to send for this request. microversion can be given as a string or a tuple. (optional) :param str microversion_service_type: The service_type to be sent in the microversion header, if a microversion is given. Defaults to the value of service_type from endpoint_filter if one exists. If endpoint_filter is not provided or does not have a service_type, microversion is given and microversion_service_type is not provided, an exception will be raised. :param kwargs: any other parameter that can be passed to :meth:`requests.Session.request` (such as `headers`). Except: - `data` will be overwritten by the data in the `json` param. - `allow_redirects` is ignored as redirects are handled by the session. :raises keystoneauth1.exceptions.base.ClientException: For connection failure, or to indicate an error response code. :returns: The response to the request. """ headers = kwargs.setdefault('headers', dict()) if microversion: self._set_microversion_headers(headers, microversion, microversion_service_type, endpoint_filter) if authenticated is None: authenticated = bool(auth or self.auth) if authenticated: auth_headers = self.get_auth_headers(auth) if auth_headers is None: msg = 'No valid authentication is available' raise exceptions.AuthorizationFailure(msg) headers.update(auth_headers) if osprofiler_web: headers.update(osprofiler_web.get_trace_id_headers()) # if we are passed a fully qualified URL and an endpoint_filter we # should ignore the filter. This will make it easier for clients who # want to overrule the default endpoint_filter data added to all client # requests. We check fully qualified here by the presence of a host. if not urllib.parse.urlparse(url).netloc: base_url = None if endpoint_override: base_url = endpoint_override % _StringFormatter(self, auth) elif endpoint_filter: base_url = self.get_endpoint(auth, allow=allow, **endpoint_filter) if not base_url: raise exceptions.EndpointNotFound() url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) if self.cert: kwargs.setdefault('cert', self.cert) if self.timeout is not None: kwargs.setdefault('timeout', self.timeout) if user_agent: headers['User-Agent'] = user_agent elif self.user_agent: user_agent = headers.setdefault('User-Agent', self.user_agent) else: # Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be # ordered by decreasing significance. If a user sets their product # that value will be used. Otherwise we attempt to derive a useful # product value. The value will be prepended it to the KSA version, # requests version, and then the Python version. agent = [] if self.app_name and self.app_version: agent.append('%s/%s' % (self.app_name, self.app_version)) elif self.app_name: agent.append(self.app_name) for additional in self.additional_user_agent: agent.append('%s/%s' % additional) if client_name and client_version: agent.append('%s/%s' % (client_name, client_version)) elif client_name: agent.append(client_name) if not agent: # NOTE(jamielennox): determine_user_agent will return an empty # string on failure so checking for None will ensure it is only # called once even on failure. if self._determined_user_agent is None: self._determined_user_agent = _determine_user_agent() if self._determined_user_agent: agent.append(self._determined_user_agent) agent.append(DEFAULT_USER_AGENT) user_agent = headers.setdefault('User-Agent', ' '.join(agent)) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: headers.setdefault('Content-Type', 'application/json') kwargs['data'] = self._json.encode(json) for k, v in self.additional_headers.items(): headers.setdefault(k, v) kwargs.setdefault('verify', self.verify) if requests_auth: kwargs['auth'] = requests_auth # Query parameters that are included in the url string will # be logged properly, but those sent in the `params` parameter # (which the requests library handles) need to be explicitly # picked out so they can be included in the URL that gets loggged. query_params = kwargs.get('params', dict()) if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, query_params=query_params, logger=logger) # Force disable requests redirect handling. We will manage this below. kwargs['allow_redirects'] = False if redirect is None: redirect = self.redirect send = functools.partial(self._send_request, url, method, redirect, log, logger, connect_retries) try: connection_params = self.get_auth_connection_params(auth=auth) except exceptions.MissingAuthPlugin: # NOTE(jamielennox): If we've gotten this far without an auth # plugin then we should be happy with allowing no additional # connection params. This will be the typical case for plugins # anyway. pass else: if connection_params: kwargs.update(connection_params) resp = send(**kwargs) # log callee and caller request-id for each api call if log: # service_name should be fetched from endpoint_filter if it is not # present then use service_type as service_name. service_name = None if endpoint_filter: service_name = endpoint_filter.get('service_name') if not service_name: service_name = endpoint_filter.get('service_type') # Nova uses 'x-compute-request-id' and other services like # Glance, Cinder etc are using 'x-openstack-request-id' to store # request-id in the header request_id = (resp.headers.get('x-openstack-request-id') or resp.headers.get('x-compute-request-id')) if request_id: logger.debug( '%(method)s call to %(service_name)s for ' '%(url)s used request id ' '%(response_request_id)s', { 'method': resp.request.method, 'service_name': service_name, 'url': resp.url, 'response_request_id': request_id }) # handle getting a 401 Unauthorized response by invalidating the plugin # and then retrying the request. This is only tried once. if resp.status_code == 401 and authenticated and allow_reauth: if self.invalidate(auth): auth_headers = self.get_auth_headers(auth) if auth_headers is not None: headers.update(auth_headers) resp = send(**kwargs) if raise_exc and resp.status_code >= 400: logger.debug('Request returned failure status: %s', resp.status_code) raise exceptions.from_response(resp, method, url) return resp
def request(self, url, method, json=None, original_ip=None, user_agent=None, redirect=None, endpoint_filter=None, raise_exc=True, log=True, microversion=None, endpoint_override=None, connect_retries=0, allow=None, client_name=None, client_version=None, **kwargs): self._determined_user_agent = None headers = kwargs.setdefault('headers', dict()) auth_headers = self.get_auth_headers() if auth_headers is None: msg = 'No valid authentication is available' raise exceptions.AuthorizationFailure(msg) headers.update(auth_headers) base_url = "" if not urllib.parse.urlparse(url).netloc: if endpoint_override: base_url = endpoint_override # base_url = endpoint_override % {"project_id": self.project_id} elif endpoint_filter: base_url = self.get_endpoint( interface=endpoint_filter.interface, service_type=endpoint_filter.service_type) if not urllib.parse.urlparse(base_url).netloc: raise exceptions.EndpointNotFound() url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) headers.setdefault("Host", urllib.parse.urlparse(url).netloc) if self.cert: kwargs.setdefault('cert', self.cert) if self.timeout is not None: kwargs.setdefault('timeout', self.timeout) if user_agent: headers['User-Agent'] = user_agent elif self.user_agent: user_agent = headers.setdefault('User-Agent', self.user_agent) else: agent = [] if self.app_name and self.app_version: agent.append('%s/%s' % (self.app_name, self.app_version)) elif self.app_name: agent.append(self.app_name) if client_name and client_version: agent.append('%s/%s' % (client_name, client_version)) elif client_name: agent.append(client_name) for additional in self.additional_user_agent: agent.append('%s/%s' % additional) if not agent: # NOTE(jamielennox): determine_user_agent will return an empty # string on failure so checking for None will ensure it is only # called once even on failure. if self._determined_user_agent is None: self._determined_user_agent = _determine_user_agent() if self._determined_user_agent: agent.append(self._determined_user_agent) agent.append(DEFAULT_USER_AGENT) user_agent = headers.setdefault('User-Agent', ' '.join(agent)) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: kwargs['data'] = self._json.encode(json) headers.setdefault('Content-Type', 'application/json') if self.additional_headers: for k, v in self.additional_headers.items(): headers.setdefault(k, v) kwargs.setdefault('verify', self.verify) # if requests_auth: # kwargs['auth'] = requests_auth # Query parameters that are included in the url string will # be logged properly, but those sent in the `params` parameter # (which the requests library handles) need to be explicitly # picked out so they can be included in the URL that gets loggged. query_params = kwargs.get('params', dict()) if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, query_params=query_params, logger=_logger) # Force disable requests redirect handling. We will manage this below. kwargs['allow_redirects'] = False if redirect is None: redirect = self.redirect send = functools.partial(self._send_request, url, method, redirect, log, _logger, connect_retries) resp = send(**kwargs) # log callee and caller request-id for each api call if log: # service_name should be fetched from endpoint_filter if it is not # present then use service_type as service_name. service_name = None if endpoint_filter: service_name = endpoint_filter.get('service_name') if not service_name: service_name = endpoint_filter.get('service_type') # Nova uses 'x-compute-request-id' and other services like # Glance, Cinder etc are using 'x-openstack-request-id' to store # request-id in the header request_id = (resp.headers.get('x-openstack-request-id') or resp.headers.get('x-compute-request-id')) if request_id: _logger.debug( '%(method)s call to %(service_name)s for ' '%(url)s used request id ' '%(response_request_id)s', { 'method': resp.request.method, 'service_name': service_name, 'url': resp.url, 'response_request_id': request_id }) if raise_exc and resp.status_code >= 400: _logger.debug('Request returned failure status: %s', resp.status_code) raise exceptions.from_response(resp, method, url) return resp
def fake_get_kwapi_client(ksclient, endpoint): raise exceptions.EndpointNotFound("fake keystone exception")
def request(self, url, method, json=None, original_ip=None, user_agent=None, redirect=None, endpoint_filter=None, raise_exc=True, log=True, microversion=None, endpoint_override=None, connect_retries=0, allow=None, client_name=None, client_version=None, **kwargs): headers = kwargs.setdefault('headers', dict()) if microversion: self._set_microversion_headers(headers, microversion, None, endpoint_filter) if self._securitytoken: headers.setdefault("X-Security-Token", self._securitytoken) if not urllib.parse.urlparse(url).netloc: if endpoint_override: base_url = endpoint_override % {"project_id": self.project_id} elif endpoint_filter: base_url = self.get_endpoint(interface=endpoint_filter.interface, service_type=endpoint_filter.service_type) if not urllib.parse.urlparse(base_url).netloc: raise exceptions.EndpointNotFound() url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) headers.setdefault("Host", urllib.parse.urlparse(url).netloc) if self.cert: kwargs.setdefault('cert', self.cert) if self.timeout is not None: kwargs.setdefault('timeout', self.timeout) if user_agent: headers['User-Agent'] = user_agent elif self.user_agent: user_agent = headers.setdefault('User-Agent', self.user_agent) else: # Per RFC 7231 Section 5.5.3, identifiers in a user-agent should be # ordered by decreasing significance. If a user sets their product # that value will be used. Otherwise we attempt to derive a useful # product value. The value will be prepended it to the KSA version, # requests version, and then the Python version. agent = [] if self.app_name and self.app_version: agent.append('%s/%s' % (self.app_name, self.app_version)) elif self.app_name: agent.append(self.app_name) if client_name and client_version: agent.append('%s/%s' % (client_name, client_version)) elif client_name: agent.append(client_name) for additional in self.additional_user_agent: agent.append('%s/%s' % additional) if not agent: # NOTE(jamielennox): determine_user_agent will return an empty # string on failure so checking for None will ensure it is only # called once even on failure. if self._determined_user_agent is None: self._determined_user_agent = _determine_user_agent() if self._determined_user_agent: agent.append(self._determined_user_agent) agent.append(DEFAULT_USER_AGENT) user_agent = headers.setdefault('User-Agent', ' '.join(agent)) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: kwargs['data'] = self._json.encode(json) # surpport maas,map_reduce when without request body headers.setdefault('Content-Type', 'application/json') if self.domain_id: headers.setdefault('X-Domain-Id', self.domain_id) # surpport sub-project id for some service the endpoint contain project id elif self.project_id: headers.setdefault('X-Project-Id', self.project_id) if self.additional_headers: for k, v in self.additional_headers.items(): headers.setdefault(k, v) kwargs.setdefault('verify', self.verify) # if requests_auth: # kwargs['auth'] = requests_auth # Query parameters that are included in the url string will # be logged properly, but those sent in the `params` parameter # (which the requests library handles) need to be explicitly # picked out so they can be included in the URL that gets loggged. query_params = kwargs.get('params', dict()) headers.setdefault("X-Sdk-Date", datetime.datetime.strftime(datetime.datetime.utcnow(), "%Y%m%dT%H%M%SZ")) signedstring = self.signer.signature(method=method, url=url, headers=headers, svr=endpoint_filter.service_type if endpoint_filter else '', params=query_params, data=kwargs.get("data", None)) # print(signedstring) headers.setdefault("Authorization", signedstring) if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, query_params=query_params, logger=_logger) # Force disable requests redirect handling. We will manage this below. kwargs['allow_redirects'] = False if redirect is None: redirect = self.redirect send = functools.partial(self._send_request, url, method, redirect, log, _logger, connect_retries) resp = send(**kwargs) # log callee and caller request-id for each api call if log: # service_name should be fetched from endpoint_filter if it is not # present then use service_type as service_name. service_name = None if endpoint_filter: service_name = endpoint_filter.get('service_name') if not service_name: service_name = endpoint_filter.get('service_type') # Nova uses 'x-compute-request-id' and other services like # Glance, Cinder etc are using 'x-openstack-request-id' to store # request-id in the header request_id = (resp.headers.get('x-openstack-request-id') or resp.headers.get('x-compute-request-id')) if request_id: _logger.debug('%(method)s call to %(service_name)s for ' '%(url)s used request id ' '%(response_request_id)s', {'method': resp.request.method, 'service_name': service_name, 'url': resp.url, 'response_request_id': request_id}) if raise_exc and resp.status_code >= 400: _logger.debug('Request returned failure status: %s', resp.status_code) raise exceptions.from_response(resp, method, url) return resp
def endpoint_data_for(self, service_type=None, interface='public', region_name=None, service_name=None, service_id=None, endpoint_id=None): """Fetch endpoint data from the service catalog. Fetch the specified endpoint data from the service catalog for a particular endpoint attribute. If no attribute is given, return the first endpoint of the specified type. Valid interface types: `public` or `publicURL`, `internal` or `internalURL`, `admin` or 'adminURL` :param string service_type: Service type of the endpoint. :param interface: Type of endpoint. Can be a single value or a list of values. If it's a list of values, they will be looked for in order of preference. :param string region_name: Region of the endpoint. :param string service_name: The assigned name of the service. :param string service_id: The identifier of a service. :param string endpoint_id: The identifier of an endpoint. """ if not self._catalog: raise exceptions.EmptyCatalog('The service catalog is empty.') endpoint_data_list = self.get_endpoint_data_list( service_type=service_type, interface=interface, region_name=region_name, service_name=service_name, service_id=service_id, endpoint_id=endpoint_id) if endpoint_data_list: return endpoint_data_list[0] if service_name and region_name: msg = ('%(interface)s endpoint for %(service_type)s service ' 'named %(service_name)s in %(region_name)s region not ' 'found' % { 'interface': interface, 'service_type': service_type, 'service_name': service_name, 'region_name': region_name }) elif service_name: msg = ('%(interface)s endpoint for %(service_type)s service ' 'named %(service_name)s not found' % { 'interface': interface, 'service_type': service_type, 'service_name': service_name }) elif region_name: msg = ('%(interface)s endpoint for %(service_type)s service ' 'in %(region_name)s region not found' % { 'interface': interface, 'service_type': service_type, 'region_name': region_name }) else: msg = ('%(interface)s endpoint for %(service_type)s service ' 'not found' % { 'interface': interface, 'service_type': service_type }) raise exceptions.EndpointNotFound(msg)
def fake_ks_service_catalog_url_for(*args, **kwargs): raise exceptions.EndpointNotFound("Fake keystone exception")
def request(self, url, method, json=None, original_ip=None, user_agent=None, redirect=None, authenticated=None, endpoint_filter=None, auth=None, requests_auth=None, raise_exc=True, allow_reauth=True, log=True, endpoint_override=None, connect_retries=0, logger=_logger, **kwargs): """Send an HTTP request with the specified characteristics. Wrapper around `requests.Session.request` to handle tasks such as setting headers, JSON encoding/decoding, and error handling. Arguments that are not handled are passed through to the requests library. :param str url: Path or fully qualified URL of HTTP request. If only a path is provided then endpoint_filter must also be provided such that the base URL can be determined. If a fully qualified URL is provided then endpoint_filter will be ignored. :param str method: The http method to use. (e.g. 'GET', 'POST') :param str original_ip: Mark this request as forwarded for this ip. (optional) :param dict headers: Headers to be included in the request. (optional) :param json: Some data to be represented as JSON. (optional) :param str user_agent: A user_agent to use for the request. If present will override one present in headers. (optional) :param int/bool redirect: the maximum number of redirections that can be followed by a request. Either an integer for a specific count or True/False for forever/never. (optional) :param int connect_retries: the maximum number of retries that should be attempted for connection errors. (optional, defaults to 0 - never retry). :param bool authenticated: True if a token should be attached to this request, False if not or None for attach if an auth_plugin is available. (optional, defaults to None) :param dict endpoint_filter: Data to be provided to an auth plugin with which it should be able to determine an endpoint to use for this request. If not provided then URL is expected to be a fully qualified URL. (optional) :param str endpoint_override: The URL to use instead of looking up the endpoint in the auth plugin. This will be ignored if a fully qualified URL is provided but take priority over an endpoint_filter. This string may contain the values ``%(project_id)s`` and ``%(user_id)s`` to have those values replaced by the project_id/user_id of the current authentication. (optional) :param auth: The auth plugin to use when authenticating this request. This will override the plugin that is attached to the session (if any). (optional) :type auth: keystoneauth1.plugin.BaseAuthPlugin :param requests_auth: A requests library auth plugin that cannot be passed via kwarg because the `auth` kwarg collides with our own auth plugins. (optional) :type requests_auth: :py:class:`requests.auth.AuthBase` :param bool raise_exc: If True then raise an appropriate exception for failed HTTP requests. If False then return the request object. (optional, default True) :param bool allow_reauth: Allow fetching a new token and retrying the request on receiving a 401 Unauthorized response. (optional, default True) :param bool log: If True then log the request and response data to the debug log. (optional, default True) :param logger: The logger object to use to log request and responses. If not provided the keystoneauth1.session default logger will be used. :type logger: logging.Logger :param kwargs: any other parameter that can be passed to :meth:`requests.Session.request` (such as `headers`). Except: - `data` will be overwritten by the data in the `json` param. - `allow_redirects` is ignored as redirects are handled by the session. :raises keystoneauth1.exceptions.base.ClientException: For connection failure, or to indicate an error response code. :returns: The response to the request. """ headers = kwargs.setdefault('headers', dict()) if authenticated is None: authenticated = bool(auth or self.auth) if authenticated: auth_headers = self.get_auth_headers(auth) if auth_headers is None: msg = 'No valid authentication is available' raise exceptions.AuthorizationFailure(msg) headers.update(auth_headers) if osprofiler_web: headers.update(osprofiler_web.get_trace_id_headers()) # if we are passed a fully qualified URL and an endpoint_filter we # should ignore the filter. This will make it easier for clients who # want to overrule the default endpoint_filter data added to all client # requests. We check fully qualified here by the presence of a host. if not urllib.parse.urlparse(url).netloc: base_url = None if endpoint_override: base_url = endpoint_override % _StringFormatter(self, auth) elif endpoint_filter: base_url = self.get_endpoint(auth, **endpoint_filter) if not base_url: raise exceptions.EndpointNotFound() url = '%s/%s' % (base_url.rstrip('/'), url.lstrip('/')) if self.cert: kwargs.setdefault('cert', self.cert) if self.timeout is not None: kwargs.setdefault('timeout', self.timeout) if user_agent: headers['User-Agent'] = user_agent elif self.user_agent: user_agent = headers.setdefault('User-Agent', self.user_agent) else: user_agent = headers.setdefault('User-Agent', DEFAULT_USER_AGENT) if self.original_ip: headers.setdefault('Forwarded', 'for=%s;by=%s' % (self.original_ip, user_agent)) if json is not None: headers['Content-Type'] = 'application/json' kwargs['data'] = self._json.encode(json) kwargs.setdefault('verify', self.verify) if requests_auth: kwargs['auth'] = requests_auth if log: self._http_log_request(url, method=method, data=kwargs.get('data'), headers=headers, logger=logger) # Force disable requests redirect handling. We will manage this below. kwargs['allow_redirects'] = False if redirect is None: redirect = self.redirect send = functools.partial(self._send_request, url, method, redirect, log, logger, connect_retries) try: connection_params = self.get_auth_connection_params(auth=auth) except exceptions.MissingAuthPlugin: # NOTE(jamielennox): If we've gotten this far without an auth # plugin then we should be happy with allowing no additional # connection params. This will be the typical case for plugins # anyway. pass else: if connection_params: kwargs.update(connection_params) resp = send(**kwargs) # handle getting a 401 Unauthorized response by invalidating the plugin # and then retrying the request. This is only tried once. if resp.status_code == 401 and authenticated and allow_reauth: if self.invalidate(auth): auth_headers = self.get_auth_headers(auth) if auth_headers is not None: headers.update(auth_headers) resp = send(**kwargs) if raise_exc and resp.status_code >= 400: logger.debug('Request returned failure status: %s', resp.status_code) raise exceptions.from_response(resp, method, url) return resp