Example #1
0
    def _do_create_plugin(self, session):
        plugin = None

        try:
            disc = self.get_discovery(session,
                                      self.auth_url,
                                      authenticated=False)
        except (exceptions.DiscoveryFailure,
                exceptions.HttpError,
                exceptions.ConnectionError):
            LOG.warning('Discovering versions from the identity service '
                        'failed when creating the password plugin. '
                        'Attempting to determine version from URL.')

            url_parts = urlparse.urlparse(self.auth_url)
            path = url_parts.path.lower()

            if path.startswith('/v2.0'):
                if self._has_domain_scope:
                    raise exceptions.DiscoveryFailure(
                        'Cannot use v2 authentication with domain scope')
                plugin = self.create_plugin(session, (2, 0), self.auth_url)
            elif path.startswith('/v3'):
                plugin = self.create_plugin(session, (3, 0), self.auth_url)

        else:
            disc_data = disc.version_data()

            v2_with_domain_scope = False
            for data in disc_data:
                version = data['version']

                if (discover.version_match((2,), version) and
                        self._has_domain_scope):
                    # NOTE(jamielennox): if there are domain parameters there
                    # is no point even trying against v2 APIs.
                    v2_with_domain_scope = True
                    continue

                plugin = self.create_plugin(session,
                                            version,
                                            data['url'],
                                            raw_status=data['raw_status'])

                if plugin:
                    break
            if not plugin and v2_with_domain_scope:
                raise exceptions.DiscoveryFailure(
                    'Cannot use v2 authentication with domain scope')

        if plugin:
            return plugin

        # so there were no URLs that i could use for auth of any version.
        raise exceptions.DiscoveryFailure('Could not determine a suitable URL '
                                          'for the plugin')
Example #2
0
def get_version_data(session, url, authenticated=None):
    """Retrieve raw version data from a url."""
    headers = {'Accept': 'application/json'}

    resp = session.get(url, headers=headers, authenticated=authenticated)

    try:
        body_resp = resp.json()
    except ValueError:
        pass
    else:
        # In the event of querying a root URL we will get back a list of
        # available versions.
        try:
            return body_resp['versions']['values']
        except (KeyError, TypeError):
            pass

        # Most servers don't have a 'values' element so accept a simple
        # versions dict if available.
        try:
            return body_resp['versions']
        except KeyError:
            pass

        # Otherwise if we query an endpoint like /v2.0 then we will get back
        # just the one available version.
        try:
            return [body_resp['version']]
        except KeyError:
            pass

    err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text
    raise exceptions.DiscoveryFailure('Invalid Response - Bad version data '
                                      'returned: %s' % err_text)
    def _run_discovery(self, session, cache, min_version, max_version,
                       project_id, allow_version_hack, discover_versions):
        tried = set()

        for vers_url in self._get_discovery_url_choices(
                project_id=project_id,
                allow_version_hack=allow_version_hack,
                min_version=min_version,
                max_version=max_version):

            if self._catalog_matches_exactly and not discover_versions:
                # The version we started with is correct, and we don't want
                # new data
                return

            if vers_url in tried:
                continue
            tried.add(vers_url)

            try:
                self._disc = get_discovery(
                    session, vers_url,
                    cache=cache,
                    authenticated=False)
                break
            except (exceptions.DiscoveryFailure,
                    exceptions.HttpError,
                    exceptions.ConnectionError):
                continue
        if not self._disc:
            # We couldn't find a version discovery document anywhere.
            if self._catalog_matches_version:
                # But - the version in the catalog is fine.
                self.service_url = self.catalog_url
                return

            # NOTE(jamielennox): The logic here is required for backwards
            # compatibility. By itself it is not ideal.
            if allow_version_hack:
                # NOTE(jamielennox): If we can't contact the server we
                # fall back to just returning the URL from the catalog.  This
                # is backwards compatible behaviour and used when there is no
                # other choice. Realistically if you have provided a version
                # you should be able to rely on that version being returned or
                # the request failing.
                _LOGGER.warning(
                    'Failed to contact the endpoint at %s for '
                    'discovery. Fallback to using that endpoint as '
                    'the base url.', self.url)
                return

            else:
                # NOTE(jamielennox): If you've said no to allow_version_hack
                # and we can't determine the actual URL this is a failure
                # because we are specifying that the deployment must be up to
                # date enough to properly specify a version and keystoneauth
                # can't deliver.
                raise exceptions.DiscoveryFailure(
                    "Version requested but version discovery document was not"
                    " found and allow_version_hack was False")
Example #4
0
    def test_discovery_failure(self, get):
        """Test failure when discovery for placement URL failed.

        Replicate in devstack: start devstack with placement
        engine, create valid placement service user and specify it
        in auth section of [placement] in nova.conf. Stop keystone service.
        """
        get.side_effect = ks_exc.DiscoveryFailure()
        res = self.cmd._check_placement()
        self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code)
        self.assertIn('Discovery for placement API URI failed.', res.details)
Example #5
0
 def test_run_discovery_cache(self, mock_url_choices, mock_get_disc):
     # get_discovery raises so we keep looping
     mock_get_disc.side_effect = exceptions.DiscoveryFailure()
     # Duplicate 'url1' in here to validate the cache behavior
     mock_url_choices.return_value = ('url1', 'url2', 'url1', 'url3')
     epd = discover.EndpointData()
     epd._run_discovery(
         session='sess', cache='cache', min_version='min',
         max_version='max', project_id='projid',
         allow_version_hack='allow_hack', discover_versions='disc_vers')
     # Only one call with 'url1'
     self.assertEqual(3, mock_get_disc.call_count)
     mock_get_disc.assert_has_calls(
         [mock.call('sess', url, cache='cache', authenticated=False)
          for url in ('url1', 'url2', 'url3')])
Example #6
0
    def test_init(self):
        from keystoneauth1 import exceptions as ks_exc

        actual_exc = ks_exc.ConnectionError("Something")
        exc = osclients.AuthenticationFailed(error=actual_exc,
                                             url="https://example.com",
                                             username="******",
                                             project="project")
        # only original exc should be used
        self.assertEqual("Something", exc.format_message())

        actual_exc = Exception("Something")
        exc = osclients.AuthenticationFailed(error=actual_exc,
                                             url="https://example.com",
                                             username="******",
                                             project="project")
        # additional info should be added
        self.assertEqual(
            "Failed to authenticate to https://example.com for "
            "user 'user' in project 'project': "
            "[Exception] Something", exc.format_message())

        # check cutting message
        actual_exc = ks_exc.DiscoveryFailure(
            "Could not find versioned identity endpoints when attempting to "
            "authenticate. Please check that your auth_url is correct. "
            "Unable to establish connection to https://example.com: "
            "HTTPConnectionPool(host='example.com', port=80): Max retries "
            "exceeded with url: / (Caused by NewConnectionError('"
            "<urllib3.connection.HTTPConnection object at 0x7f32ab9809d0>: "
            "Failed to establish a new connection: [Errno -2] Name or service"
            " not known',))")
        exc = osclients.AuthenticationFailed(error=actual_exc,
                                             url="https://example.com",
                                             username="******",
                                             project="project")
        # original message should be simplified
        self.assertEqual(
            "Could not find versioned identity endpoints when attempting to "
            "authenticate. Please check that your auth_url is correct. "
            "Unable to establish connection to https://example.com",
            exc.format_message())
Example #7
0
    def create_plugin(self, session, version, url, raw_status=None):
        if discover.version_match((2, ), version):
            if self._user_domain_id or self._user_domain_name:
                raise exceptions.DiscoveryFailure(
                    'Cannot use v2 authentication with domain scope')

            return v2.Password(auth_url=url,
                               user_id=self._user_id,
                               username=self._username,
                               password=self._password,
                               **self._v2_params)

        elif discover.version_match((3, ), version):
            return v3.Password(auth_url=url,
                               user_id=self._user_id,
                               username=self._username,
                               user_domain_id=self._user_domain_id,
                               user_domain_name=self._user_domain_name,
                               password=self._password,
                               **self._v3_params)
Example #8
0
    def _do_create_plugin(self, session):
        plugin = None

        try:
            disc = self.get_discovery(session,
                                      self.auth_url,
                                      authenticated=False)
        except (exceptions.DiscoveryFailure,
                exceptions.HttpError,
                exceptions.ConnectionError):
            LOG.warning('Discovering versions from the identity service '
                        'failed when creating the password plugin. '
                        'Attempting to determine version from URL.')

            url_parts = urlparse.urlparse(self.auth_url)
            path = url_parts.path.lower()

            if path.startswith('/v2.0'):
                if self._has_domain_scope:
                    raise exceptions.DiscoveryFailure(
                        'Cannot use v2 authentication with domain scope')
                plugin = self.create_plugin(session, (2, 0), self.auth_url)
            elif path.startswith('/v3'):
                plugin = self.create_plugin(session, (3, 0), self.auth_url)

        else:
            # NOTE(jamielennox): version_data is always in oldest to newest
            # order. This is fine normally because we explicitly skip v2 below
            # if there is domain data present. With default_domain params
            # though we want a v3 plugin if available and fall back to v2 so we
            # have to process in reverse order.  FIXME(jamielennox): if we ever
            # go for another version we should reverse this logic as we always
            # want to favour the newest available version.
            reverse = self._default_domain_id or self._default_domain_name
            disc_data = disc.version_data(reverse=bool(reverse))

            v2_with_domain_scope = False
            for data in disc_data:
                version = data['version']

                if (discover.version_match((2,), version) and
                        self._has_domain_scope):
                    # NOTE(jamielennox): if there are domain parameters there
                    # is no point even trying against v2 APIs.
                    v2_with_domain_scope = True
                    continue

                plugin = self.create_plugin(session,
                                            version,
                                            data['url'],
                                            raw_status=data['raw_status'])

                if plugin:
                    break
            if not plugin and v2_with_domain_scope:
                raise exceptions.DiscoveryFailure(
                    'Cannot use v2 authentication with domain scope')

        if plugin:
            return plugin

        # so there were no URLs that i could use for auth of any version.
        raise exceptions.DiscoveryFailure('Could not determine a suitable URL '
                                          'for the plugin')
    def _set_version_info(self, session, allow=None, cache=None,
                          allow_version_hack=True, project_id=None,
                          discover_versions=False,
                          min_version=None, max_version=None):
        match_url = None

        no_version = not max_version and not min_version
        if no_version and not discover_versions:
            # NOTE(jamielennox): This may not be the best thing to default to
            # but is here for backwards compatibility. It may be worth
            # defaulting to the most recent version.
            return
        elif no_version and discover_versions:
            # We want to run discovery, but we don't want to find different
            # endpoints than what's in the catalog
            allow_version_hack = False
            match_url = self.url

        if project_id:
            self.project_id = project_id
        discovered_data = None
        # Maybe we've run discovery in the past and have a document that can
        # satisfy the request without further work
        if self._disc:
            discovered_data = self._disc.versioned_data_for(
                min_version=min_version, max_version=max_version,
                url=match_url, **allow)
        if not discovered_data:
            self._run_discovery(
                session=session, cache=cache,
                min_version=min_version, max_version=max_version,
                project_id=project_id, allow_version_hack=allow_version_hack,
                discover_versions=discover_versions)
            if not self._disc:
                return
            discovered_data = self._disc.versioned_data_for(
                min_version=min_version, max_version=max_version,
                url=match_url, **allow)

        if not discovered_data:
            if min_version and not max_version:
                raise exceptions.DiscoveryFailure(
                    "Minimum version {min_version} was not found".format(
                        min_version=version_to_string(min_version)))
            elif max_version and not min_version:
                raise exceptions.DiscoveryFailure(
                    "Maximum version {max_version} was not found".format(
                        max_version=version_to_string(max_version)))
            elif min_version and max_version:
                raise exceptions.DiscoveryFailure(
                    "No version found between {min_version}"
                    " and {max_version}".format(
                        min_version=version_to_string(min_version),
                        max_version=version_to_string(max_version)))

        self.min_microversion = discovered_data['min_microversion']
        self.max_microversion = discovered_data['max_microversion']
        self.next_min_version = discovered_data['next_min_version']
        self.not_before = discovered_data['not_before']
        self.api_version = discovered_data['version']

        # TODO(mordred): these next two things should be done by Discover
        # in versioned_data_for.
        discovered_url = discovered_data['url']

        # NOTE(jamielennox): urljoin allows the url to be relative or even
        # protocol-less. The additional trailing '/' make urljoin respect
        # the current path as canonical even if the url doesn't include it.
        # for example a "v2" path from http://host/admin should resolve as
        # http://host/admin/v2 where it would otherwise be host/v2.
        # This has no effect on absolute urls returned from url_for.
        url = urllib.parse.urljoin(self._disc._url.rstrip('/') + '/',
                                   discovered_url)

        # If we had to pop a project_id from the catalog_url, put it back on
        if self._saved_project_id:
            url = urllib.parse.urljoin(url.rstrip('/') + '/',
                                       self._saved_project_id)
        self.service_url = url
def get_version_data(session, url, authenticated=None):
    """Retrieve raw version data from a url.

    The return is a list of dicts of the form::

      [{
          'status': 'STABLE',
          'id': 'v2.3',
          'links': [
              {
                  'href': 'http://network.example.com/v2.3',
                  'rel': 'self',
              },
              {
                  'href': 'http://network.example.com/',
                  'rel': 'collection',
              },
          ],
          'min_version': '2.0',
          'max_version': '2.7',
       },
       ...,
      ]

    Note:
    The maximum microversion may be specified by `max_version` or `version`,
    the former superseding the latter.
    All `*version` keys are optional.
    Other keys and 'links' entries are permitted, but ignored.

    :param session: A Session object that can be used for communication.
    :type session: keystoneauth1.session.Session
    :param string url: Endpoint or discovery URL from which to retrieve data.
    :param bool authenticated: Include a token in the discovery call.
                               (optional) Defaults to None.
    :return: A list of dicts containing version information.
    :rtype: list(dict)
    """
    headers = {'Accept': 'application/json'}

    resp = session.get(url, headers=headers, authenticated=authenticated)

    try:
        body_resp = resp.json()
    except ValueError:
        pass
    else:
        # In the event of querying a root URL we will get back a list of
        # available versions.
        try:
            return body_resp['versions']['values']
        except (KeyError, TypeError):
            pass

        # Most servers don't have a 'values' element so accept a simple
        # versions dict if available.
        try:
            return body_resp['versions']
        except KeyError:
            pass

        # Otherwise if we query an endpoint like /v2.0 then we will get back
        # just the one available version.
        try:
            return [body_resp['version']]
        except KeyError:
            pass

    err_text = resp.text[:50] + '...' if len(resp.text) > 50 else resp.text
    raise exceptions.DiscoveryFailure('Invalid Response - Bad version data '
                                      'returned: %s' % err_text)
Example #11
0
 def get_discovery(self, session, url, authenticated=None):
     raise exceptions.DiscoveryFailure()