aboutsummaryrefslogtreecommitdiffstats
path: root/keystone-moon/keystone/catalog
diff options
context:
space:
mode:
Diffstat (limited to 'keystone-moon/keystone/catalog')
-rw-r--r--keystone-moon/keystone/catalog/__init__.py1
-rw-r--r--keystone-moon/keystone/catalog/backends/sql.py429
-rw-r--r--keystone-moon/keystone/catalog/backends/templated.py160
-rw-r--r--keystone-moon/keystone/catalog/controllers.py234
-rw-r--r--keystone-moon/keystone/catalog/core.py388
-rw-r--r--keystone-moon/keystone/catalog/routers.py142
-rw-r--r--keystone-moon/keystone/catalog/schema.py21
7 files changed, 1227 insertions, 148 deletions
diff --git a/keystone-moon/keystone/catalog/__init__.py b/keystone-moon/keystone/catalog/__init__.py
index 8d4d1567..29f297d6 100644
--- a/keystone-moon/keystone/catalog/__init__.py
+++ b/keystone-moon/keystone/catalog/__init__.py
@@ -14,4 +14,3 @@
from keystone.catalog import controllers # noqa
from keystone.catalog.core import * # noqa
-from keystone.catalog import routers # noqa
diff --git a/keystone-moon/keystone/catalog/backends/sql.py b/keystone-moon/keystone/catalog/backends/sql.py
index fe69db58..bd92f107 100644
--- a/keystone-moon/keystone/catalog/backends/sql.py
+++ b/keystone-moon/keystone/catalog/backends/sql.py
@@ -21,8 +21,10 @@ from sqlalchemy.sql import true
from keystone import catalog
from keystone.catalog import core
+from keystone.common import driver_hints
from keystone.common import sql
from keystone import exception
+from keystone.i18n import _
CONF = cfg.CONF
@@ -43,13 +45,6 @@ class Region(sql.ModelBase, sql.DictBase):
# "left" and "right" and provide support for a nested set
# model.
parent_region_id = sql.Column(sql.String(255), nullable=True)
-
- # TODO(jaypipes): I think it's absolutely stupid that every single model
- # is required to have an "extra" column because of the
- # DictBase in the keystone.common.sql.core module. Forcing
- # tables to have pointless columns in the database is just
- # bad. Remove all of this extra JSON blob stuff.
- # See: https://bugs.launchpad.net/keystone/+bug/1265071
extra = sql.Column(sql.JsonBlob())
endpoints = sqlalchemy.orm.relationship("Endpoint", backref="region")
@@ -89,10 +84,10 @@ class Endpoint(sql.ModelBase, sql.DictBase):
class Catalog(catalog.CatalogDriverV8):
# Regions
def list_regions(self, hints):
- session = sql.get_session()
- regions = session.query(Region)
- regions = sql.filter_limit_query(Region, regions, hints)
- return [s.to_dict() for s in list(regions)]
+ with sql.session_for_read() as session:
+ regions = session.query(Region)
+ regions = sql.filter_limit_query(Region, regions, hints)
+ return [s.to_dict() for s in list(regions)]
def _get_region(self, session, region_id):
ref = session.query(Region).get(region_id)
@@ -141,12 +136,11 @@ class Catalog(catalog.CatalogDriverV8):
return False
def get_region(self, region_id):
- session = sql.get_session()
- return self._get_region(session, region_id).to_dict()
+ with sql.session_for_read() as session:
+ return self._get_region(session, region_id).to_dict()
def delete_region(self, region_id):
- session = sql.get_session()
- with session.begin():
+ with sql.session_for_write() as session:
ref = self._get_region(session, region_id)
if self._has_endpoints(session, ref, ref):
raise exception.RegionDeletionError(region_id=region_id)
@@ -155,16 +149,14 @@ class Catalog(catalog.CatalogDriverV8):
@sql.handle_conflicts(conflict_type='region')
def create_region(self, region_ref):
- session = sql.get_session()
- with session.begin():
+ with sql.session_for_write() as session:
self._check_parent_region(session, region_ref)
region = Region.from_dict(region_ref)
session.add(region)
- return region.to_dict()
+ return region.to_dict()
def update_region(self, region_id, region_ref):
- session = sql.get_session()
- with session.begin():
+ with sql.session_for_write() as session:
self._check_parent_region(session, region_ref)
ref = self._get_region(session, region_id)
old_dict = ref.to_dict()
@@ -174,15 +166,15 @@ class Catalog(catalog.CatalogDriverV8):
for attr in Region.attributes:
if attr != 'id':
setattr(ref, attr, getattr(new_region, attr))
- return ref.to_dict()
+ return ref.to_dict()
# Services
- @sql.truncated
+ @driver_hints.truncated
def list_services(self, hints):
- session = sql.get_session()
- services = session.query(Service)
- services = sql.filter_limit_query(Service, services, hints)
- return [s.to_dict() for s in list(services)]
+ with sql.session_for_read() as session:
+ services = session.query(Service)
+ services = sql.filter_limit_query(Service, services, hints)
+ return [s.to_dict() for s in list(services)]
def _get_service(self, session, service_id):
ref = session.query(Service).get(service_id)
@@ -191,26 +183,23 @@ class Catalog(catalog.CatalogDriverV8):
return ref
def get_service(self, service_id):
- session = sql.get_session()
- return self._get_service(session, service_id).to_dict()
+ with sql.session_for_read() as session:
+ return self._get_service(session, service_id).to_dict()
def delete_service(self, service_id):
- session = sql.get_session()
- with session.begin():
+ with sql.session_for_write() as session:
ref = self._get_service(session, service_id)
session.query(Endpoint).filter_by(service_id=service_id).delete()
session.delete(ref)
def create_service(self, service_id, service_ref):
- session = sql.get_session()
- with session.begin():
+ with sql.session_for_write() as session:
service = Service.from_dict(service_ref)
session.add(service)
- return service.to_dict()
+ return service.to_dict()
def update_service(self, service_id, service_ref):
- session = sql.get_session()
- with session.begin():
+ with sql.session_for_write() as session:
ref = self._get_service(session, service_id)
old_dict = ref.to_dict()
old_dict.update(service_ref)
@@ -219,20 +208,17 @@ class Catalog(catalog.CatalogDriverV8):
if attr != 'id':
setattr(ref, attr, getattr(new_service, attr))
ref.extra = new_service.extra
- return ref.to_dict()
+ return ref.to_dict()
# Endpoints
def create_endpoint(self, endpoint_id, endpoint_ref):
- session = sql.get_session()
new_endpoint = Endpoint.from_dict(endpoint_ref)
-
- with session.begin():
+ with sql.session_for_write() as session:
session.add(new_endpoint)
return new_endpoint.to_dict()
def delete_endpoint(self, endpoint_id):
- session = sql.get_session()
- with session.begin():
+ with sql.session_for_write() as session:
ref = self._get_endpoint(session, endpoint_id)
session.delete(ref)
@@ -243,20 +229,18 @@ class Catalog(catalog.CatalogDriverV8):
raise exception.EndpointNotFound(endpoint_id=endpoint_id)
def get_endpoint(self, endpoint_id):
- session = sql.get_session()
- return self._get_endpoint(session, endpoint_id).to_dict()
+ with sql.session_for_read() as session:
+ return self._get_endpoint(session, endpoint_id).to_dict()
- @sql.truncated
+ @driver_hints.truncated
def list_endpoints(self, hints):
- session = sql.get_session()
- endpoints = session.query(Endpoint)
- endpoints = sql.filter_limit_query(Endpoint, endpoints, hints)
- return [e.to_dict() for e in list(endpoints)]
+ with sql.session_for_read() as session:
+ endpoints = session.query(Endpoint)
+ endpoints = sql.filter_limit_query(Endpoint, endpoints, hints)
+ return [e.to_dict() for e in list(endpoints)]
def update_endpoint(self, endpoint_id, endpoint_ref):
- session = sql.get_session()
-
- with session.begin():
+ with sql.session_for_write() as session:
ref = self._get_endpoint(session, endpoint_id)
old_dict = ref.to_dict()
old_dict.update(endpoint_ref)
@@ -265,7 +249,7 @@ class Catalog(catalog.CatalogDriverV8):
if attr != 'id':
setattr(ref, attr, getattr(new_endpoint, attr))
ref.extra = new_endpoint.extra
- return ref.to_dict()
+ return ref.to_dict()
def get_catalog(self, user_id, tenant_id):
"""Retrieve and format the V2 service catalog.
@@ -287,44 +271,47 @@ class Catalog(catalog.CatalogDriverV8):
substitutions.update({'user_id': user_id})
silent_keyerror_failures = []
if tenant_id:
- substitutions.update({'tenant_id': tenant_id})
+ substitutions.update({
+ 'tenant_id': tenant_id,
+ 'project_id': tenant_id
+ })
else:
- silent_keyerror_failures = ['tenant_id']
-
- session = sql.get_session()
- endpoints = (session.query(Endpoint).
- options(sql.joinedload(Endpoint.service)).
- filter(Endpoint.enabled == true()).all())
-
- catalog = {}
-
- for endpoint in endpoints:
- if not endpoint.service['enabled']:
- continue
- try:
- formatted_url = core.format_url(
- endpoint['url'], substitutions,
- silent_keyerror_failures=silent_keyerror_failures)
- if formatted_url is not None:
- url = formatted_url
- else:
+ silent_keyerror_failures = ['tenant_id', 'project_id', ]
+
+ with sql.session_for_read() as session:
+ endpoints = (session.query(Endpoint).
+ options(sql.joinedload(Endpoint.service)).
+ filter(Endpoint.enabled == true()).all())
+
+ catalog = {}
+
+ for endpoint in endpoints:
+ if not endpoint.service['enabled']:
continue
- except exception.MalformedEndpoint:
- continue # this failure is already logged in format_url()
-
- region = endpoint['region_id']
- service_type = endpoint.service['type']
- default_service = {
- 'id': endpoint['id'],
- 'name': endpoint.service.extra.get('name', ''),
- 'publicURL': ''
- }
- catalog.setdefault(region, {})
- catalog[region].setdefault(service_type, default_service)
- interface_url = '%sURL' % endpoint['interface']
- catalog[region][service_type][interface_url] = url
-
- return catalog
+ try:
+ formatted_url = core.format_url(
+ endpoint['url'], substitutions,
+ silent_keyerror_failures=silent_keyerror_failures)
+ if formatted_url is not None:
+ url = formatted_url
+ else:
+ continue
+ except exception.MalformedEndpoint:
+ continue # this failure is already logged in format_url()
+
+ region = endpoint['region_id']
+ service_type = endpoint.service['type']
+ default_service = {
+ 'id': endpoint['id'],
+ 'name': endpoint.service.extra.get('name', ''),
+ 'publicURL': ''
+ }
+ catalog.setdefault(region, {})
+ catalog[region].setdefault(service_type, default_service)
+ interface_url = '%sURL' % endpoint['interface']
+ catalog[region][service_type][interface_url] = url
+
+ return catalog
def get_v3_catalog(self, user_id, tenant_id):
"""Retrieve and format the current V3 service catalog.
@@ -344,40 +331,242 @@ class Catalog(catalog.CatalogDriverV8):
d.update({'user_id': user_id})
silent_keyerror_failures = []
if tenant_id:
- d.update({'tenant_id': tenant_id})
+ d.update({
+ 'tenant_id': tenant_id,
+ 'project_id': tenant_id,
+ })
else:
- silent_keyerror_failures = ['tenant_id']
-
- session = sql.get_session()
- services = (session.query(Service).filter(Service.enabled == true()).
- options(sql.joinedload(Service.endpoints)).
- all())
-
- def make_v3_endpoints(endpoints):
- for endpoint in (ep.to_dict() for ep in endpoints if ep.enabled):
- del endpoint['service_id']
- del endpoint['legacy_endpoint_id']
- del endpoint['enabled']
- endpoint['region'] = endpoint['region_id']
- try:
- formatted_url = core.format_url(
- endpoint['url'], d,
- silent_keyerror_failures=silent_keyerror_failures)
- if formatted_url:
- endpoint['url'] = formatted_url
- else:
+ silent_keyerror_failures = ['tenant_id', 'project_id', ]
+
+ with sql.session_for_read() as session:
+ services = (session.query(Service).filter(
+ Service.enabled == true()).options(
+ sql.joinedload(Service.endpoints)).all())
+
+ def make_v3_endpoints(endpoints):
+ for endpoint in (ep.to_dict()
+ for ep in endpoints if ep.enabled):
+ del endpoint['service_id']
+ del endpoint['legacy_endpoint_id']
+ del endpoint['enabled']
+ endpoint['region'] = endpoint['region_id']
+ try:
+ formatted_url = core.format_url(
+ endpoint['url'], d,
+ silent_keyerror_failures=silent_keyerror_failures)
+ if formatted_url:
+ endpoint['url'] = formatted_url
+ else:
+ continue
+ except exception.MalformedEndpoint:
+ # this failure is already logged in format_url()
continue
- except exception.MalformedEndpoint:
- continue # this failure is already logged in format_url()
- yield endpoint
+ yield endpoint
+
+ # TODO(davechen): If there is service with no endpoints, we should
+ # skip the service instead of keeping it in the catalog,
+ # see bug #1436704.
+ def make_v3_service(svc):
+ eps = list(make_v3_endpoints(svc.endpoints))
+ service = {'endpoints': eps, 'id': svc.id, 'type': svc.type}
+ service['name'] = svc.extra.get('name', '')
+ return service
+
+ return [make_v3_service(svc) for svc in services]
+
+ @sql.handle_conflicts(conflict_type='project_endpoint')
+ def add_endpoint_to_project(self, endpoint_id, project_id):
+ with sql.session_for_write() as session:
+ endpoint_filter_ref = ProjectEndpoint(endpoint_id=endpoint_id,
+ project_id=project_id)
+ session.add(endpoint_filter_ref)
+
+ def _get_project_endpoint_ref(self, session, endpoint_id, project_id):
+ endpoint_filter_ref = session.query(ProjectEndpoint).get(
+ (endpoint_id, project_id))
+ if endpoint_filter_ref is None:
+ msg = _('Endpoint %(endpoint_id)s not found in project '
+ '%(project_id)s') % {'endpoint_id': endpoint_id,
+ 'project_id': project_id}
+ raise exception.NotFound(msg)
+ return endpoint_filter_ref
+
+ def check_endpoint_in_project(self, endpoint_id, project_id):
+ with sql.session_for_read() as session:
+ self._get_project_endpoint_ref(session, endpoint_id, project_id)
+
+ def remove_endpoint_from_project(self, endpoint_id, project_id):
+ with sql.session_for_write() as session:
+ endpoint_filter_ref = self._get_project_endpoint_ref(
+ session, endpoint_id, project_id)
+ session.delete(endpoint_filter_ref)
+
+ def list_endpoints_for_project(self, project_id):
+ with sql.session_for_read() as session:
+ query = session.query(ProjectEndpoint)
+ query = query.filter_by(project_id=project_id)
+ endpoint_filter_refs = query.all()
+ return [ref.to_dict() for ref in endpoint_filter_refs]
+
+ def list_projects_for_endpoint(self, endpoint_id):
+ with sql.session_for_read() as session:
+ query = session.query(ProjectEndpoint)
+ query = query.filter_by(endpoint_id=endpoint_id)
+ endpoint_filter_refs = query.all()
+ return [ref.to_dict() for ref in endpoint_filter_refs]
+
+ def delete_association_by_endpoint(self, endpoint_id):
+ with sql.session_for_write() as session:
+ query = session.query(ProjectEndpoint)
+ query = query.filter_by(endpoint_id=endpoint_id)
+ query.delete(synchronize_session=False)
+
+ def delete_association_by_project(self, project_id):
+ with sql.session_for_write() as session:
+ query = session.query(ProjectEndpoint)
+ query = query.filter_by(project_id=project_id)
+ query.delete(synchronize_session=False)
+
+ def create_endpoint_group(self, endpoint_group_id, endpoint_group):
+ with sql.session_for_write() as session:
+ endpoint_group_ref = EndpointGroup.from_dict(endpoint_group)
+ session.add(endpoint_group_ref)
+ return endpoint_group_ref.to_dict()
+
+ def _get_endpoint_group(self, session, endpoint_group_id):
+ endpoint_group_ref = session.query(EndpointGroup).get(
+ endpoint_group_id)
+ if endpoint_group_ref is None:
+ raise exception.EndpointGroupNotFound(
+ endpoint_group_id=endpoint_group_id)
+ return endpoint_group_ref
+
+ def get_endpoint_group(self, endpoint_group_id):
+ with sql.session_for_read() as session:
+ endpoint_group_ref = self._get_endpoint_group(session,
+ endpoint_group_id)
+ return endpoint_group_ref.to_dict()
+
+ def update_endpoint_group(self, endpoint_group_id, endpoint_group):
+ with sql.session_for_write() as session:
+ endpoint_group_ref = self._get_endpoint_group(session,
+ endpoint_group_id)
+ old_endpoint_group = endpoint_group_ref.to_dict()
+ old_endpoint_group.update(endpoint_group)
+ new_endpoint_group = EndpointGroup.from_dict(old_endpoint_group)
+ for attr in EndpointGroup.mutable_attributes:
+ setattr(endpoint_group_ref, attr,
+ getattr(new_endpoint_group, attr))
+ return endpoint_group_ref.to_dict()
+
+ def delete_endpoint_group(self, endpoint_group_id):
+ with sql.session_for_write() as session:
+ endpoint_group_ref = self._get_endpoint_group(session,
+ endpoint_group_id)
+ self._delete_endpoint_group_association_by_endpoint_group(
+ session, endpoint_group_id)
+ session.delete(endpoint_group_ref)
+
+ def get_endpoint_group_in_project(self, endpoint_group_id, project_id):
+ with sql.session_for_read() as session:
+ ref = self._get_endpoint_group_in_project(session,
+ endpoint_group_id,
+ project_id)
+ return ref.to_dict()
+
+ @sql.handle_conflicts(conflict_type='project_endpoint_group')
+ def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
+ with sql.session_for_write() as session:
+ # Create a new Project Endpoint group entity
+ endpoint_group_project_ref = ProjectEndpointGroupMembership(
+ endpoint_group_id=endpoint_group_id, project_id=project_id)
+ session.add(endpoint_group_project_ref)
+
+ def _get_endpoint_group_in_project(self, session,
+ endpoint_group_id, project_id):
+ endpoint_group_project_ref = session.query(
+ ProjectEndpointGroupMembership).get((endpoint_group_id,
+ project_id))
+ if endpoint_group_project_ref is None:
+ msg = _('Endpoint Group Project Association not found')
+ raise exception.NotFound(msg)
+ else:
+ return endpoint_group_project_ref
+
+ def list_endpoint_groups(self):
+ with sql.session_for_read() as session:
+ query = session.query(EndpointGroup)
+ endpoint_group_refs = query.all()
+ return [e.to_dict() for e in endpoint_group_refs]
+
+ def list_endpoint_groups_for_project(self, project_id):
+ with sql.session_for_read() as session:
+ query = session.query(ProjectEndpointGroupMembership)
+ query = query.filter_by(project_id=project_id)
+ endpoint_group_refs = query.all()
+ return [ref.to_dict() for ref in endpoint_group_refs]
+
+ def remove_endpoint_group_from_project(self, endpoint_group_id,
+ project_id):
+ with sql.session_for_write() as session:
+ endpoint_group_project_ref = self._get_endpoint_group_in_project(
+ session, endpoint_group_id, project_id)
+ session.delete(endpoint_group_project_ref)
+
+ def list_projects_associated_with_endpoint_group(self, endpoint_group_id):
+ with sql.session_for_read() as session:
+ query = session.query(ProjectEndpointGroupMembership)
+ query = query.filter_by(endpoint_group_id=endpoint_group_id)
+ endpoint_group_refs = query.all()
+ return [ref.to_dict() for ref in endpoint_group_refs]
+
+ def _delete_endpoint_group_association_by_endpoint_group(
+ self, session, endpoint_group_id):
+ query = session.query(ProjectEndpointGroupMembership)
+ query = query.filter_by(endpoint_group_id=endpoint_group_id)
+ query.delete()
+
+ def delete_endpoint_group_association_by_project(self, project_id):
+ with sql.session_for_write() as session:
+ query = session.query(ProjectEndpointGroupMembership)
+ query = query.filter_by(project_id=project_id)
+ query.delete()
+
+
+class ProjectEndpoint(sql.ModelBase, sql.ModelDictMixin):
+ """project-endpoint relationship table."""
+
+ __tablename__ = 'project_endpoint'
+ attributes = ['endpoint_id', 'project_id']
+ endpoint_id = sql.Column(sql.String(64),
+ primary_key=True,
+ nullable=False)
+ project_id = sql.Column(sql.String(64),
+ primary_key=True,
+ nullable=False)
+
- # TODO(davechen): If there is service with no endpoints, we should skip
- # the service instead of keeping it in the catalog, see bug #1436704.
- def make_v3_service(svc):
- eps = list(make_v3_endpoints(svc.endpoints))
- service = {'endpoints': eps, 'id': svc.id, 'type': svc.type}
- service['name'] = svc.extra.get('name', '')
- return service
+class EndpointGroup(sql.ModelBase, sql.ModelDictMixin):
+ """Endpoint Groups table."""
- return [make_v3_service(svc) for svc in services]
+ __tablename__ = 'endpoint_group'
+ attributes = ['id', 'name', 'description', 'filters']
+ mutable_attributes = frozenset(['name', 'description', 'filters'])
+ id = sql.Column(sql.String(64), primary_key=True)
+ name = sql.Column(sql.String(255), nullable=False)
+ description = sql.Column(sql.Text, nullable=True)
+ filters = sql.Column(sql.JsonBlob(), nullable=False)
+
+
+class ProjectEndpointGroupMembership(sql.ModelBase, sql.ModelDictMixin):
+ """Project to Endpoint group relationship table."""
+
+ __tablename__ = 'project_endpoint_group'
+ attributes = ['endpoint_group_id', 'project_id']
+ endpoint_group_id = sql.Column(sql.String(64),
+ sql.ForeignKey('endpoint_group.id'),
+ nullable=False)
+ project_id = sql.Column(sql.String(64), nullable=False)
+ __table_args__ = (sql.PrimaryKeyConstraint('endpoint_group_id',
+ 'project_id'),)
diff --git a/keystone-moon/keystone/catalog/backends/templated.py b/keystone-moon/keystone/catalog/backends/templated.py
index 31d8b9e0..2e80fd32 100644
--- a/keystone-moon/keystone/catalog/backends/templated.py
+++ b/keystone-moon/keystone/catalog/backends/templated.py
@@ -1,4 +1,4 @@
-# Copyright 2012 OpenStack Foundationc
+# Copyright 2012 OpenStack Foundation
#
# 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
@@ -17,8 +17,8 @@ import os.path
from oslo_config import cfg
from oslo_log import log
+import six
-from keystone.catalog.backends import kvs
from keystone.catalog import core
from keystone import exception
from keystone.i18n import _LC
@@ -56,7 +56,7 @@ def parse_templates(template_lines):
return o
-class Catalog(kvs.Catalog):
+class Catalog(core.Driver):
"""A backend that generates endpoints for the Catalog based on templates.
It is usually configured via config entries that look like:
@@ -100,11 +100,101 @@ class Catalog(kvs.Catalog):
def _load_templates(self, template_file):
try:
- self.templates = parse_templates(open(template_file))
+ with open(template_file) as f:
+ self.templates = parse_templates(f)
except IOError:
LOG.critical(_LC('Unable to open template file %s'), template_file)
raise
+ # region crud
+
+ def create_region(self, region_ref):
+ raise exception.NotImplemented()
+
+ def list_regions(self, hints):
+ return [{'id': region_id, 'description': '', 'parent_region_id': ''}
+ for region_id in self.templates]
+
+ def get_region(self, region_id):
+ if region_id in self.templates:
+ return {'id': region_id, 'description': '', 'parent_region_id': ''}
+ raise exception.RegionNotFound(region_id=region_id)
+
+ def update_region(self, region_id, region_ref):
+ raise exception.NotImplemented()
+
+ def delete_region(self, region_id):
+ raise exception.NotImplemented()
+
+ # service crud
+
+ def create_service(self, service_id, service_ref):
+ raise exception.NotImplemented()
+
+ def _list_services(self, hints):
+ for region_ref in six.itervalues(self.templates):
+ for service_type, service_ref in six.iteritems(region_ref):
+ yield {
+ 'id': service_type,
+ 'enabled': True,
+ 'name': service_ref.get('name', ''),
+ 'description': service_ref.get('description', ''),
+ 'type': service_type,
+ }
+
+ def list_services(self, hints):
+ return list(self._list_services(hints=None))
+
+ def get_service(self, service_id):
+ for service in self._list_services(hints=None):
+ if service['id'] == service_id:
+ return service
+ raise exception.ServiceNotFound(service_id=service_id)
+
+ def update_service(self, service_id, service_ref):
+ raise exception.NotImplemented()
+
+ def delete_service(self, service_id):
+ raise exception.NotImplemented()
+
+ # endpoint crud
+
+ def create_endpoint(self, endpoint_id, endpoint_ref):
+ raise exception.NotImplemented()
+
+ def _list_endpoints(self):
+ for region_id, region_ref in six.iteritems(self.templates):
+ for service_type, service_ref in six.iteritems(region_ref):
+ for key in service_ref:
+ if key.endswith('URL'):
+ interface = key[:-3]
+ endpoint_id = ('%s-%s-%s' %
+ (region_id, service_type, interface))
+ yield {
+ 'id': endpoint_id,
+ 'service_id': service_type,
+ 'interface': interface,
+ 'url': service_ref[key],
+ 'legacy_endpoint_id': None,
+ 'region_id': region_id,
+ 'enabled': True,
+ }
+
+ def list_endpoints(self, hints):
+ return list(self._list_endpoints())
+
+ def get_endpoint(self, endpoint_id):
+ for endpoint in self._list_endpoints():
+ if endpoint['id'] == endpoint_id:
+ return endpoint
+ raise exception.EndpointNotFound(endpoint_id=endpoint_id)
+
+ def update_endpoint(self, endpoint_id, endpoint_ref):
+ raise exception.NotImplemented()
+
+ def delete_endpoint(self, endpoint_id):
+ raise exception.NotImplemented()
+
def get_catalog(self, user_id, tenant_id):
"""Retrieve and format the V2 service catalog.
@@ -124,9 +214,12 @@ class Catalog(kvs.Catalog):
substitutions.update({'user_id': user_id})
silent_keyerror_failures = []
if tenant_id:
- substitutions.update({'tenant_id': tenant_id})
+ substitutions.update({
+ 'tenant_id': tenant_id,
+ 'project_id': tenant_id,
+ })
else:
- silent_keyerror_failures = ['tenant_id']
+ silent_keyerror_failures = ['tenant_id', 'project_id', ]
catalog = {}
# TODO(davechen): If there is service with no endpoints, we should
@@ -148,3 +241,58 @@ class Catalog(kvs.Catalog):
catalog[region][service] = service_data
return catalog
+
+ def add_endpoint_to_project(self, endpoint_id, project_id):
+ raise exception.NotImplemented()
+
+ def remove_endpoint_from_project(self, endpoint_id, project_id):
+ raise exception.NotImplemented()
+
+ def check_endpoint_in_project(self, endpoint_id, project_id):
+ raise exception.NotImplemented()
+
+ def list_endpoints_for_project(self, project_id):
+ raise exception.NotImplemented()
+
+ def list_projects_for_endpoint(self, endpoint_id):
+ raise exception.NotImplemented()
+
+ def delete_association_by_endpoint(self, endpoint_id):
+ raise exception.NotImplemented()
+
+ def delete_association_by_project(self, project_id):
+ raise exception.NotImplemented()
+
+ def create_endpoint_group(self, endpoint_group):
+ raise exception.NotImplemented()
+
+ def get_endpoint_group(self, endpoint_group_id):
+ raise exception.NotImplemented()
+
+ def update_endpoint_group(self, endpoint_group_id, endpoint_group):
+ raise exception.NotImplemented()
+
+ def delete_endpoint_group(self, endpoint_group_id):
+ raise exception.NotImplemented()
+
+ def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
+ raise exception.NotImplemented()
+
+ def get_endpoint_group_in_project(self, endpoint_group_id, project_id):
+ raise exception.NotImplemented()
+
+ def list_endpoint_groups(self):
+ raise exception.NotImplemented()
+
+ def list_endpoint_groups_for_project(self, project_id):
+ raise exception.NotImplemented()
+
+ def list_projects_associated_with_endpoint_group(self, endpoint_group_id):
+ raise exception.NotImplemented()
+
+ def remove_endpoint_group_from_project(self, endpoint_group_id,
+ project_id):
+ raise exception.NotImplemented()
+
+ def delete_endpoint_group_association_by_project(self, project_id):
+ raise exception.NotImplemented()
diff --git a/keystone-moon/keystone/catalog/controllers.py b/keystone-moon/keystone/catalog/controllers.py
index e14b268a..fc64c922 100644
--- a/keystone-moon/keystone/catalog/controllers.py
+++ b/keystone-moon/keystone/catalog/controllers.py
@@ -15,6 +15,8 @@
import uuid
+import six
+
from keystone.catalog import core
from keystone.catalog import schema
from keystone.common import controller
@@ -24,6 +26,7 @@ from keystone.common import wsgi
from keystone import exception
from keystone.i18n import _
from keystone import notifications
+from keystone import resource
INTERFACES = ['public', 'internal', 'admin']
@@ -379,3 +382,234 @@ class EndpointV3(controller.V3Controller):
def delete_endpoint(self, context, endpoint_id):
initiator = notifications._get_request_audit_info(context)
return self.catalog_api.delete_endpoint(endpoint_id, initiator)
+
+
+@dependency.requires('catalog_api', 'resource_api')
+class EndpointFilterV3Controller(controller.V3Controller):
+
+ def __init__(self):
+ super(EndpointFilterV3Controller, self).__init__()
+ notifications.register_event_callback(
+ notifications.ACTIONS.deleted, 'project',
+ self._on_project_or_endpoint_delete)
+ notifications.register_event_callback(
+ notifications.ACTIONS.deleted, 'endpoint',
+ self._on_project_or_endpoint_delete)
+
+ def _on_project_or_endpoint_delete(self, service, resource_type, operation,
+ payload):
+ project_or_endpoint_id = payload['resource_info']
+ if resource_type == 'project':
+ self.catalog_api.delete_association_by_project(
+ project_or_endpoint_id)
+ else:
+ self.catalog_api.delete_association_by_endpoint(
+ project_or_endpoint_id)
+
+ @controller.protected()
+ def add_endpoint_to_project(self, context, project_id, endpoint_id):
+ """Establishes an association between an endpoint and a project."""
+ # NOTE(gyee): we just need to make sure endpoint and project exist
+ # first. We don't really care whether if project is disabled.
+ # The relationship can still be established even with a disabled
+ # project as there are no security implications.
+ self.catalog_api.get_endpoint(endpoint_id)
+ self.resource_api.get_project(project_id)
+ self.catalog_api.add_endpoint_to_project(endpoint_id,
+ project_id)
+
+ @controller.protected()
+ def check_endpoint_in_project(self, context, project_id, endpoint_id):
+ """Verifies endpoint is currently associated with given project."""
+ self.catalog_api.get_endpoint(endpoint_id)
+ self.resource_api.get_project(project_id)
+ self.catalog_api.check_endpoint_in_project(endpoint_id,
+ project_id)
+
+ @controller.protected()
+ def list_endpoints_for_project(self, context, project_id):
+ """List all endpoints currently associated with a given project."""
+ self.resource_api.get_project(project_id)
+ filtered_endpoints = self.catalog_api.list_endpoints_for_project(
+ project_id)
+
+ return EndpointV3.wrap_collection(
+ context, [v for v in six.itervalues(filtered_endpoints)])
+
+ @controller.protected()
+ def remove_endpoint_from_project(self, context, project_id, endpoint_id):
+ """Remove the endpoint from the association with given project."""
+ self.catalog_api.remove_endpoint_from_project(endpoint_id,
+ project_id)
+
+ @controller.protected()
+ def list_projects_for_endpoint(self, context, endpoint_id):
+ """Return a list of projects associated with the endpoint."""
+ self.catalog_api.get_endpoint(endpoint_id)
+ refs = self.catalog_api.list_projects_for_endpoint(endpoint_id)
+
+ projects = [self.resource_api.get_project(
+ ref['project_id']) for ref in refs]
+ return resource.controllers.ProjectV3.wrap_collection(context,
+ projects)
+
+
+@dependency.requires('catalog_api', 'resource_api')
+class EndpointGroupV3Controller(controller.V3Controller):
+ collection_name = 'endpoint_groups'
+ member_name = 'endpoint_group'
+
+ VALID_FILTER_KEYS = ['service_id', 'region_id', 'interface']
+
+ def __init__(self):
+ super(EndpointGroupV3Controller, self).__init__()
+
+ @classmethod
+ def base_url(cls, context, path=None):
+ """Construct a path and pass it to V3Controller.base_url method."""
+ path = '/OS-EP-FILTER/' + cls.collection_name
+ return super(EndpointGroupV3Controller, cls).base_url(context,
+ path=path)
+
+ @controller.protected()
+ @validation.validated(schema.endpoint_group_create, 'endpoint_group')
+ def create_endpoint_group(self, context, endpoint_group):
+ """Creates an Endpoint Group with the associated filters."""
+ ref = self._assign_unique_id(self._normalize_dict(endpoint_group))
+ self._require_attribute(ref, 'filters')
+ self._require_valid_filter(ref)
+ ref = self.catalog_api.create_endpoint_group(ref['id'], ref)
+ return EndpointGroupV3Controller.wrap_member(context, ref)
+
+ def _require_valid_filter(self, endpoint_group):
+ filters = endpoint_group.get('filters')
+ for key in six.iterkeys(filters):
+ if key not in self.VALID_FILTER_KEYS:
+ raise exception.ValidationError(
+ attribute=self._valid_filter_keys(),
+ target='endpoint_group')
+
+ def _valid_filter_keys(self):
+ return ' or '.join(self.VALID_FILTER_KEYS)
+
+ @controller.protected()
+ def get_endpoint_group(self, context, endpoint_group_id):
+ """Retrieve the endpoint group associated with the id if exists."""
+ ref = self.catalog_api.get_endpoint_group(endpoint_group_id)
+ return EndpointGroupV3Controller.wrap_member(
+ context, ref)
+
+ @controller.protected()
+ @validation.validated(schema.endpoint_group_update, 'endpoint_group')
+ def update_endpoint_group(self, context, endpoint_group_id,
+ endpoint_group):
+ """Update fixed values and/or extend the filters."""
+ if 'filters' in endpoint_group:
+ self._require_valid_filter(endpoint_group)
+ ref = self.catalog_api.update_endpoint_group(endpoint_group_id,
+ endpoint_group)
+ return EndpointGroupV3Controller.wrap_member(
+ context, ref)
+
+ @controller.protected()
+ def delete_endpoint_group(self, context, endpoint_group_id):
+ """Delete endpoint_group."""
+ self.catalog_api.delete_endpoint_group(endpoint_group_id)
+
+ @controller.protected()
+ def list_endpoint_groups(self, context):
+ """List all endpoint groups."""
+ refs = self.catalog_api.list_endpoint_groups()
+ return EndpointGroupV3Controller.wrap_collection(
+ context, refs)
+
+ @controller.protected()
+ def list_endpoint_groups_for_project(self, context, project_id):
+ """List all endpoint groups associated with a given project."""
+ return EndpointGroupV3Controller.wrap_collection(
+ context,
+ self.catalog_api.get_endpoint_groups_for_project(project_id))
+
+ @controller.protected()
+ def list_projects_associated_with_endpoint_group(self,
+ context,
+ endpoint_group_id):
+ """List all projects associated with endpoint group."""
+ endpoint_group_refs = (self.catalog_api.
+ list_projects_associated_with_endpoint_group(
+ endpoint_group_id))
+ projects = []
+ for endpoint_group_ref in endpoint_group_refs:
+ project = self.resource_api.get_project(
+ endpoint_group_ref['project_id'])
+ if project:
+ projects.append(project)
+ return resource.controllers.ProjectV3.wrap_collection(context,
+ projects)
+
+ @controller.protected()
+ def list_endpoints_associated_with_endpoint_group(self,
+ context,
+ endpoint_group_id):
+ """List all the endpoints filtered by a specific endpoint group."""
+ filtered_endpoints = (self.catalog_api.
+ get_endpoints_filtered_by_endpoint_group(
+ endpoint_group_id))
+ return EndpointV3.wrap_collection(context, filtered_endpoints)
+
+
+@dependency.requires('catalog_api', 'resource_api')
+class ProjectEndpointGroupV3Controller(controller.V3Controller):
+ collection_name = 'project_endpoint_groups'
+ member_name = 'project_endpoint_group'
+
+ def __init__(self):
+ super(ProjectEndpointGroupV3Controller, self).__init__()
+ notifications.register_event_callback(
+ notifications.ACTIONS.deleted, 'project',
+ self._on_project_delete)
+
+ def _on_project_delete(self, service, resource_type,
+ operation, payload):
+ project_id = payload['resource_info']
+ (self.catalog_api.
+ delete_endpoint_group_association_by_project(
+ project_id))
+
+ @controller.protected()
+ def get_endpoint_group_in_project(self, context, endpoint_group_id,
+ project_id):
+ """Retrieve the endpoint group associated with the id if exists."""
+ self.resource_api.get_project(project_id)
+ self.catalog_api.get_endpoint_group(endpoint_group_id)
+ ref = self.catalog_api.get_endpoint_group_in_project(
+ endpoint_group_id, project_id)
+ return ProjectEndpointGroupV3Controller.wrap_member(
+ context, ref)
+
+ @controller.protected()
+ def add_endpoint_group_to_project(self, context, endpoint_group_id,
+ project_id):
+ """Creates an association between an endpoint group and project."""
+ self.resource_api.get_project(project_id)
+ self.catalog_api.get_endpoint_group(endpoint_group_id)
+ self.catalog_api.add_endpoint_group_to_project(
+ endpoint_group_id, project_id)
+
+ @controller.protected()
+ def remove_endpoint_group_from_project(self, context, endpoint_group_id,
+ project_id):
+ """Remove the endpoint group from associated project."""
+ self.resource_api.get_project(project_id)
+ self.catalog_api.get_endpoint_group(endpoint_group_id)
+ self.catalog_api.remove_endpoint_group_from_project(
+ endpoint_group_id, project_id)
+
+ @classmethod
+ def _add_self_referential_link(cls, context, ref):
+ url = ('/OS-EP-FILTER/endpoint_groups/%(endpoint_group_id)s'
+ '/projects/%(project_id)s' % {
+ 'endpoint_group_id': ref['endpoint_group_id'],
+ 'project_id': ref['project_id']})
+ ref.setdefault('links', {})
+ ref['links']['self'] = url
diff --git a/keystone-moon/keystone/catalog/core.py b/keystone-moon/keystone/catalog/core.py
index 8bb72619..384a9b2b 100644
--- a/keystone-moon/keystone/catalog/core.py
+++ b/keystone-moon/keystone/catalog/core.py
@@ -18,6 +18,7 @@
import abc
import itertools
+from oslo_cache import core as oslo_cache
from oslo_config import cfg
from oslo_log import log
import six
@@ -35,12 +36,24 @@ from keystone import notifications
CONF = cfg.CONF
LOG = log.getLogger(__name__)
-MEMOIZE = cache.get_memoization_decorator(section='catalog')
WHITELISTED_PROPERTIES = [
- 'tenant_id', 'user_id', 'public_bind_host', 'admin_bind_host',
+ 'tenant_id', 'project_id', 'user_id',
+ 'public_bind_host', 'admin_bind_host',
'compute_host', 'admin_port', 'public_port',
'public_endpoint', 'admin_endpoint', ]
+# This is a general cache region for catalog administration (CRUD operations).
+MEMOIZE = cache.get_memoization_decorator(group='catalog')
+
+# This builds a discrete cache region dedicated to complete service catalogs
+# computed for a given user + project pair. Any write operation to create,
+# modify or delete elements of the service catalog should invalidate this
+# entire cache region.
+COMPUTED_CATALOG_REGION = oslo_cache.create_region()
+MEMOIZE_COMPUTED_CATALOG = cache.get_memoization_decorator(
+ group='catalog',
+ region=COMPUTED_CATALOG_REGION)
+
def format_url(url, substitutions, silent_keyerror_failures=None):
"""Formats a user-defined URL with the given substitutions.
@@ -52,7 +65,6 @@ def format_url(url, substitutions, silent_keyerror_failures=None):
:returns: a formatted URL
"""
-
substitutions = utils.WhiteListedItemFilter(
WHITELISTED_PROPERTIES,
substitutions)
@@ -108,6 +120,7 @@ def check_endpoint_url(url):
@dependency.provider('catalog_api')
+@dependency.requires('resource_api')
class Manager(manager.Manager):
"""Default pivot point for the Catalog backend.
@@ -129,7 +142,8 @@ class Manager(manager.Manager):
# Check duplicate ID
try:
self.get_region(region_ref['id'])
- except exception.RegionNotFound:
+ except exception.RegionNotFound: # nosec
+ # A region with the same id doesn't exist already, good.
pass
else:
msg = _('Duplicate ID, %s.') % region_ref['id']
@@ -148,6 +162,7 @@ class Manager(manager.Manager):
raise exception.RegionNotFound(region_id=parent_region_id)
notifications.Audit.created(self._REGION, ret['id'], initiator)
+ COMPUTED_CATALOG_REGION.invalidate()
return ret
@MEMOIZE
@@ -166,6 +181,7 @@ class Manager(manager.Manager):
ref = self.driver.update_region(region_id, region_ref)
notifications.Audit.updated(self._REGION, region_id, initiator)
self.get_region.invalidate(self, region_id)
+ COMPUTED_CATALOG_REGION.invalidate()
return ref
def delete_region(self, region_id, initiator=None):
@@ -173,6 +189,7 @@ class Manager(manager.Manager):
ret = self.driver.delete_region(region_id)
notifications.Audit.deleted(self._REGION, region_id, initiator)
self.get_region.invalidate(self, region_id)
+ COMPUTED_CATALOG_REGION.invalidate()
return ret
except exception.NotFound:
raise exception.RegionNotFound(region_id=region_id)
@@ -186,6 +203,7 @@ class Manager(manager.Manager):
service_ref.setdefault('name', '')
ref = self.driver.create_service(service_id, service_ref)
notifications.Audit.created(self._SERVICE, service_id, initiator)
+ COMPUTED_CATALOG_REGION.invalidate()
return ref
@MEMOIZE
@@ -199,6 +217,7 @@ class Manager(manager.Manager):
ref = self.driver.update_service(service_id, service_ref)
notifications.Audit.updated(self._SERVICE, service_id, initiator)
self.get_service.invalidate(self, service_id)
+ COMPUTED_CATALOG_REGION.invalidate()
return ref
def delete_service(self, service_id, initiator=None):
@@ -210,6 +229,7 @@ class Manager(manager.Manager):
for endpoint in endpoints:
if endpoint['service_id'] == service_id:
self.get_endpoint.invalidate(self, endpoint['id'])
+ COMPUTED_CATALOG_REGION.invalidate()
return ret
except exception.NotFound:
raise exception.ServiceNotFound(service_id=service_id)
@@ -240,6 +260,7 @@ class Manager(manager.Manager):
ref = self.driver.create_endpoint(endpoint_id, endpoint_ref)
notifications.Audit.created(self._ENDPOINT, endpoint_id, initiator)
+ COMPUTED_CATALOG_REGION.invalidate()
return ref
def update_endpoint(self, endpoint_id, endpoint_ref, initiator=None):
@@ -248,6 +269,7 @@ class Manager(manager.Manager):
ref = self.driver.update_endpoint(endpoint_id, endpoint_ref)
notifications.Audit.updated(self._ENDPOINT, endpoint_id, initiator)
self.get_endpoint.invalidate(self, endpoint_id)
+ COMPUTED_CATALOG_REGION.invalidate()
return ref
def delete_endpoint(self, endpoint_id, initiator=None):
@@ -255,6 +277,7 @@ class Manager(manager.Manager):
ret = self.driver.delete_endpoint(endpoint_id)
notifications.Audit.deleted(self._ENDPOINT, endpoint_id, initiator)
self.get_endpoint.invalidate(self, endpoint_id)
+ COMPUTED_CATALOG_REGION.invalidate()
return ret
except exception.NotFound:
raise exception.EndpointNotFound(endpoint_id=endpoint_id)
@@ -270,12 +293,96 @@ class Manager(manager.Manager):
def list_endpoints(self, hints=None):
return self.driver.list_endpoints(hints or driver_hints.Hints())
+ @MEMOIZE_COMPUTED_CATALOG
def get_catalog(self, user_id, tenant_id):
try:
return self.driver.get_catalog(user_id, tenant_id)
except exception.NotFound:
raise exception.NotFound('Catalog not found for user and tenant')
+ @MEMOIZE_COMPUTED_CATALOG
+ def get_v3_catalog(self, user_id, tenant_id):
+ return self.driver.get_v3_catalog(user_id, tenant_id)
+
+ def add_endpoint_to_project(self, endpoint_id, project_id):
+ self.driver.add_endpoint_to_project(endpoint_id, project_id)
+ COMPUTED_CATALOG_REGION.invalidate()
+
+ def remove_endpoint_from_project(self, endpoint_id, project_id):
+ self.driver.remove_endpoint_from_project(endpoint_id, project_id)
+ COMPUTED_CATALOG_REGION.invalidate()
+
+ def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
+ self.driver.add_endpoint_group_to_project(
+ endpoint_group_id, project_id)
+ COMPUTED_CATALOG_REGION.invalidate()
+
+ def remove_endpoint_group_from_project(self, endpoint_group_id,
+ project_id):
+ self.driver.remove_endpoint_group_from_project(
+ endpoint_group_id, project_id)
+ COMPUTED_CATALOG_REGION.invalidate()
+
+ def get_endpoint_groups_for_project(self, project_id):
+ # recover the project endpoint group memberships and for each
+ # membership recover the endpoint group
+ self.resource_api.get_project(project_id)
+ try:
+ refs = self.list_endpoint_groups_for_project(project_id)
+ endpoint_groups = [self.get_endpoint_group(
+ ref['endpoint_group_id']) for ref in refs]
+ return endpoint_groups
+ except exception.EndpointGroupNotFound:
+ return []
+
+ def get_endpoints_filtered_by_endpoint_group(self, endpoint_group_id):
+ endpoints = self.list_endpoints()
+ filters = self.get_endpoint_group(endpoint_group_id)['filters']
+ filtered_endpoints = []
+
+ for endpoint in endpoints:
+ is_candidate = True
+ for key, value in filters.items():
+ if endpoint[key] != value:
+ is_candidate = False
+ break
+ if is_candidate:
+ filtered_endpoints.append(endpoint)
+ return filtered_endpoints
+
+ def list_endpoints_for_project(self, project_id):
+ """List all endpoints associated with a project.
+
+ :param project_id: project identifier to check
+ :type project_id: string
+ :returns: a list of endpoint ids or an empty list.
+
+ """
+ refs = self.driver.list_endpoints_for_project(project_id)
+ filtered_endpoints = {}
+ for ref in refs:
+ try:
+ endpoint = self.get_endpoint(ref['endpoint_id'])
+ filtered_endpoints.update({ref['endpoint_id']: endpoint})
+ except exception.EndpointNotFound:
+ # remove bad reference from association
+ self.remove_endpoint_from_project(ref['endpoint_id'],
+ project_id)
+
+ # need to recover endpoint_groups associated with project
+ # then for each endpoint group return the endpoints.
+ endpoint_groups = self.get_endpoint_groups_for_project(project_id)
+ for endpoint_group in endpoint_groups:
+ endpoint_refs = self.get_endpoints_filtered_by_endpoint_group(
+ endpoint_group['id'])
+ # now check if any endpoints for current endpoint group are not
+ # contained in the list of filtered endpoints
+ for endpoint_ref in endpoint_refs:
+ if endpoint_ref['id'] not in filtered_endpoints:
+ filtered_endpoints[endpoint_ref['id']] = endpoint_ref
+
+ return filtered_endpoints
+
@six.add_metaclass(abc.ABCMeta)
class CatalogDriverV8(object):
@@ -304,8 +411,9 @@ class CatalogDriverV8(object):
def create_region(self, region_ref):
"""Creates a new region.
- :raises: keystone.exception.Conflict
- :raises: keystone.exception.RegionNotFound (if parent region invalid)
+ :raises keystone.exception.Conflict: If the region already exists.
+ :raises keystone.exception.RegionNotFound: If the parent region
+ is invalid.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -328,7 +436,7 @@ class CatalogDriverV8(object):
"""Get region by id.
:returns: region_ref dict
- :raises: keystone.exception.RegionNotFound
+ :raises keystone.exception.RegionNotFound: If the region doesn't exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -338,7 +446,7 @@ class CatalogDriverV8(object):
"""Update region by id.
:returns: region_ref dict
- :raises: keystone.exception.RegionNotFound
+ :raises keystone.exception.RegionNotFound: If the region doesn't exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -347,7 +455,7 @@ class CatalogDriverV8(object):
def delete_region(self, region_id):
"""Deletes an existing region.
- :raises: keystone.exception.RegionNotFound
+ :raises keystone.exception.RegionNotFound: If the region doesn't exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -356,7 +464,7 @@ class CatalogDriverV8(object):
def create_service(self, service_id, service_ref):
"""Creates a new service.
- :raises: keystone.exception.Conflict
+ :raises keystone.exception.Conflict: If a duplicate service exists.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -379,7 +487,8 @@ class CatalogDriverV8(object):
"""Get service by id.
:returns: service_ref dict
- :raises: keystone.exception.ServiceNotFound
+ :raises keystone.exception.ServiceNotFound: If the service doesn't
+ exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -389,7 +498,8 @@ class CatalogDriverV8(object):
"""Update service by id.
:returns: service_ref dict
- :raises: keystone.exception.ServiceNotFound
+ :raises keystone.exception.ServiceNotFound: If the service doesn't
+ exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -398,7 +508,8 @@ class CatalogDriverV8(object):
def delete_service(self, service_id):
"""Deletes an existing service.
- :raises: keystone.exception.ServiceNotFound
+ :raises keystone.exception.ServiceNotFound: If the service doesn't
+ exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -407,8 +518,9 @@ class CatalogDriverV8(object):
def create_endpoint(self, endpoint_id, endpoint_ref):
"""Creates a new endpoint for a service.
- :raises: keystone.exception.Conflict,
- keystone.exception.ServiceNotFound
+ :raises keystone.exception.Conflict: If a duplicate endpoint exists.
+ :raises keystone.exception.ServiceNotFound: If the service doesn't
+ exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -418,7 +530,8 @@ class CatalogDriverV8(object):
"""Get endpoint by id.
:returns: endpoint_ref dict
- :raises: keystone.exception.EndpointNotFound
+ :raises keystone.exception.EndpointNotFound: If the endpoint doesn't
+ exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -441,8 +554,10 @@ class CatalogDriverV8(object):
"""Get endpoint by id.
:returns: endpoint_ref dict
- :raises: keystone.exception.EndpointNotFound
- keystone.exception.ServiceNotFound
+ :raises keystone.exception.EndpointNotFound: If the endpoint doesn't
+ exist.
+ :raises keystone.exception.ServiceNotFound: If the service doesn't
+ exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -451,7 +566,8 @@ class CatalogDriverV8(object):
def delete_endpoint(self, endpoint_id):
"""Deletes an endpoint for a service.
- :raises: keystone.exception.EndpointNotFound
+ :raises keystone.exception.EndpointNotFound: If the endpoint doesn't
+ exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -476,7 +592,7 @@ class CatalogDriverV8(object):
:returns: A nested dict representing the service catalog or an
empty dict.
- :raises: keystone.exception.NotFound
+ :raises keystone.exception.NotFound: If the endpoint doesn't exist.
"""
raise exception.NotImplemented() # pragma: no cover
@@ -508,7 +624,7 @@ class CatalogDriverV8(object):
}]
:returns: A list representing the service catalog or an empty list
- :raises: keystone.exception.NotFound
+ :raises keystone.exception.NotFound: If the endpoint doesn't exist.
"""
v2_catalog = self.get_catalog(user_id, tenant_id)
@@ -544,5 +660,235 @@ class CatalogDriverV8(object):
return v3_catalog
+ @abc.abstractmethod
+ def add_endpoint_to_project(self, endpoint_id, project_id):
+ """Create an endpoint to project association.
+
+ :param endpoint_id: identity of endpoint to associate
+ :type endpoint_id: string
+ :param project_id: identity of the project to be associated with
+ :type project_id: string
+ :raises: keystone.exception.Conflict: If the endpoint was already
+ added to project.
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def remove_endpoint_from_project(self, endpoint_id, project_id):
+ """Removes an endpoint to project association.
+
+ :param endpoint_id: identity of endpoint to remove
+ :type endpoint_id: string
+ :param project_id: identity of the project associated with
+ :type project_id: string
+ :raises keystone.exception.NotFound: If the endpoint was not found
+ in the project.
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def check_endpoint_in_project(self, endpoint_id, project_id):
+ """Checks if an endpoint is associated with a project.
+
+ :param endpoint_id: identity of endpoint to check
+ :type endpoint_id: string
+ :param project_id: identity of the project associated with
+ :type project_id: string
+ :raises keystone.exception.NotFound: If the endpoint was not found
+ in the project.
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def list_endpoints_for_project(self, project_id):
+ """List all endpoints associated with a project.
+
+ :param project_id: identity of the project to check
+ :type project_id: string
+ :returns: a list of identity endpoint ids or an empty list.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def list_projects_for_endpoint(self, endpoint_id):
+ """List all projects associated with an endpoint.
+
+ :param endpoint_id: identity of endpoint to check
+ :type endpoint_id: string
+ :returns: a list of projects or an empty list.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def delete_association_by_endpoint(self, endpoint_id):
+ """Removes all the endpoints to project association with endpoint.
+
+ :param endpoint_id: identity of endpoint to check
+ :type endpoint_id: string
+ :returns: None
+
+ """
+ raise exception.NotImplemented()
+
+ @abc.abstractmethod
+ def delete_association_by_project(self, project_id):
+ """Removes all the endpoints to project association with project.
+
+ :param project_id: identity of the project to check
+ :type project_id: string
+ :returns: None
+
+ """
+ raise exception.NotImplemented()
+
+ @abc.abstractmethod
+ def create_endpoint_group(self, endpoint_group):
+ """Create an endpoint group.
+
+ :param endpoint_group: endpoint group to create
+ :type endpoint_group: dictionary
+ :raises: keystone.exception.Conflict: If a duplicate endpoint group
+ already exists.
+ :returns: an endpoint group representation.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def get_endpoint_group(self, endpoint_group_id):
+ """Get an endpoint group.
+
+ :param endpoint_group_id: identity of endpoint group to retrieve
+ :type endpoint_group_id: string
+ :raises keystone.exception.NotFound: If the endpoint group was not
+ found.
+ :returns: an endpoint group representation.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def update_endpoint_group(self, endpoint_group_id, endpoint_group):
+ """Update an endpoint group.
+
+ :param endpoint_group_id: identity of endpoint group to retrieve
+ :type endpoint_group_id: string
+ :param endpoint_group: A full or partial endpoint_group
+ :type endpoint_group: dictionary
+ :raises keystone.exception.NotFound: If the endpoint group was not
+ found.
+ :returns: an endpoint group representation.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def delete_endpoint_group(self, endpoint_group_id):
+ """Delete an endpoint group.
+
+ :param endpoint_group_id: identity of endpoint group to delete
+ :type endpoint_group_id: string
+ :raises keystone.exception.NotFound: If the endpoint group was not
+ found.
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def add_endpoint_group_to_project(self, endpoint_group_id, project_id):
+ """Adds an endpoint group to project association.
+
+ :param endpoint_group_id: identity of endpoint to associate
+ :type endpoint_group_id: string
+ :param project_id: identity of project to associate
+ :type project_id: string
+ :raises keystone.exception.Conflict: If the endpoint group was already
+ added to the project.
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def get_endpoint_group_in_project(self, endpoint_group_id, project_id):
+ """Get endpoint group to project association.
+
+ :param endpoint_group_id: identity of endpoint group to retrieve
+ :type endpoint_group_id: string
+ :param project_id: identity of project to associate
+ :type project_id: string
+ :raises keystone.exception.NotFound: If the endpoint group to the
+ project association was not found.
+ :returns: a project endpoint group representation.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def list_endpoint_groups(self):
+ """List all endpoint groups.
+
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def list_endpoint_groups_for_project(self, project_id):
+ """List all endpoint group to project associations for a project.
+
+ :param project_id: identity of project to associate
+ :type project_id: string
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def list_projects_associated_with_endpoint_group(self, endpoint_group_id):
+ """List all projects associated with endpoint group.
+
+ :param endpoint_group_id: identity of endpoint to associate
+ :type endpoint_group_id: string
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def remove_endpoint_group_from_project(self, endpoint_group_id,
+ project_id):
+ """Remove an endpoint to project association.
+
+ :param endpoint_group_id: identity of endpoint to associate
+ :type endpoint_group_id: string
+ :param project_id: identity of project to associate
+ :type project_id: string
+ :raises keystone.exception.NotFound: If endpoint group project
+ association was not found.
+ :returns: None.
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
+
+ @abc.abstractmethod
+ def delete_endpoint_group_association_by_project(self, project_id):
+ """Remove endpoint group to project associations.
+
+ :param project_id: identity of the project to check
+ :type project_id: string
+ :returns: None
+
+ """
+ raise exception.NotImplemented() # pragma: no cover
Driver = manager.create_legacy_driver(CatalogDriverV8)
diff --git a/keystone-moon/keystone/catalog/routers.py b/keystone-moon/keystone/catalog/routers.py
index f3bd988b..8c6e96f0 100644
--- a/keystone-moon/keystone/catalog/routers.py
+++ b/keystone-moon/keystone/catalog/routers.py
@@ -12,15 +12,72 @@
# License for the specific language governing permissions and limitations
# under the License.
+import functools
+
from keystone.catalog import controllers
+from keystone.common import json_home
from keystone.common import router
from keystone.common import wsgi
+build_resource_relation = functools.partial(
+ json_home.build_v3_extension_resource_relation,
+ extension_name='OS-EP-FILTER', extension_version='1.0')
+
+build_parameter_relation = functools.partial(
+ json_home.build_v3_extension_parameter_relation,
+ extension_name='OS-EP-FILTER', extension_version='1.0')
+
+ENDPOINT_GROUP_PARAMETER_RELATION = build_parameter_relation(
+ parameter_name='endpoint_group_id')
+
+
class Routers(wsgi.RoutersBase):
+ """API for the keystone catalog.
+
+ The API Endpoint Filter looks like::
+
+ PUT /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}
+ GET /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}
+ HEAD /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}
+ DELETE /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id}
+ GET /OS-EP-FILTER/endpoints/{endpoint_id}/projects
+ GET /OS-EP-FILTER/projects/{project_id}/endpoints
+ GET /OS-EP-FILTER/projects/{project_id}/endpoint_groups
+
+ GET /OS-EP-FILTER/endpoint_groups
+ POST /OS-EP-FILTER/endpoint_groups
+ GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}
+ HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}
+ PATCH /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}
+ DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}
+
+ GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects
+ GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/endpoints
+
+ PUT /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/
+ {project_id}
+ GET /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/
+ {project_id}
+ HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/
+ {project_id}
+ DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/
+ {project_id}
+
+ """
+
+ PATH_PREFIX = '/OS-EP-FILTER'
+ PATH_PROJECT_ENDPOINT = '/projects/{project_id}/endpoints/{endpoint_id}'
+ PATH_ENDPOINT_GROUPS = '/endpoint_groups/{endpoint_group_id}'
+ PATH_ENDPOINT_GROUP_PROJECTS = PATH_ENDPOINT_GROUPS + (
+ '/projects/{project_id}')
def append_v3_routers(self, mapper, routers):
regions_controller = controllers.RegionV3()
+ endpoint_filter_controller = controllers.EndpointFilterV3Controller()
+ endpoint_group_controller = controllers.EndpointGroupV3Controller()
+ project_endpoint_group_controller = (
+ controllers.ProjectEndpointGroupV3Controller())
routers.append(router.Router(regions_controller,
'regions', 'region',
resource_descriptions=self.v3_resources))
@@ -38,3 +95,88 @@ class Routers(wsgi.RoutersBase):
routers.append(router.Router(controllers.EndpointV3(),
'endpoints', 'endpoint',
resource_descriptions=self.v3_resources))
+
+ self._add_resource(
+ mapper, endpoint_filter_controller,
+ path=self.PATH_PREFIX + '/endpoints/{endpoint_id}/projects',
+ get_action='list_projects_for_endpoint',
+ rel=build_resource_relation(resource_name='endpoint_projects'),
+ path_vars={
+ 'endpoint_id': json_home.Parameters.ENDPOINT_ID,
+ })
+ self._add_resource(
+ mapper, endpoint_filter_controller,
+ path=self.PATH_PREFIX + self.PATH_PROJECT_ENDPOINT,
+ get_head_action='check_endpoint_in_project',
+ put_action='add_endpoint_to_project',
+ delete_action='remove_endpoint_from_project',
+ rel=build_resource_relation(resource_name='project_endpoint'),
+ path_vars={
+ 'endpoint_id': json_home.Parameters.ENDPOINT_ID,
+ 'project_id': json_home.Parameters.PROJECT_ID,
+ })
+ self._add_resource(
+ mapper, endpoint_filter_controller,
+ path=self.PATH_PREFIX + '/projects/{project_id}/endpoints',
+ get_action='list_endpoints_for_project',
+ rel=build_resource_relation(resource_name='project_endpoints'),
+ path_vars={
+ 'project_id': json_home.Parameters.PROJECT_ID,
+ })
+ self._add_resource(
+ mapper, endpoint_group_controller,
+ path=self.PATH_PREFIX + '/projects/{project_id}/endpoint_groups',
+ get_action='list_endpoint_groups_for_project',
+ rel=build_resource_relation(
+ resource_name='project_endpoint_groups'),
+ path_vars={
+ 'project_id': json_home.Parameters.PROJECT_ID,
+ })
+ self._add_resource(
+ mapper, endpoint_group_controller,
+ path=self.PATH_PREFIX + '/endpoint_groups',
+ get_action='list_endpoint_groups',
+ post_action='create_endpoint_group',
+ rel=build_resource_relation(resource_name='endpoint_groups'))
+ self._add_resource(
+ mapper, endpoint_group_controller,
+ path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS,
+ get_head_action='get_endpoint_group',
+ patch_action='update_endpoint_group',
+ delete_action='delete_endpoint_group',
+ rel=build_resource_relation(resource_name='endpoint_group'),
+ path_vars={
+ 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION
+ })
+ self._add_resource(
+ mapper, project_endpoint_group_controller,
+ path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUP_PROJECTS,
+ get_head_action='get_endpoint_group_in_project',
+ put_action='add_endpoint_group_to_project',
+ delete_action='remove_endpoint_group_from_project',
+ rel=build_resource_relation(
+ resource_name='endpoint_group_to_project_association'),
+ path_vars={
+ 'project_id': json_home.Parameters.PROJECT_ID,
+ 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION
+ })
+ self._add_resource(
+ mapper, endpoint_group_controller,
+ path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS + (
+ '/projects'),
+ get_action='list_projects_associated_with_endpoint_group',
+ rel=build_resource_relation(
+ resource_name='projects_associated_with_endpoint_group'),
+ path_vars={
+ 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION
+ })
+ self._add_resource(
+ mapper, endpoint_group_controller,
+ path=self.PATH_PREFIX + self.PATH_ENDPOINT_GROUPS + (
+ '/endpoints'),
+ get_action='list_endpoints_associated_with_endpoint_group',
+ rel=build_resource_relation(
+ resource_name='endpoints_in_endpoint_group'),
+ path_vars={
+ 'endpoint_group_id': ENDPOINT_GROUP_PARAMETER_RELATION
+ })
diff --git a/keystone-moon/keystone/catalog/schema.py b/keystone-moon/keystone/catalog/schema.py
index 671f1233..b9643131 100644
--- a/keystone-moon/keystone/catalog/schema.py
+++ b/keystone-moon/keystone/catalog/schema.py
@@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+from keystone.common import validation
from keystone.common.validation import parameter_types
@@ -96,3 +97,23 @@ endpoint_update = {
'minProperties': 1,
'additionalProperties': True
}
+
+_endpoint_group_properties = {
+ 'description': validation.nullable(parameter_types.description),
+ 'filters': {
+ 'type': 'object'
+ },
+ 'name': parameter_types.name
+}
+
+endpoint_group_create = {
+ 'type': 'object',
+ 'properties': _endpoint_group_properties,
+ 'required': ['name', 'filters']
+}
+
+endpoint_group_update = {
+ 'type': 'object',
+ 'properties': _endpoint_group_properties,
+ 'minProperties': 1
+}