Пример #1
0
    def _get_endpoint_versions(self, service_type, endpoint):
        """Get available endpoints from the remote service

        Take the endpoint that the Service Catalog gives us, then split off
        anything and just take the root. We need to make a request there
        to get the versions the API exposes.
        """
        parts = parse.urlparse(endpoint)
        if ':' in parts.netloc:
            root_endpoint = "://".join([parts.scheme, parts.netloc])
        else:
            root_endpoint = endpoint

        response = self.get(root_endpoint)

        # Normalize the version response. Identity nests the versions
        # a level deeper than others, inside of a "values" dictionary.
        response_body = response.json()
        if "versions" in response_body:
            versions = response_body["versions"]
            if "values" in versions:
                versions = versions["values"]
            return root_endpoint, versions

        raise exceptions.EndpointNotFound("Unable to parse endpoints for %s" %
                                          service_type)
    def get_url(self, service):
        """Fetch an endpoint from the service catalog.

        Get the first endpoint that matches the service filter.

        :param ServiceFilter service: The filter to identify the desired
                                      service.
        """
        urls = self.get_urls(service)
        if len(urls) < 1:
            message = "Endpoint not found for %s" % six.text_type(service)
            raise exceptions.EndpointNotFound(message)
        return urls[0]
Пример #3
0
    def _get_version_match(self, versions, profile_version, service_type,
                           root_endpoint, requires_project_id):
        """Return the best matching version

        Look through each version trying to find the best match for
        the version specified in this profile.
         * The best match will only ever be found within the same
           major version, meaning a v2 profile will never match if
           only v3 is available on the server.
         * The search for the best match is fuzzy if needed.
           * If the profile specifies v2 and the server has
             v2.0, v2.1, and v2.2, the match will be v2.2.
           * When an exact major/minor is specified, e.g., v2.0,
             it will only match v2.0.
        """

        match_version = None

        for version in versions:
            api_version = self._parse_version(version["id"])
            if profile_version.major != api_version.major:
                continue

            if profile_version.minor <= api_version.minor:
                for link in version["links"]:
                    if link["rel"] == "self":
                        resp_link = link['href']
                        match_version = parse.urlsplit(resp_link).path

            # Only break out of the loop on an exact match,
            # otherwise keep trying.
            if profile_version.minor == api_version.minor:
                break

        if match_version is None:
            raise exceptions.EndpointNotFound(
                "Unable to determine endpoint for %s" % service_type)

        # Make sure "root_endpoint" has no overlap with match_version
        root_parts = parse.urlsplit(root_endpoint)
        match_version = match_version.replace(root_parts.path, "", 1)
        match = utils.urljoin(root_endpoint, match_version)

        # For services that require the project id in the request URI,
        # add them in here.
        if requires_project_id:
            match = utils.urljoin(match, self.get_project_id())

        return match
Пример #4
0
    def _get_version_match(self, versions, profile_version, service_type,
                           root_endpoint, requires_project_id):
        """Return the best matching version

        Look through each version trying to find the best match for
        the version specified in this profile.
         * The best match will only ever be found within the same
           major version, meaning a v2 profile will never match if
           only v3 is available on the server.
         * The search for the best match is fuzzy if needed.
           * If the profile specifies v2 and the server has
             v2.0, v2.1, and v2.2, the match will be v2.2.
           * When an exact major/minor is specified, e.g., v2.0,
             it will only match v2.0.
        """
        match = None
        for version in versions:
            api_version = self._parse_version(version["id"])
            if profile_version.major != api_version.major:
                continue

            if profile_version.minor <= api_version.minor:
                for link in version["links"]:
                    if link["rel"] == "self":
                        match = link["href"]

            # Only break out of the loop on an exact match,
            # otherwise keep trying.
            if profile_version.minor == api_version.minor:
                break

        if match is None:
            raise exceptions.EndpointNotFound(
                "Unable to determine endpoint for %s" % service_type)

        # Some services return only the path fragment of a URI.
        # If we split and see that we're not given the scheme and netloc,
        # construct the match with the root from the service catalog.
        match_split = parse.urlsplit(match)
        if not all([match_split.scheme, match_split.netloc]):
            match = root_endpoint + match

        # For services that require the project id in the request URI,
        # add them in here.
        if requires_project_id:
            match = utils.urljoin(match, self.get_project_id())

        return match
Пример #5
0
    def _get_endpoint_versions(self, service_type, endpoint):
        """Get available endpoints from the remote service

        Take the endpoint that the Service Catalog gives us as a base
        and then work from there. In most cases, the path-less 'root'
        of the URI is the base of the service which contains the versions.
        In other cases, we need to discover it by trying the paths that
        eminate from that root. Generally this is achieved in one roundtrip
        request/response, but depending on how the service is installed,
        it may require multiple requests.
        """
        parts = parse.urlparse(endpoint)

        just_root = "://".join([parts.scheme, parts.netloc])

        # If we need to try using a portion of the parts,
        # the project id won't be one worth asking for so remove it.
        # However, we do need to know that the project id was
        # previously there, so keep it.
        project_id = self.get_project_id()
        # Domain scope token don't include project id
        project_id_location = parts.path.find(project_id) if project_id else -1
        if project_id_location > -1:
            usable_path = parts.path[slice(0, project_id_location)]
            needs_project_id = True
        else:
            usable_path = parts.path
            needs_project_id = False

        # Generate a series of paths that might contain our version
        # information. This will build successively longer paths from
        # the split, so /nova/v2 would return "", "/nova",
        # "/nova/v2" out of it. Based on what we've normally seen,
        # the match will be found early on within those.
        paths = accumulate(usable_path.split("/"),
                           func=lambda *fragments: "/".join(fragments))

        result = None

        # If we have paths, try them from the root outwards.
        # NOTE: Both the body of the for loop and the else clause
        # cover the request for `just_root`. The else clause is explicit
        # in only testing it because there are no path parts. In the for
        # loop, it gets requested in the first iteration.
        for path in paths:
            response = self._parse_versions_response(just_root + path)
            if response is not None:
                result = response
                break
        else:
            # If we didn't have paths, root is all we can do anyway.
            response = self._parse_versions_response(just_root)
            if response is not None:
                result = response

        if result is not None:
            if needs_project_id:
                result.needs_project_id = True
                result.project_id = project_id

            return result

        raise exceptions.EndpointNotFound(
            "Unable to parse endpoints for %s" % service_type)
Пример #6
0
    def request(self,
                url,
                method,
                json=None,
                original_ip=None,
                user_agent=None,
                redirect=None,
                endpoint_filter=None,
                raise_exc=True,
                log=True,
                endpoint_override=None,
                connect_retries=0,
                allow=None,
                client_name=None,
                client_version=None,
                **kwargs):
        headers = kwargs.setdefault('headers', dict())

        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')
        # surpport sub-project id for some service the endpoint contain 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,
                                             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