def version_data(self, **kwargs): """Get normalized version data. Return version data in a structured way. :returns: A list of version data dictionaries sorted by version number. Each data element in the returned list is a dictionary consisting of at least: :version tuple: The normalized version of the endpoint. :url str: The url for the endpoint. :raw_status str: The status as provided by the server :rtype: list(dict) """ data = self.raw_version_data(**kwargs) versions = [] for v in data: try: version_str = v['id'] except KeyError: _LOGGER.info(_LI('Skipping invalid version data. Missing ID.')) continue try: links = v['links'] except KeyError: _LOGGER.info( _LI('Skipping invalid version data. Missing links')) continue version_number = normalize_version_number(version_str) for link in links: try: rel = link['rel'] url = link['href'] except (KeyError, TypeError): _LOGGER.info( _LI('Skipping invalid version link. ' 'Missing link URL or relationship.')) continue if rel.lower() == 'self': break else: _LOGGER.info( _LI('Skipping invalid version data. ' 'Missing link to endpoint.')) continue versions.append({ 'version': version_number, 'url': url, 'raw_status': v['status'] }) versions.sort(key=lambda v: v['version']) return versions
def version_data(self, **kwargs): """Get normalized version data. Return version data in a structured way. :returns: A list of version data dictionaries sorted by version number. Each data element in the returned list is a dictionary consisting of at least: :version tuple: The normalized version of the endpoint. :url str: The url for the endpoint. :raw_status str: The status as provided by the server :rtype: list(dict) """ if kwargs.pop('unstable', None): kwargs.setdefault('allow_experimental', True) kwargs.setdefault('allow_unknown', True) data = self.raw_version_data(**kwargs) versions = [] for v in data: try: version_str = v['id'] except KeyError: _LOGGER.info(_LI('Skipping invalid version data. Missing ID.')) continue try: links = v['links'] except KeyError: _LOGGER.info( _LI('Skipping invalid version data. Missing links')) continue version_number = normalize_version_number(version_str) for link in links: try: rel = link['rel'] url = link['href'] except (KeyError, TypeError): _LOGGER.info(_LI('Skipping invalid version link. ' 'Missing link URL or relationship.')) continue if rel.lower() == 'self': break else: _LOGGER.info(_LI('Skipping invalid version data. ' 'Missing link to endpoint.')) continue versions.append({'version': version_number, 'url': url, 'raw_status': v['status']}) versions.sort(key=lambda v: v['version']) return versions
def version_data(self, **kwargs): """Get normalized version data. Return version data in a structured way. :returns: A list of version data dictionaries sorted by version number. Each data element in the returned list is a dictionary consisting of at least: :version tuple: The normalized version of the endpoint. :url str: The url for the endpoint. :raw_status str: The status as provided by the server :rtype: list(dict) """ if kwargs.pop("unstable", None): kwargs.setdefault("allow_experimental", True) kwargs.setdefault("allow_unknown", True) data = self.raw_version_data(**kwargs) versions = [] for v in data: try: version_str = v["id"] except KeyError: _LOGGER.info(_LI("Skipping invalid version data. Missing ID.")) continue try: links = v["links"] except KeyError: _LOGGER.info(_LI("Skipping invalid version data. Missing links")) continue version_number = normalize_version_number(version_str) for link in links: try: rel = link["rel"] url = link["href"] except (KeyError, TypeError): _LOGGER.info(_LI("Skipping invalid version link. " "Missing link URL or relationship.")) continue if rel.lower() == "self": break else: _LOGGER.info(_LI("Skipping invalid version data. " "Missing link to endpoint.")) continue versions.append({"version": version_number, "url": url, "raw_status": v["status"]}) versions.sort(key=lambda v: v["version"]) return versions
def _cs_request(self, url, method, management=True, **kwargs): """Makes an authenticated request to keystone endpoint by concatenating self.management_url and url and passing in method and any associated kwargs. """ # NOTE(jamielennox): remember that if you use the legacy client mode # (you create a client without a session) then this HTTPClient object # is the auth plugin you are using. Values in the endpoint_filter may # be ignored and you should look at get_endpoint to figure out what. interface = 'admin' if management else 'public' endpoint_filter = kwargs.setdefault('endpoint_filter', {}) endpoint_filter.setdefault('service_type', 'identity') endpoint_filter.setdefault('interface', interface) if self.version: endpoint_filter.setdefault('version', self.version) if self.region_name: endpoint_filter.setdefault('region_name', self.region_name) kwargs.setdefault('authenticated', None) try: return self.request(url, method, **kwargs) except exceptions.MissingAuthPlugin: _logger.info(_LI('Cannot get authenticated endpoint without an ' 'auth plugin')) raise exceptions.AuthorizationFailure( _('Current authorization does not have a known management ' 'url'))
def _send_request(self, url, method, redirect, log, logger, connect_retries, connect_retry_delay=0.5, **kwargs): # NOTE(jamielennox): We handle redirection manually because the # requests lib follows some browser patterns where it will redirect # POSTs as GETs for certain statuses which is not want we want for an # API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get # NOTE(jamielennox): The interaction between retries and redirects are # handled naively. We will attempt only a maximum number of retries and # redirects rather than per request limits. Otherwise the extreme case # could be redirects * retries requests. This will be sufficient in # most cases and can be fixed properly if there's ever a need. try: try: resp = self.session.request(method, url, **kwargs) except requests.exceptions.SSLError as e: msg = _('SSL exception connecting to %(url)s: ' '%(error)s') % {'url': url, 'error': e} raise exceptions.SSLError(msg) except requests.exceptions.Timeout: msg = _('Request to %s timed out') % url raise exceptions.RequestTimeout(msg) except requests.exceptions.ConnectionError: msg = _('Unable to establish connection to %s') % url raise exceptions.ConnectionRefused(msg) except (exceptions.RequestTimeout, exceptions.ConnectionRefused) as e: if connect_retries <= 0: raise logger.info(_LI('Failure: %(e)s. Retrying in %(delay).1fs.'), {'e': e, 'delay': connect_retry_delay}) time.sleep(connect_retry_delay) return self._send_request( url, method, redirect, log, logger, connect_retries=connect_retries - 1, connect_retry_delay=connect_retry_delay * 2, **kwargs) if log: self._http_log_response(resp, logger) if resp.status_code in self._REDIRECT_STATUSES: # be careful here in python True == 1 and False == 0 if isinstance(redirect, bool): redirect_allowed = redirect else: redirect -= 1 redirect_allowed = redirect >= 0 if not redirect_allowed: return resp try: location = resp.headers['location'] except KeyError: logger.warning(_LW("Failed to redirect request to %s as new " "location was not provided."), resp.url) else: # NOTE(jamielennox): We don't pass through connect_retry_delay. # This request actually worked so we can reset the delay count. new_resp = self._send_request( location, method, redirect, log, logger, connect_retries=connect_retries, **kwargs) if not isinstance(new_resp.history, list): new_resp.history = list(new_resp.history) new_resp.history.insert(0, resp) resp = new_resp return resp
def _send_request(self, url, method, redirect, log, logger, connect_retries, connect_retry_delay=0.5, **kwargs): # NOTE(jamielennox): We handle redirection manually because the # requests lib follows some browser patterns where it will redirect # POSTs as GETs for certain statuses which is not want we want for an # API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get # NOTE(jamielennox): The interaction between retries and redirects are # handled naively. We will attempt only a maximum number of retries and # redirects rather than per request limits. Otherwise the extreme case # could be redirects * retries requests. This will be sufficient in # most cases and can be fixed properly if there's ever a need. try: try: resp = self.session.request(method, url, **kwargs) except requests.exceptions.SSLError as e: msg = _('SSL exception connecting to %(url)s: ' '%(error)s') % { 'url': url, 'error': e } raise exceptions.SSLError(msg) except requests.exceptions.Timeout: msg = _('Request to %s timed out') % url raise exceptions.RequestTimeout(msg) except requests.exceptions.ConnectionError: msg = _('Unable to establish connection to %s') % url raise exceptions.ConnectionRefused(msg) except (exceptions.RequestTimeout, exceptions.ConnectionRefused) as e: if connect_retries <= 0: raise logger.info(_LI('Failure: %(e)s. Retrying in %(delay).1fs.'), { 'e': e, 'delay': connect_retry_delay }) time.sleep(connect_retry_delay) return self._send_request(url, method, redirect, log, logger, connect_retries=connect_retries - 1, connect_retry_delay=connect_retry_delay * 2, **kwargs) if log: self._http_log_response(resp, logger) if resp.status_code in self._REDIRECT_STATUSES: # be careful here in python True == 1 and False == 0 if isinstance(redirect, bool): redirect_allowed = redirect else: redirect -= 1 redirect_allowed = redirect >= 0 if not redirect_allowed: return resp try: location = resp.headers['location'] except KeyError: logger.warning( _LW("Failed to redirect request to %s as new " "location was not provided."), resp.url) else: # NOTE(jamielennox): We don't pass through connect_retry_delay. # This request actually worked so we can reset the delay count. new_resp = self._send_request(location, method, redirect, log, logger, connect_retries=connect_retries, **kwargs) if not isinstance(new_resp.history, list): new_resp.history = list(new_resp.history) new_resp.history.insert(0, resp) resp = new_resp return resp