diff --git a/keystone/api/__init__.py b/keystone/api/__init__.py index 2e714eb8af..ca99cdddfc 100644 --- a/keystone/api/__init__.py +++ b/keystone/api/__init__.py @@ -12,6 +12,7 @@ from keystone.api import credentials from keystone.api import discovery +from keystone.api import endpoints from keystone.api import limits from keystone.api import os_ep_filter from keystone.api import os_oauth1 @@ -25,6 +26,7 @@ from keystone.api import trusts __all__ = ( 'discovery', 'credentials', + 'endpoints', 'limits', 'os_ep_filter', 'os_oauth1', @@ -39,6 +41,7 @@ __all__ = ( __apis__ = ( discovery, credentials, + endpoints, limits, os_ep_filter, os_oauth1, diff --git a/keystone/api/endpoints.py b/keystone/api/endpoints.py new file mode 100644 index 0000000000..348fe82f18 --- /dev/null +++ b/keystone/api/endpoints.py @@ -0,0 +1,149 @@ +# 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. + +# This file handles all flask-restful resources for /v3/services + +import flask_restful +import functools +from six.moves import http_client + +from keystone.catalog import schema +from keystone.common import json_home +from keystone.common import provider_api +from keystone.common import rbac_enforcer +from keystone.common import utils +from keystone.common import validation +from keystone import exception +from keystone.server import flask as ks_flask + + +ENFORCER = rbac_enforcer.RBACEnforcer +PROVIDERS = provider_api.ProviderAPIs + +# TODO(morgan) move the partial functions to a common place so that they +# can be referenced by multiple APIs, such as endpoints and endpoint_policy. +_build_resource_relation = functools.partial( + json_home.build_v3_extension_resource_relation, + extension_name='OS-ENDPOINT-POLICY', extension_version='1.0') + + +def _filter_endpoint(ref): + ref.pop('legacy_endpoint_id', None) + ref['region'] = ref['region_id'] + return ref + + +class EndpointResource(ks_flask.ResourceBase): + collection_key = 'endpoints' + member_key = 'endpoint' + + def __init__(self): + super(EndpointResource, self).__init__() + self.get_member_from_driver = PROVIDERS.catalog_api.get_endpoint + + @staticmethod + def _validate_endpoint_region(endpoint): + """Ensure the region for the endpoint exists. + + If 'region_id' is used to specify the region, then we will let the + manager/driver take care of this. If, however, 'region' is used, + then for backward compatibility, we will auto-create the region. + + """ + if (endpoint.get('region_id') is None and + endpoint.get('region') is not None): + # To maintain backward compatibility with clients that are + # using the v3 API in the same way as they used the v2 API, + # create the endpoint region, if that region does not exist + # in keystone. + endpoint['region_id'] = endpoint.pop('region') + try: + PROVIDERS.catalog_api.get_region(endpoint['region_id']) + except exception.RegionNotFound: + region = dict(id=endpoint['region_id']) + PROVIDERS.catalog_api.create_region( + region, initiator=ks_flask.build_audit_initiator()) + return endpoint + + def _get_endpoint(self, endpoint_id): + ENFORCER.enforce_call(action='identity:get_endpoint') + return self.wrap_member(_filter_endpoint( + PROVIDERS.catalog_api.get_endpoint(endpoint_id))) + + def _list_endpoints(self): + filters = ['interface', 'service_id', 'region_id'] + ENFORCER.enforce_call(action='identity:list_endpoints', + filters=filters) + hints = self.build_driver_hints(filters) + refs = PROVIDERS.catalog_api.list_endpoints(hints=hints) + return self.wrap_collection([_filter_endpoint(r) for r in refs], + hints=hints) + + def get(self, endpoint_id=None): + if endpoint_id is not None: + return self._get_endpoint(endpoint_id) + return self._list_endpoints() + + def post(self): + ENFORCER.enforce_call(action='identity:create_endpoint') + endpoint = self.request_body_json.get('endpoint') + validation.lazy_validate(schema.endpoint_create, endpoint) + utils.check_endpoint_url(endpoint['url']) + endpoint = self._assign_unique_id(self._normalize_dict(endpoint)) + endpoint = self._validate_endpoint_region(endpoint) + ref = PROVIDERS.catalog_api.create_endpoint( + endpoint['id'], endpoint, initiator=self.audit_initiator) + return self.wrap_member(_filter_endpoint(ref)), http_client.CREATED + + def patch(self, endpoint_id): + ENFORCER.enforce_call(action='identity:update_endpoint') + endpoint = self.request_body_json.get('endpoint') + validation.lazy_validate(schema.endpoint_update, endpoint) + self._require_matching_id(endpoint) + endpoint = self._validate_endpoint_region(endpoint) + ref = PROVIDERS.catalog_api.update_endpoint( + endpoint_id, endpoint, initiator=self.audit_initiator) + return self.wrap_member(_filter_endpoint(ref)) + + def delete(self, endpoint_id): + ENFORCER.enforce_call(action='identity:delete_endpoint') + PROVIDERS.catalog_api.delete_endpoint(endpoint_id, + initiator=self.audit_initiator) + return None, http_client.NO_CONTENT + + +class EndpointPolicyEndpointResource(flask_restful.Resource): + def get(self, endpoint_id): + ENFORCER.enforce_call(action='identity:get_policy_for_endpoint') + PROVIDERS.catalog_api.get_endpoint(endpoint_id) + ref = PROVIDERS.endpoint_policy_api.get_policy_for_endpoint( + endpoint_id) + return ks_flask.ResourceBase.wrap_member( + ref, collection_name='endpoints', member_name='policy') + + +class EndpointAPI(ks_flask.APIBase): + _name = 'endpoints' + _import_name = __name__ + resources = [EndpointResource] + resource_mapping = [ + ks_flask.construct_resource_map( + resource=EndpointPolicyEndpointResource, + url='/endpoints//OS-ENDPOINT-POLICY/policy', + resource_kwargs={}, + rel='endpoint_policy', + resource_relation_func=_build_resource_relation, + path_vars={'endpoint_id': json_home.Parameters.ENDPOINT_ID}) + ] + + +APIs = (EndpointAPI,) diff --git a/keystone/api/os_ep_filter.py b/keystone/api/os_ep_filter.py index 072d030e8e..47ca2339fc 100644 --- a/keystone/api/os_ep_filter.py +++ b/keystone/api/os_ep_filter.py @@ -16,6 +16,7 @@ import flask_restful import functools from six.moves import http_client +from keystone.api import endpoints as _endpoints_api from keystone.catalog import schema from keystone.common import json_home from keystone.common import provider_api @@ -41,12 +42,9 @@ _ENDPOINT_GROUP_PARAMETER_RELATION = _build_parameter_relation( parameter_name='endpoint_group_id') -# TODO(morgan): move this to a common location once catalog endpoint API is -# converted to flask -def _filter_endpoint(ref): - ref.pop('legacy_endpoint_id', None) - ref['region'] = ref['region_id'] - return ref +# NOTE(morgan): This is shared from keystone.api.endpoint, this is a special +# case where cross-api code is used. This pattern should not be replicated. +_filter_endpoint = _endpoints_api._filter_endpoint class EndpointGroupsResource(ks_flask.ResourceBase): diff --git a/keystone/catalog/__init__.py b/keystone/catalog/__init__.py index 29f297d621..fc3e854fe8 100644 --- a/keystone/catalog/__init__.py +++ b/keystone/catalog/__init__.py @@ -12,5 +12,4 @@ # License for the specific language governing permissions and limitations # under the License. -from keystone.catalog import controllers # noqa from keystone.catalog.core import * # noqa diff --git a/keystone/catalog/controllers.py b/keystone/catalog/controllers.py deleted file mode 100644 index 7dbd7932b7..0000000000 --- a/keystone/catalog/controllers.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright 2012 OpenStack Foundation -# Copyright 2012 Canonical Ltd. -# -# 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. - -from keystone.catalog import schema -from keystone.common import controller -from keystone.common import provider_api -from keystone.common import utils -from keystone.common import validation -from keystone import exception - - -INTERFACES = ['public', 'internal', 'admin'] -PROVIDERS = provider_api.ProviderAPIs - - -class EndpointV3(controller.V3Controller): - collection_name = 'endpoints' - member_name = 'endpoint' - - def __init__(self): - super(EndpointV3, self).__init__() - self.get_member_from_driver = PROVIDERS.catalog_api.get_endpoint - - @classmethod - def filter_endpoint(cls, ref): - if 'legacy_endpoint_id' in ref: - ref.pop('legacy_endpoint_id') - ref['region'] = ref['region_id'] - return ref - - @classmethod - def wrap_member(cls, context, ref): - ref = cls.filter_endpoint(ref) - return super(EndpointV3, cls).wrap_member(context, ref) - - def _validate_endpoint_region(self, endpoint, request): - """Ensure the region for the endpoint exists. - - If 'region_id' is used to specify the region, then we will let the - manager/driver take care of this. If, however, 'region' is used, - then for backward compatibility, we will auto-create the region. - - """ - if (endpoint.get('region_id') is None and - endpoint.get('region') is not None): - # To maintain backward compatibility with clients that are - # using the v3 API in the same way as they used the v2 API, - # create the endpoint region, if that region does not exist - # in keystone. - endpoint['region_id'] = endpoint.pop('region') - try: - PROVIDERS.catalog_api.get_region(endpoint['region_id']) - except exception.RegionNotFound: - region = dict(id=endpoint['region_id']) - PROVIDERS.catalog_api.create_region( - region, initiator=request.audit_initiator - ) - - return endpoint - - @controller.protected() - def create_endpoint(self, request, endpoint): - validation.lazy_validate(schema.endpoint_create, endpoint) - utils.check_endpoint_url(endpoint['url']) - ref = self._assign_unique_id(self._normalize_dict(endpoint)) - ref = self._validate_endpoint_region(ref, request) - ref = PROVIDERS.catalog_api.create_endpoint( - ref['id'], ref, initiator=request.audit_initiator - ) - return EndpointV3.wrap_member(request.context_dict, ref) - - @controller.filterprotected('interface', 'service_id', 'region_id') - def list_endpoints(self, request, filters): - hints = EndpointV3.build_driver_hints(request, filters) - refs = PROVIDERS.catalog_api.list_endpoints(hints=hints) - return EndpointV3.wrap_collection(request.context_dict, - refs, - hints=hints) - - @controller.protected() - def get_endpoint(self, request, endpoint_id): - ref = PROVIDERS.catalog_api.get_endpoint(endpoint_id) - return EndpointV3.wrap_member(request.context_dict, ref) - - @controller.protected() - def update_endpoint(self, request, endpoint_id, endpoint): - validation.lazy_validate(schema.endpoint_update, endpoint) - self._require_matching_id(endpoint_id, endpoint) - - endpoint = self._validate_endpoint_region(endpoint.copy(), - request) - - ref = PROVIDERS.catalog_api.update_endpoint( - endpoint_id, endpoint, initiator=request.audit_initiator - ) - return EndpointV3.wrap_member(request.context_dict, ref) - - @controller.protected() - def delete_endpoint(self, request, endpoint_id): - return PROVIDERS.catalog_api.delete_endpoint( - endpoint_id, initiator=request.audit_initiator - ) diff --git a/keystone/catalog/routers.py b/keystone/catalog/routers.py deleted file mode 100644 index 7fde383c7e..0000000000 --- a/keystone/catalog/routers.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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 -# 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. - -from keystone.catalog import controllers -from keystone.common import router -from keystone.common import wsgi - - -class Routers(wsgi.RoutersBase): - """API for the keystone catalog.""" - - _path_prefixes = ('endpoints',) - - def append_v3_routers(self, mapper, routers): - routers.append(router.Router(controllers.EndpointV3(), - 'endpoints', 'endpoint', - resource_descriptions=self.v3_resources)) diff --git a/keystone/endpoint_policy/controllers.py b/keystone/endpoint_policy/controllers.py index 52c721ca9d..ca4df6b97b 100644 --- a/keystone/endpoint_policy/controllers.py +++ b/keystone/endpoint_policy/controllers.py @@ -135,19 +135,6 @@ class EndpointPolicyV3Controller(controller.V3Controller): PROVIDERS.endpoint_policy_api.delete_policy_association( policy_id, service_id=service_id, region_id=region_id) - @controller.protected() - def get_policy_for_endpoint(self, request, endpoint_id): - """Get the effective policy for an endpoint.""" - PROVIDERS.catalog_api.get_endpoint(endpoint_id) - ref = PROVIDERS.endpoint_policy_api.get_policy_for_endpoint( - endpoint_id - ) - # NOTE(henry-nash): since the collection and member for this class is - # set to endpoints, we have to handle wrapping this policy entity - # ourselves. - self._add_self_referential_link(request.context_dict, ref) - return {'policy': ref} - # NOTE(henry-nash): As in the catalog controller, we must ensure that the # legacy_endpoint_id does not escape. diff --git a/keystone/endpoint_policy/routers.py b/keystone/endpoint_policy/routers.py index 5f51d37f76..744cd7577d 100644 --- a/keystone/endpoint_policy/routers.py +++ b/keystone/endpoint_policy/routers.py @@ -33,12 +33,6 @@ class Routers(wsgi.RoutersBase): def append_v3_routers(self, mapper, routers): endpoint_policy_controller = controllers.EndpointPolicyV3Controller() - self._add_resource( - mapper, endpoint_policy_controller, - path='/endpoints/{endpoint_id}' + self.PATH_PREFIX + '/policy', - get_head_action='get_policy_for_endpoint', - rel=build_resource_relation(resource_name='endpoint_policy'), - path_vars={'endpoint_id': json_home.Parameters.ENDPOINT_ID}) self._add_resource( mapper, endpoint_policy_controller, path='/policies/{policy_id}' + self.PATH_PREFIX + '/endpoints', diff --git a/keystone/server/flask/application.py b/keystone/server/flask/application.py index 4d2c6dac33..4707b720a4 100644 --- a/keystone/server/flask/application.py +++ b/keystone/server/flask/application.py @@ -27,7 +27,6 @@ import keystone.api from keystone.application_credential import routers as app_cred_routers from keystone.assignment import routers as assignment_routers from keystone.auth import routers as auth_routers -from keystone.catalog import routers as catalog_routers from keystone.common import wsgi as keystone_wsgi from keystone.contrib.ec2 import routers as ec2_routers from keystone.contrib.s3 import routers as s3_routers @@ -42,6 +41,7 @@ from keystone.resource import routers as resource_routers # support is removed. _MOVED_API_PREFIXES = frozenset( ['credentials', + 'endpoints', 'OS-OAUTH1', 'OS-EP-FILTER', 'OS-REVOKE', @@ -59,7 +59,6 @@ LOG = log.getLogger(__name__) ALL_API_ROUTERS = [auth_routers, assignment_routers, - catalog_routers, identity_routers, app_cred_routers, policy_routers,