Exemple #1
0
    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
Exemple #4
0
    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
Exemple #6
0
    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