Add support for endpoing filter commands

Implements the commands that allow to link and endpoint to
a project for endpoint filter management.

Implements: blueprint keystone-endpoint-filter

Change-Id: Iecf61495664fb8413d35ef69f07ea929d190d002
This commit is contained in:
Jose Castro Leon 2017-10-25 15:39:44 +02:00
parent 4742d4df70
commit 12ee186108
8 changed files with 425 additions and 17 deletions

View File

@ -4,6 +4,34 @@ endpoint
Identity v2, v3
endpoint add project
--------------------
Associate a project to and endpoint for endpoint filtering
.. program:: endpoint add project
.. code:: bash
openstack endpoint add project
[--project-domain <project-domain>]
<endpoint>
<project>
.. option:: --project-domain <project-domain>
Domain the project belongs to (name or ID).
This can be used in case collisions between project names exist.
.. _endpoint_add_project-endpoint:
.. describe:: <endpoint>
Endpoint to associate with specified project (name or ID)
.. _endpoint_add_project-project:
.. describe:: <project>
Project to associate with specified endpoint (name or ID)
endpoint create
---------------
@ -107,6 +135,8 @@ List endpoints
[--interface <interface>]
[--region <region-id>]
[--long]
[--endpoint <endpoint> |
--project <project> [--project-domain <project-domain>]]
.. option:: --service <service>
@ -132,6 +162,55 @@ List endpoints
*Identity version 2 only*
.. option:: --endpoint
List projects that have access to that endpoint using
endpoint filtering
*Identity version 3 only*
.. option:: --project
List endpoints available for the project using
endpoint filtering
*Identity version 3 only*
.. option:: --project-domain
Domain the project belongs to (name or ID).
This can be used in case collisions between project names exist.
*Identity version 3 only*
endpoint remove project
-----------------------
Dissociate a project from an endpoint.
.. program:: endpoint remove project
.. code:: bash
openstack endpoint remove project
[--project-domain <project-domain>]
<endpoint>
<project>
.. option:: --project-domain <project-domain>
Domain the project belongs to (name or ID).
This can be used in case collisions between project names exist.
.. _endpoint_remove_project-endpoint:
.. describe:: <endpoint>
Endpoint to dissociate with specified project (name or ID)
.. _endpoint_remove_project-project:
.. describe:: <project>
Project to dissociate with specified endpoint (name or ID)
endpoint set
------------

View File

@ -36,6 +36,42 @@ def get_service_name(service):
return ''
class AddProjectToEndpoint(command.Command):
_description = _("Associate a project to an endpoint")
def get_parser(self, prog_name):
parser = super(
AddProjectToEndpoint, self).get_parser(prog_name)
parser.add_argument(
'endpoint',
metavar='<endpoint>',
help=_('Endpoint to associate with '
'specified project (name or ID)'),
)
parser.add_argument(
'project',
metavar='<project>',
help=_('Project to associate with '
'specified endpoint name or ID)'),
)
common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.identity
endpoint = utils.find_resource(client.endpoints,
parsed_args.endpoint)
project = common.find_project(client,
parsed_args.project,
parsed_args.project_domain)
client.endpoint_filter.add_endpoint_to_project(
project=project.id,
endpoint=endpoint.id)
class CreateEndpoint(command.ShowOne):
_description = _("Create new endpoint")
@ -152,27 +188,68 @@ class ListEndpoint(command.Lister):
metavar='<region-id>',
help=_('Filter by region ID'),
)
list_group = parser.add_mutually_exclusive_group()
list_group.add_argument(
'--endpoint',
metavar='<endpoint-group>',
help=_('Endpoint to list filters'),
)
list_group.add_argument(
'--project',
metavar='<project>',
help=_('Project to list filters (name or ID)'),
)
common.add_project_domain_option_to_parser(list_group)
return parser
def take_action(self, parsed_args):
identity_client = self.app.client_manager.identity
columns = ('ID', 'Region', 'Service Name', 'Service Type',
'Enabled', 'Interface', 'URL')
kwargs = {}
if parsed_args.service:
service = common.find_service(identity_client, parsed_args.service)
kwargs['service'] = service.id
if parsed_args.interface:
kwargs['interface'] = parsed_args.interface
if parsed_args.region:
kwargs['region'] = parsed_args.region
data = identity_client.endpoints.list(**kwargs)
service_list = identity_client.services.list()
for ep in data:
service = common.find_service_in_list(service_list, ep.service_id)
ep.service_name = get_service_name(service)
ep.service_type = service.type
endpoint = None
if parsed_args.endpoint:
endpoint = utils.find_resource(identity_client.endpoints,
parsed_args.endpoint)
project = None
if parsed_args.project:
project = common.find_project(identity_client,
parsed_args.project,
parsed_args.project_domain)
if endpoint:
columns = ('ID', 'Name')
data = (
identity_client.endpoint_filter
.list_projects_for_endpoint(endpoint=endpoint.id)
)
else:
columns = ('ID', 'Region', 'Service Name', 'Service Type',
'Enabled', 'Interface', 'URL')
kwargs = {}
if parsed_args.service:
service = common.find_service(identity_client,
parsed_args.service)
kwargs['service'] = service.id
if parsed_args.interface:
kwargs['interface'] = parsed_args.interface
if parsed_args.region:
kwargs['region'] = parsed_args.region
if project:
data = (
identity_client.endpoint_filter
.list_endpoints_for_project(project=project.id)
)
else:
data = identity_client.endpoints.list(**kwargs)
service_list = identity_client.services.list()
for ep in data:
service = common.find_service_in_list(service_list,
ep.service_id)
ep.service_name = get_service_name(service)
ep.service_type = service.type
return (columns,
(utils.get_item_properties(
s, columns,
@ -180,6 +257,42 @@ class ListEndpoint(command.Lister):
) for s in data))
class RemoveProjectFromEndpoint(command.Command):
_description = _("Dissociate a project from an endpoint")
def get_parser(self, prog_name):
parser = super(
RemoveProjectFromEndpoint, self).get_parser(prog_name)
parser.add_argument(
'endpoint',
metavar='<endpoint>',
help=_('Endpoint to dissociate from '
'specified project (name or ID)'),
)
parser.add_argument(
'project',
metavar='<project>',
help=_('Project to dissociate from '
'specified endpoint name or ID)'),
)
common.add_project_domain_option_to_parser(parser)
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.identity
endpoint = utils.find_resource(client.endpoints,
parsed_args.endpoint)
project = common.find_project(client,
parsed_args.project,
parsed_args.project_domain)
client.endpoint_filter.delete_endpoint_from_project(
project=project.id,
endpoint=endpoint.id)
class SetEndpoint(command.Command):
_description = _("Set endpoint properties")

View File

@ -42,6 +42,7 @@ class IdentityTests(base.TestCase):
REGION_LIST_HEADERS = ['Region', 'Parent Region', 'Description']
ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type',
'Enabled', 'Interface', 'URL']
ENDPOINT_LIST_PROJECT_HEADERS = ['ID', 'Name']
IDENTITY_PROVIDER_FIELDS = ['description', 'enabled', 'id', 'remote_ids',
'domain_id']

View File

@ -42,6 +42,29 @@ class EndpointTests(common.IdentityTests):
items = self.parse_listing(raw_output)
self.assert_table_structure(items, self.ENDPOINT_LIST_HEADERS)
def test_endpoint_list_filter(self):
endpoint_id = self._create_dummy_endpoint(add_clean_up=False)
project_id = self._create_dummy_project(add_clean_up=False)
raw_output = self.openstack(
'endpoint add project '
'%(endpoint_id)s '
'%(project_id)s' % {
'project_id': project_id,
'endpoint_id': endpoint_id})
self.assertEqual(0, len(raw_output))
raw_output = self.openstack(
'endpoint list --endpoint %s' % endpoint_id)
self.assertIn(project_id, raw_output)
items = self.parse_listing(raw_output)
self.assert_table_structure(items,
self.ENDPOINT_LIST_PROJECT_HEADERS)
raw_output = self.openstack(
'endpoint list --project %s' % project_id)
self.assertIn(endpoint_id, raw_output)
items = self.parse_listing(raw_output)
self.assert_table_structure(items, self.ENDPOINT_LIST_HEADERS)
def test_endpoint_set(self):
endpoint_id = self._create_dummy_endpoint()
new_endpoint_url = data_utils.rand_url()
@ -65,3 +88,22 @@ class EndpointTests(common.IdentityTests):
raw_output = self.openstack('endpoint show %s' % endpoint_id)
items = self.parse_show(raw_output)
self.assert_show_fields(items, self.ENDPOINT_FIELDS)
def test_endpoint_add_remove_project(self):
endpoint_id = self._create_dummy_endpoint(add_clean_up=False)
project_id = self._create_dummy_project(add_clean_up=False)
raw_output = self.openstack(
'endpoint add project '
'%(endpoint_id)s '
'%(project_id)s' % {
'project_id': project_id,
'endpoint_id': endpoint_id})
self.assertEqual(0, len(raw_output))
raw_output = self.openstack(
'endpoint remove project '
'%(endpoint_id)s '
'%(project_id)s' % {
'project_id': project_id,
'endpoint_id': endpoint_id})
self.assertEqual(0, len(raw_output))

View File

@ -493,6 +493,8 @@ class FakeIdentityv3Client(object):
self.credentials.resource_class = fakes.FakeResource(None, {})
self.endpoints = mock.Mock()
self.endpoints.resource_class = fakes.FakeResource(None, {})
self.endpoint_filter = mock.Mock()
self.endpoint_filter.resource_class = fakes.FakeResource(None, {})
self.groups = mock.Mock()
self.groups.resource_class = fakes.FakeResource(None, {})
self.oauth1 = mock.Mock()
@ -911,6 +913,31 @@ class FakeEndpoint(object):
loaded=True)
return endpoint
@staticmethod
def create_one_endpoint_filter(attrs=None):
"""Create a fake endpoint project relationship.
:param Dictionary attrs:
A dictionary with all attributes of endpoint filter
:return:
A FakeResource object with project, endpoint and so on
"""
attrs = attrs or {}
# Set default attribute
endpoint_filter_info = {
'project': 'project-id-' + uuid.uuid4().hex,
'endpoint': 'endpoint-id-' + uuid.uuid4().hex,
}
# Overwrite default attributes if there are some attributes set
endpoint_filter_info.update(attrs)
endpoint_filter = fakes.FakeModel(
copy.deepcopy(endpoint_filter_info))
return endpoint_filter
class FakeService(object):
"""Fake one or more service."""

View File

@ -22,11 +22,23 @@ class TestEndpoint(identity_fakes.TestIdentityv3):
# Get a shortcut to the EndpointManager Mock
self.endpoints_mock = self.app.client_manager.identity.endpoints
self.endpoints_mock.reset_mock()
self.ep_filter_mock = (
self.app.client_manager.identity.endpoint_filter
)
self.ep_filter_mock.reset_mock()
# Get a shortcut to the ServiceManager Mock
self.services_mock = self.app.client_manager.identity.services
self.services_mock.reset_mock()
# Get a shortcut to the DomainManager Mock
self.domains_mock = self.app.client_manager.identity.domains
self.domains_mock.reset_mock()
# Get a shortcut to the ProjectManager Mock
self.projects_mock = self.app.client_manager.identity.projects
self.projects_mock.reset_mock()
class TestEndpointCreate(TestEndpoint):
@ -750,3 +762,130 @@ class TestEndpointShowServiceWithoutName(TestEndpointShow):
# Get the command object to test
self.cmd = endpoint.ShowEndpoint(self.app, None)
class TestAddProjectToEndpoint(TestEndpoint):
project = identity_fakes.FakeProject.create_one_project()
domain = identity_fakes.FakeDomain.create_one_domain()
service = identity_fakes.FakeService.create_one_service()
endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
attrs={'service_id': service.id})
new_ep_filter = identity_fakes.FakeEndpoint.create_one_endpoint_filter(
attrs={'endpoint': endpoint.id,
'project': project.id}
)
def setUp(self):
super(TestAddProjectToEndpoint, self).setUp()
# This is the return value for utils.find_resource()
self.endpoints_mock.get.return_value = self.endpoint
# Update the image_id in the MEMBER dict
self.ep_filter_mock.create.return_value = self.new_ep_filter
self.projects_mock.get.return_value = self.project
self.domains_mock.get.return_value = self.domain
# Get the command object to test
self.cmd = endpoint.AddProjectToEndpoint(self.app, None)
def test_add_project_to_endpoint_no_option(self):
arglist = [
self.endpoint.id,
self.project.id,
]
verifylist = [
('endpoint', self.endpoint.id),
('project', self.project.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.ep_filter_mock.add_endpoint_to_project.assert_called_with(
project=self.project.id,
endpoint=self.endpoint.id
)
self.assertIsNone(result)
def test_add_project_to_endpoint_with_option(self):
arglist = [
self.endpoint.id,
self.project.id,
'--project-domain', self.domain.id,
]
verifylist = [
('endpoint', self.endpoint.id),
('project', self.project.id),
('project_domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.ep_filter_mock.add_endpoint_to_project.assert_called_with(
project=self.project.id,
endpoint=self.endpoint.id
)
self.assertIsNone(result)
class TestRemoveProjectEndpoint(TestEndpoint):
project = identity_fakes.FakeProject.create_one_project()
domain = identity_fakes.FakeDomain.create_one_domain()
service = identity_fakes.FakeService.create_one_service()
endpoint = identity_fakes.FakeEndpoint.create_one_endpoint(
attrs={'service_id': service.id})
def setUp(self):
super(TestRemoveProjectEndpoint, self).setUp()
# This is the return value for utils.find_resource()
self.endpoints_mock.get.return_value = self.endpoint
self.projects_mock.get.return_value = self.project
self.domains_mock.get.return_value = self.domain
self.ep_filter_mock.delete.return_value = None
# Get the command object to test
self.cmd = endpoint.RemoveProjectFromEndpoint(self.app, None)
def test_remove_project_endpoint_no_options(self):
arglist = [
self.endpoint.id,
self.project.id,
]
verifylist = [
('endpoint', self.endpoint.id),
('project', self.project.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.ep_filter_mock.delete_endpoint_from_project.assert_called_with(
project=self.project.id,
endpoint=self.endpoint.id,
)
self.assertIsNone(result)
def test_remove_project_endpoint_with_options(self):
arglist = [
self.endpoint.id,
self.project.id,
'--project-domain', self.domain.id,
]
verifylist = [
('endpoint', self.endpoint.id),
('project', self.project.id),
('project_domain', self.domain.id),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
result = self.cmd.take_action(parsed_args)
self.ep_filter_mock.delete_endpoint_from_project.assert_called_with(
project=self.project.id,
endpoint=self.endpoint.id,
)
self.assertIsNone(result)

View File

@ -0,0 +1,5 @@
---
features:
- |
Add ``endpoint add project``, ``endpoint remove project`` and ``endpoint
list`` commands to manage endpoint filters in identity v3.

View File

@ -227,11 +227,13 @@ openstack.identity.v3 =
ec2_credentials_list = openstackclient.identity.v3.ec2creds:ListEC2Creds
ec2_credentials_show = openstackclient.identity.v3.ec2creds:ShowEC2Creds
endpoint_add_project = openstackclient.identity.v3.endpoint:AddProjectToEndpoint
endpoint_create = openstackclient.identity.v3.endpoint:CreateEndpoint
endpoint_delete = openstackclient.identity.v3.endpoint:DeleteEndpoint
endpoint_list = openstackclient.identity.v3.endpoint:ListEndpoint
endpoint_remove_project = openstackclient.identity.v3.endpoint:RemoveProjectFromEndpoint
endpoint_set = openstackclient.identity.v3.endpoint:SetEndpoint
endpoint_show = openstackclient.identity.v3.endpoint:ShowEndpoint
endpoint_list = openstackclient.identity.v3.endpoint:ListEndpoint
group_add_user = openstackclient.identity.v3.group:AddUserToGroup
group_contains_user = openstackclient.identity.v3.group:CheckUserInGroup