Move endpoint policy into keystone core

Remove endpoint_policy as an extension and move it to a core resource.
For now we leave the database migrations in the extension directory
until we have a general policy for merging these into core.

DocImpact: You should no longer run the migrations for this extension.
Implements: bp replace-extensions
Change-Id: I6215b7df497c142a5e73b62543e0d76458c85f64
This commit is contained in:
Jamie Lennox 2015-04-13 12:59:40 +10:00 committed by Dave Chen
parent f5085b2b14
commit 696c1569e8
13 changed files with 283 additions and 209 deletions

View File

@ -15,11 +15,11 @@ from keystone import auth
from keystone import catalog from keystone import catalog
from keystone.common import cache from keystone.common import cache
from keystone.contrib import endpoint_filter from keystone.contrib import endpoint_filter
from keystone.contrib import endpoint_policy
from keystone.contrib import federation from keystone.contrib import federation
from keystone.contrib import oauth1 from keystone.contrib import oauth1
from keystone.contrib import revoke from keystone.contrib import revoke
from keystone import credential from keystone import credential
from keystone import endpoint_policy
from keystone import identity from keystone import identity
from keystone import policy from keystone import policy
from keystone import resource from keystone import resource

View File

@ -526,6 +526,9 @@ FILE_OPTIONS = {
'exists.'), 'exists.'),
], ],
'endpoint_policy': [ 'endpoint_policy': [
cfg.BoolOpt('enabled',
default=True,
help='Enable endpoint_policy functionality.'),
cfg.StrOpt('driver', cfg.StrOpt('driver',
default='sql', default='sql',
help='Endpoint policy backend driver'), help='Endpoint policy backend driver'),

View File

@ -1,15 +0,0 @@
# Copyright 2014 IBM Corp.
#
# 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.contrib.endpoint_policy.core import * # noqa

View File

@ -1,5 +1,3 @@
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
@ -12,129 +10,22 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import uuid import logging
import sqlalchemy from keystone.endpoint_policy.backends import sql
from keystone.openstack.common import versionutils
from keystone.common import sql LOG = logging.getLogger(__name__)
from keystone import exception
_OLD = 'keystone.contrib.endpoint_policy.backends.sql.EndpointPolicy'
_NEW = 'keystone.endpoint_policy.backends.sql.EndpointPolicy'
class PolicyAssociation(sql.ModelBase, sql.ModelDictMixin): class EndpointPolicy(sql.EndpointPolicy):
__tablename__ = 'policy_association'
attributes = ['policy_id', 'endpoint_id', 'region_id', 'service_id']
# The id column is never exposed outside this module. It only exists to
# provide a primary key, given that the real columns we would like to use
# (endpoint_id, service_id, region_id) can be null
id = sql.Column(sql.String(64), primary_key=True)
policy_id = sql.Column(sql.String(64), nullable=False)
endpoint_id = sql.Column(sql.String(64), nullable=True)
service_id = sql.Column(sql.String(64), nullable=True)
region_id = sql.Column(sql.String(64), nullable=True)
__table_args__ = (sql.UniqueConstraint('endpoint_id', 'service_id',
'region_id'), {})
def to_dict(self): @versionutils.deprecated(versionutils.deprecated.LIBERTY,
"""Returns the model's attributes as a dictionary. in_favor_of=_NEW,
remove_in=1,
We override the standard method in order to hide the id column, what=_OLD)
since this only exists to provide the table with a primary key. def __init__(self, *args, **kwargs):
super(EndpointPolicy, self).__init__(*args, **kwargs)
"""
d = {}
for attr in self.__class__.attributes:
d[attr] = getattr(self, attr)
return d
class EndpointPolicy(object):
def create_policy_association(self, policy_id, endpoint_id=None,
service_id=None, region_id=None):
with sql.transaction() as session:
try:
# See if there is already a row for this association, and if
# so, update it with the new policy_id
query = session.query(PolicyAssociation)
query = query.filter_by(endpoint_id=endpoint_id)
query = query.filter_by(service_id=service_id)
query = query.filter_by(region_id=region_id)
association = query.one()
association.policy_id = policy_id
except sql.NotFound:
association = PolicyAssociation(id=uuid.uuid4().hex,
policy_id=policy_id,
endpoint_id=endpoint_id,
service_id=service_id,
region_id=region_id)
session.add(association)
def check_policy_association(self, policy_id, endpoint_id=None,
service_id=None, region_id=None):
sql_constraints = sqlalchemy.and_(
PolicyAssociation.policy_id == policy_id,
PolicyAssociation.endpoint_id == endpoint_id,
PolicyAssociation.service_id == service_id,
PolicyAssociation.region_id == region_id)
# NOTE(henry-nash): Getting a single value to save object
# management overhead.
with sql.transaction() as session:
if session.query(PolicyAssociation.id).filter(
sql_constraints).distinct().count() == 0:
raise exception.PolicyAssociationNotFound()
def delete_policy_association(self, policy_id, endpoint_id=None,
service_id=None, region_id=None):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(policy_id=policy_id)
query = query.filter_by(endpoint_id=endpoint_id)
query = query.filter_by(service_id=service_id)
query = query.filter_by(region_id=region_id)
query.delete()
def get_policy_association(self, endpoint_id=None,
service_id=None, region_id=None):
sql_constraints = sqlalchemy.and_(
PolicyAssociation.endpoint_id == endpoint_id,
PolicyAssociation.service_id == service_id,
PolicyAssociation.region_id == region_id)
try:
with sql.transaction() as session:
policy_id = session.query(PolicyAssociation.policy_id).filter(
sql_constraints).distinct().one()
return {'policy_id': policy_id}
except sql.NotFound:
raise exception.PolicyAssociationNotFound()
def list_associations_for_policy(self, policy_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(policy_id=policy_id)
return [ref.to_dict() for ref in query.all()]
def delete_association_by_endpoint(self, endpoint_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(endpoint_id=endpoint_id)
query.delete()
def delete_association_by_service(self, service_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(service_id=service_id)
query.delete()
def delete_association_by_region(self, region_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(region_id=region_id)
query.delete()
def delete_association_by_policy(self, policy_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(policy_id=policy_id)
query.delete()

View File

@ -1,5 +1,3 @@
# Copyright 2014 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
@ -12,74 +10,22 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import functools import logging
from keystone.common import json_home
from keystone.common import wsgi from keystone.common import wsgi
from keystone.contrib.endpoint_policy import controllers from keystone.openstack.common import versionutils
LOG = logging.getLogger(__name__)
_OLD = 'keystone.contrib.endpoint_policy.routers.EndpointPolicyExtension'
_NEW = 'keystone.endpoint_policy.routers.Routers'
build_resource_relation = functools.partial( class EndpointPolicyExtension(wsgi.Middleware):
json_home.build_v3_extension_resource_relation,
extension_name='OS-ENDPOINT-POLICY', extension_version='1.0')
@versionutils.deprecated(versionutils.deprecated.LIBERTY,
class EndpointPolicyExtension(wsgi.V3ExtensionRouter): in_favor_of=_NEW,
remove_in=1,
PATH_PREFIX = '/OS-ENDPOINT-POLICY' what=_OLD)
def __init__(self, *args, **kwargs):
def add_routes(self, mapper): super(EndpointPolicyExtension, self).__init__(*args, **kwargs)
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',
get_action='list_endpoints_for_policy',
rel=build_resource_relation(resource_name='policy_endpoints'),
path_vars={'policy_id': json_home.Parameters.POLICY_ID})
self._add_resource(
mapper, endpoint_policy_controller,
path=('/policies/{policy_id}' + self.PATH_PREFIX +
'/endpoints/{endpoint_id}'),
get_head_action='check_policy_association_for_endpoint',
put_action='create_policy_association_for_endpoint',
delete_action='delete_policy_association_for_endpoint',
rel=build_resource_relation(
resource_name='endpoint_policy_association'),
path_vars={
'policy_id': json_home.Parameters.POLICY_ID,
'endpoint_id': json_home.Parameters.ENDPOINT_ID,
})
self._add_resource(
mapper, endpoint_policy_controller,
path=('/policies/{policy_id}' + self.PATH_PREFIX +
'/services/{service_id}'),
get_head_action='check_policy_association_for_service',
put_action='create_policy_association_for_service',
delete_action='delete_policy_association_for_service',
rel=build_resource_relation(
resource_name='service_policy_association'),
path_vars={
'policy_id': json_home.Parameters.POLICY_ID,
'service_id': json_home.Parameters.SERVICE_ID,
})
self._add_resource(
mapper, endpoint_policy_controller,
path=('/policies/{policy_id}' + self.PATH_PREFIX +
'/services/{service_id}/regions/{region_id}'),
get_head_action='check_policy_association_for_region_and_service',
put_action='create_policy_association_for_region_and_service',
delete_action='delete_policy_association_for_region_and_service',
rel=build_resource_relation(
resource_name='region_and_service_policy_association'),
path_vars={
'policy_id': json_home.Parameters.POLICY_ID,
'service_id': json_home.Parameters.SERVICE_ID,
'region_id': json_home.Parameters.REGION_ID,
})

View File

@ -0,0 +1,14 @@
# 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.endpoint_policy.core import * # noqa
from keystone.endpoint_policy import routers # noqa

View File

@ -0,0 +1,140 @@
# Copyright 2014 IBM Corp.
#
# 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 uuid
import sqlalchemy
from keystone.common import sql
from keystone import exception
class PolicyAssociation(sql.ModelBase, sql.ModelDictMixin):
__tablename__ = 'policy_association'
attributes = ['policy_id', 'endpoint_id', 'region_id', 'service_id']
# The id column is never exposed outside this module. It only exists to
# provide a primary key, given that the real columns we would like to use
# (endpoint_id, service_id, region_id) can be null
id = sql.Column(sql.String(64), primary_key=True)
policy_id = sql.Column(sql.String(64), nullable=False)
endpoint_id = sql.Column(sql.String(64), nullable=True)
service_id = sql.Column(sql.String(64), nullable=True)
region_id = sql.Column(sql.String(64), nullable=True)
__table_args__ = (sql.UniqueConstraint('endpoint_id', 'service_id',
'region_id'), {})
def to_dict(self):
"""Returns the model's attributes as a dictionary.
We override the standard method in order to hide the id column,
since this only exists to provide the table with a primary key.
"""
d = {}
for attr in self.__class__.attributes:
d[attr] = getattr(self, attr)
return d
class EndpointPolicy(object):
def create_policy_association(self, policy_id, endpoint_id=None,
service_id=None, region_id=None):
with sql.transaction() as session:
try:
# See if there is already a row for this association, and if
# so, update it with the new policy_id
query = session.query(PolicyAssociation)
query = query.filter_by(endpoint_id=endpoint_id)
query = query.filter_by(service_id=service_id)
query = query.filter_by(region_id=region_id)
association = query.one()
association.policy_id = policy_id
except sql.NotFound:
association = PolicyAssociation(id=uuid.uuid4().hex,
policy_id=policy_id,
endpoint_id=endpoint_id,
service_id=service_id,
region_id=region_id)
session.add(association)
def check_policy_association(self, policy_id, endpoint_id=None,
service_id=None, region_id=None):
sql_constraints = sqlalchemy.and_(
PolicyAssociation.policy_id == policy_id,
PolicyAssociation.endpoint_id == endpoint_id,
PolicyAssociation.service_id == service_id,
PolicyAssociation.region_id == region_id)
# NOTE(henry-nash): Getting a single value to save object
# management overhead.
with sql.transaction() as session:
if session.query(PolicyAssociation.id).filter(
sql_constraints).distinct().count() == 0:
raise exception.PolicyAssociationNotFound()
def delete_policy_association(self, policy_id, endpoint_id=None,
service_id=None, region_id=None):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(policy_id=policy_id)
query = query.filter_by(endpoint_id=endpoint_id)
query = query.filter_by(service_id=service_id)
query = query.filter_by(region_id=region_id)
query.delete()
def get_policy_association(self, endpoint_id=None,
service_id=None, region_id=None):
sql_constraints = sqlalchemy.and_(
PolicyAssociation.endpoint_id == endpoint_id,
PolicyAssociation.service_id == service_id,
PolicyAssociation.region_id == region_id)
try:
with sql.transaction() as session:
policy_id = session.query(PolicyAssociation.policy_id).filter(
sql_constraints).distinct().one()
return {'policy_id': policy_id}
except sql.NotFound:
raise exception.PolicyAssociationNotFound()
def list_associations_for_policy(self, policy_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(policy_id=policy_id)
return [ref.to_dict() for ref in query.all()]
def delete_association_by_endpoint(self, endpoint_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(endpoint_id=endpoint_id)
query.delete()
def delete_association_by_service(self, service_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(service_id=service_id)
query.delete()
def delete_association_by_region(self, region_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(region_id=region_id)
query.delete()
def delete_association_by_policy(self, policy_id):
with sql.transaction() as session:
query = session.query(PolicyAssociation)
query = query.filter_by(policy_id=policy_id)
query.delete()

View File

@ -0,0 +1,85 @@
# Copyright 2014 IBM Corp.
#
# 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 functools
from keystone.common import json_home
from keystone.common import wsgi
from keystone.endpoint_policy import controllers
build_resource_relation = functools.partial(
json_home.build_v3_extension_resource_relation,
extension_name='OS-ENDPOINT-POLICY', extension_version='1.0')
class Routers(wsgi.RoutersBase):
PATH_PREFIX = '/OS-ENDPOINT-POLICY'
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',
get_action='list_endpoints_for_policy',
rel=build_resource_relation(resource_name='policy_endpoints'),
path_vars={'policy_id': json_home.Parameters.POLICY_ID})
self._add_resource(
mapper, endpoint_policy_controller,
path=('/policies/{policy_id}' + self.PATH_PREFIX +
'/endpoints/{endpoint_id}'),
get_head_action='check_policy_association_for_endpoint',
put_action='create_policy_association_for_endpoint',
delete_action='delete_policy_association_for_endpoint',
rel=build_resource_relation(
resource_name='endpoint_policy_association'),
path_vars={
'policy_id': json_home.Parameters.POLICY_ID,
'endpoint_id': json_home.Parameters.ENDPOINT_ID,
})
self._add_resource(
mapper, endpoint_policy_controller,
path=('/policies/{policy_id}' + self.PATH_PREFIX +
'/services/{service_id}'),
get_head_action='check_policy_association_for_service',
put_action='create_policy_association_for_service',
delete_action='delete_policy_association_for_service',
rel=build_resource_relation(
resource_name='service_policy_association'),
path_vars={
'policy_id': json_home.Parameters.POLICY_ID,
'service_id': json_home.Parameters.SERVICE_ID,
})
self._add_resource(
mapper, endpoint_policy_controller,
path=('/policies/{policy_id}' + self.PATH_PREFIX +
'/services/{service_id}/regions/{region_id}'),
get_head_action='check_policy_association_for_region_and_service',
put_action='create_policy_association_for_region_and_service',
delete_action='delete_policy_association_for_region_and_service',
rel=build_resource_relation(
resource_name='region_and_service_policy_association'),
path_vars={
'policy_id': json_home.Parameters.POLICY_ID,
'service_id': json_home.Parameters.SERVICE_ID,
'region_id': json_home.Parameters.REGION_ID,
})

View File

@ -26,6 +26,7 @@ from keystone import catalog
from keystone.common import wsgi from keystone.common import wsgi
from keystone import controllers from keystone import controllers
from keystone import credential from keystone import credential
from keystone import endpoint_policy
from keystone import identity from keystone import identity
from keystone import policy from keystone import policy
from keystone import resource from keystone import resource
@ -103,11 +104,20 @@ def v3_app_factory(global_conf, **local_conf):
sub_routers = [] sub_routers = []
_routers = [] _routers = []
router_modules = [assignment, auth, catalog, credential, identity, policy, router_modules = [assignment,
auth,
catalog,
credential,
identity,
policy,
resource] resource]
if CONF.trust.enabled: if CONF.trust.enabled:
router_modules.append(trust) router_modules.append(trust)
if CONF.endpoint_policy.enabled:
router_modules.append(endpoint_policy)
for module in router_modules: for module in router_modules:
routers_instance = module.routers.Routers() routers_instance = module.routers.Routers()
_routers.append(routers_instance) _routers.append(routers_instance)

View File

@ -120,7 +120,7 @@ keystone.endpoint_filter =
sql = keystone.contrib.endpoint_filter.backends.sql:EndpointFilter sql = keystone.contrib.endpoint_filter.backends.sql:EndpointFilter
keystone.endpoint_policy = keystone.endpoint_policy =
sql = keystone.contrib.endpoint_policy.backends.sql:EndpointPolicy sql = keystone.endpoint_policy.backends.sql:EndpointPolicy
keystone.federation = keystone.federation =
sql = keystone.contrib.federation.backends.sql:Federation sql = keystone.contrib.federation.backends.sql:Federation