openstacksdk/openstack/auth/service_catalog.py

166 lines
5.9 KiB
Python

# Copyright 2011 OpenStack Foundation
# Copyright 2011, Piston Cloud Computing, Inc.
# Copyright 2011 Nebula, Inc.
#
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import copy
import re
import six
from six.moves.urllib import parse
from openstack import exceptions
class ServiceCatalog(object):
"""Helper methods for dealing with a Keystone Service Catalog."""
def __init__(self, catalog):
if catalog is None:
self.catalog = []
raise exceptions.EmptyCatalog('The service catalog is missing')
self.catalog = copy.deepcopy(catalog)
self._normalize()
self._parse_endpoints()
def _normalize(self):
return
def _parse_endpoints(self):
pattern = re.compile('/v\d[\d.]*')
for service in self.catalog:
for endpoint in service.get('endpoints', []):
url = endpoint.get('url', '')
if not url:
continue
split = parse.urlsplit(url)
split = list(split)
path = split[2]
vstr = pattern.search(path)
if not vstr:
endpoint['url'] = (endpoint['url'].rstrip('/') +
'/%(version)s')
continue
start, end = vstr.span()
endpoint['version'] = path[start + 1:end]
path = path[:start] + '/%(version)s' + path[end:]
path = path.rstrip('/')
split[2] = path
endpoint['url'] = parse.urlunsplit(split)
def _get_endpoints(self, filtration):
"""Fetch and filter urls and version tuples for the specified service.
Returns a tuple containting the url and version for the specified
service (or all) containing the specified type, name, region and
interface.
"""
eps = []
for service in self.catalog:
if not filtration.match_service_type(service.get('type')):
continue
if not filtration.match_service_name(service.get('name')):
continue
for endpoint in service.get('endpoints', []):
if not filtration.match_region(endpoint.get('region', None)):
continue
if not filtration.match_interface(endpoint.get('interface')):
continue
url = endpoint.get('url', None)
if not url:
continue
version = endpoint.get('version', None)
eps.append((url, version))
return eps
def get_urls(self, filtration):
"""Fetch the urls based on the given service filter.
Returns a list of urls based on the service filter. If not endpoints
are found that match the service filter, an empty list is returned.
The filter may specify type, name, region, version and interface.
"""
urls = []
for url, version in self._get_endpoints(filtration):
version = filtration.get_version_path(version)
url = url % {'version': version}
urls.append(url)
return urls
def get_versions(self, filtration):
"""Fetch the versions based on the given service filter.
Returns a list of versions based on the service filter. If there is
no endpoint matching the filter, None will be returned. An empty
list of versions means the service is supported, but no version is
specified in the service catalog. The filter may specify type, name,
region, version and interface.
"""
vers = None
for url, version in self._get_endpoints(filtration):
vers = vers or []
if not version:
continue
if filtration.version and version != filtration.version:
continue
vers.append(version)
return vers
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]
class ServiceCatalogV2(ServiceCatalog):
"""The V2 service catalog from Keystone."""
def _extract_details(self, endpoint, interface):
value = {
'interface': interface,
'url': endpoint['%sURL' % interface]
}
region = endpoint.get('region', None)
if region:
value['region'] = region
return value
def _normalize(self):
"""Handle differences in the way v2 and v3 catalogs specify endpoints.
Normallize the v2 service catalog to the endpoint types used in v3.
"""
for service in self.catalog:
eps = []
for endpoint in service['endpoints']:
if 'publicURL' in endpoint:
eps += [self._extract_details(endpoint, "public")]
if 'internalURL' in endpoint:
eps += [self._extract_details(endpoint, "internal")]
if 'adminURL' in endpoint:
eps += [self._extract_details(endpoint, "admin")]
service['endpoints'] = eps