def get_endpoint_data(self, session, endpoint_override=None, discover_versions=True, **kwargs): """Return a valid endpoint data for a the service. :param session: A session object that can be used for communication. :type session: keystoneauth1.session.Session :param str endpoint_override: URL to use for version discovery. :param bool discover_versions: Whether to get version metadata from the version discovery document even if it major api version info can be inferred from the url. (optional, defaults to True) :param kwargs: Ignored. :raises keystoneauth1.exceptions.http.HttpError: An error from an invalid HTTP response. :return: Valid EndpointData or None if not available. :rtype: `keystoneauth1.discover.EndpointData` or None """ if not endpoint_override: return None endpoint_data = discover.EndpointData(catalog_url=endpoint_override) if endpoint_data.api_version and not discover_versions: return endpoint_data return endpoint_data.get_versioned_data( session, cache=self._discovery_cache, discover_versions=discover_versions)
def endpoint_data_for(self, **kwargs): kwargs.setdefault('interface', 'public') kwargs.setdefault('service_type', None) if kwargs['service_type'] == 'object-store': return discover.EndpointData( service_type='object-store', service_name='swift', interface=kwargs['interface'], region_name='default', catalog_url=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 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')])
def test_endpoint_data_str(self): """Validate EndpointData.__str__.""" # Populate a few fields to make sure they come through. epd = discover.EndpointData(catalog_url='abc', service_type='123', api_version=(2, 3)) exp = ('EndpointData{api_version=(2, 3), catalog_url=abc,' ' endpoint_id=None, interface=None, major_version=None,' ' max_microversion=None, min_microversion=None,' ' next_min_version=None, not_before=None, raw_endpoint=None,' ' region_name=None, service_id=None, service_name=None,' ' service_type=123, service_url=None, url=abc}') # Works with str() self.assertEqual(exp, str(epd)) # Works with implicit stringification self.assertEqual(exp, "%s" % epd)
def get_endpoints_data(self, service_type=None, interface=None, region_name=None, service_name=None, service_id=None, endpoint_id=None): """Fetch and filter endpoint data for the specified service(s). Returns endpoints for the specified service (or all) containing the specified type (or all) and region (or all) and service name. If there is no name in the service catalog the service_name check will be skipped. This allows compatibility with services that existed before the name was available in the catalog. 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. :returns: a list of matching EndpointData objects :rtype: list(`keystoneauth1.discover.EndpointData`) :returns: a dict, keyed by service_type, of lists of EndpointData """ interfaces = self._get_interface_list(interface) matching_endpoints = {} for service in self.normalize_catalog(): if service_type and service_type != service['type']: continue if (service_name and service['name'] and service_name != service['name']): continue if (service_id and service['id'] and service_id != service['id']): continue matching_endpoints.setdefault(service['type'], []) for endpoint in service.get('endpoints', []): if interfaces and endpoint['interface'] not in interfaces: continue if region_name and region_name != endpoint['region_name']: continue if endpoint_id and endpoint_id != endpoint['id']: continue if not endpoint['url']: continue matching_endpoints[service['type']].append( discover.EndpointData( catalog_url=endpoint['url'], service_type=service['type'], service_name=service['name'], service_id=service['id'], interface=endpoint['interface'], region_name=endpoint['region_name'], endpoint_id=endpoint['id'], raw_endpoint=endpoint['raw_endpoint'])) if not interfaces: return matching_endpoints ret = {} for service_type, endpoints in matching_endpoints.items(): if not endpoints: ret[service_type] = [] continue matches_by_interface = {} for endpoint in endpoints: matches_by_interface.setdefault(endpoint.interface, []) matches_by_interface[endpoint.interface].append(endpoint) best_interface = [ i for i in interfaces if i in matches_by_interface.keys() ][0] ret[service_type] = matches_by_interface[best_interface] return ret
def get_endpoint_data(self, session, service_type=None, interface=None, region_name=None, service_name=None, allow=None, allow_version_hack=True, discover_versions=True, skip_discovery=False, min_version=None, max_version=None, endpoint_override=None, **kwargs): """Return a valid endpoint data for a service. If a valid token is not present then a new one will be fetched using the session and kwargs. version, min_version and max_version can all be given either as a string or a tuple. Valid interface types: `public` or `publicURL`, `internal` or `internalURL`, `admin` or 'adminURL` :param session: A session object that can be used for communication. :type session: keystoneauth1.session.Session :param string service_type: The type of service to lookup the endpoint for. This plugin will return None (failure) if service_type is not provided. :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. Can also be `keystoneauth1.plugin.AUTH_INTERFACE` to indicate that the auth_url should be used instead of the value in the catalog. (optional, defaults to public) :param string region_name: The region the endpoint should exist in. (optional) :param string service_name: The name of the service in the catalog. (optional) :param dict allow: Extra filters to pass when discovering API versions. (optional) :param bool allow_version_hack: Allow keystoneauth to hack up catalog URLS to support older schemes. (optional, default True) :param bool discover_versions: Whether to get version metadata from the version discovery document even if it's not neccessary to fulfill the major version request. (optional, defaults to True) :param bool skip_discovery: Whether to skip version discovery even if a version has been given. This is useful if endpoint_override or similar has been given and grabbing additional information about the endpoint is not useful. :param min_version: The minimum version that is acceptable. Mutually exclusive with version. If min_version is given with no max_version it is as if max version is 'latest'. (optional) :param max_version: The maximum version that is acceptable. Mutually exclusive with version. If min_version is given with no max_version it is as if max version is 'latest'. (optional) :param str endpoint_override: URL to use instead of looking in the catalog. Catalog lookup will be skipped, but version discovery will be run. Sets allow_version_hack to False (optional) :param kwargs: Ignored. :raises keystoneauth1.exceptions.http.HttpError: An error from an invalid HTTP response. :return: Valid EndpointData or None if not available. :rtype: `keystoneauth1.discover.EndpointData` or None """ allow = allow or {} min_version, max_version = discover._normalize_version_args( None, min_version, max_version, service_type=service_type) # NOTE(jamielennox): if you specifically ask for requests to be sent to # the auth url then we can ignore many of the checks. Typically if you # are asking for the auth endpoint it means that there is no catalog to # query however we still need to support asking for a specific version # of the auth_url for generic plugins. if interface is plugin.AUTH_INTERFACE: endpoint_data = discover.EndpointData( service_url=self.auth_url, service_type=service_type or 'identity') project_id = None elif endpoint_override: # TODO(mordred) Make a code path that will look for a # matching entry in the catalog if the catalog # exists and fill in the interface, region_name, etc. # For now, just use any information the use has # provided. endpoint_data = discover.EndpointData( service_url=endpoint_override, catalog_url=endpoint_override, interface=interface, region_name=region_name, service_name=service_name) # Setting an endpoint_override then calling get_endpoint_data means # you absolutely want the discovery info for the URL in question. # There are no code flows where this will happen for any other # reasons. allow_version_hack = False project_id = self.get_project_id(session) else: if not service_type: LOG.warning('Plugin cannot return an endpoint without ' 'knowing the service type that is required. Add ' 'service_type to endpoint filtering data.') return None # It's possible for things higher in the stack, because of # defaults, to explicitly pass None. if not interface: interface = 'public' service_catalog = self.get_access(session).service_catalog project_id = self.get_project_id(session) # NOTE(mordred): service_catalog.url_data_for raises if it can't # find a match, so this will always be a valid object. endpoint_data = service_catalog.endpoint_data_for( service_type=service_type, interface=interface, region_name=region_name, service_name=service_name) if not endpoint_data: return None if skip_discovery: return endpoint_data try: return endpoint_data.get_versioned_data( session, project_id=project_id, min_version=min_version, max_version=max_version, cache=self._discovery_cache, discover_versions=discover_versions, allow_version_hack=allow_version_hack, allow=allow) except (exceptions.DiscoveryFailure, exceptions.HttpError, exceptions.ConnectionError): # If a version was requested, we didn't find it, return # None. if max_version or min_version: return None # If one wasn't, then the endpoint_data we already have # should be fine return endpoint_data
def test_project_id_int_fallback(self): bad_url = "https://compute.example.com/v2/123456" epd = discover.EndpointData(catalog_url=bad_url) self.assertEqual((2, 0), epd.api_version)