Implement project tags logic into manager
This change adds the tags attribute of project into the resource manager. This change builds off of the backend logic. Change-Id: Ie988b7a2065f0ecdb8ec953a1d01f5e3cbd67a1b Partially-Implements: bp project-tags Co-Authored-By: Jaewoo Park <jp655p@att.com> Co-Authored-By: Nicolas Helgeson <nh202b@att.com> Depends-On: I00f094a5584be40ab477cbf680a5f6d1afb4d21b Depends-On: I5f8e4a53089b9fcc38084bb958d09f63ccc59d2a
This commit is contained in:
parent
6d320f75fd
commit
ee900029db
|
@ -397,6 +397,10 @@ class ProjectNotFound(NotFound):
|
|||
message_format = _("Could not find project: %(project_id)s.")
|
||||
|
||||
|
||||
class ProjectTagNotFound(NotFound):
|
||||
message_format = _("Could not find project tag: %(project_tag)s.")
|
||||
|
||||
|
||||
class TokenNotFound(NotFound):
|
||||
message_format = _("Could not find token: %(token_id)s.")
|
||||
|
||||
|
|
|
@ -34,6 +34,8 @@ CONF = keystone.conf.CONF
|
|||
LOG = log.getLogger(__name__)
|
||||
MEMOIZE = cache.get_memoization_decorator(group='resource')
|
||||
|
||||
TAG_SEARCH_FILTERS = ('tags', 'tags-any', 'not-tags', 'not-tags-any')
|
||||
|
||||
|
||||
@dependency.provider('resource_api')
|
||||
@dependency.requires('assignment_api', 'credential_api', 'domain_config_api',
|
||||
|
@ -50,6 +52,7 @@ class Manager(manager.Manager):
|
|||
|
||||
_DOMAIN = 'domain'
|
||||
_PROJECT = 'project'
|
||||
_PROJECT_TAG = 'project tag'
|
||||
|
||||
def __init__(self):
|
||||
# NOTE(morgan): The resource driver must be SQL. This is because there
|
||||
|
@ -819,6 +822,18 @@ class Manager(manager.Manager):
|
|||
|
||||
@manager.response_truncated
|
||||
def list_projects(self, hints=None):
|
||||
if hints:
|
||||
tag_filters = {}
|
||||
# Handle project tag filters separately
|
||||
for f in list(hints.filters):
|
||||
if f['name'] in TAG_SEARCH_FILTERS:
|
||||
tag_filters[f['name']] = f['value']
|
||||
hints.filters.remove(f)
|
||||
if tag_filters:
|
||||
tag_refs = self.driver.list_projects_by_tags(tag_filters)
|
||||
project_refs = self.driver.list_projects(hints)
|
||||
ref_ids = [ref['id'] for ref in tag_refs]
|
||||
return [ref for ref in project_refs if ref['id'] in ref_ids]
|
||||
return self.driver.list_projects(hints or driver_hints.Hints())
|
||||
|
||||
# NOTE(henry-nash): list_projects_in_domain is actually an internal method
|
||||
|
@ -881,6 +896,78 @@ class Manager(manager.Manager):
|
|||
if new_ref['domain_id'] != orig_ref['domain_id']:
|
||||
raise exception.ValidationError(_('Cannot change Domain ID'))
|
||||
|
||||
def create_project_tag(self, project_id, tag, initiator=None):
|
||||
"""Create a new tag on project.
|
||||
|
||||
:param project_id: ID of a project to create a tag for
|
||||
:param tag: The string value of a tag to add
|
||||
|
||||
:returns: The value of the created tag
|
||||
"""
|
||||
project = self.driver.get_project(project_id)
|
||||
tag_name = tag.strip()
|
||||
project['tags'].append(tag_name)
|
||||
self.update_project(project_id, {'tags': project['tags']})
|
||||
|
||||
notifications.Audit.created(
|
||||
self._PROJECT_TAG, tag_name, initiator)
|
||||
return tag_name
|
||||
|
||||
def get_project_tag(self, project_id, tag_name):
|
||||
"""Return information for a single tag on a project.
|
||||
|
||||
:param project_id: ID of a project to retrive a tag from
|
||||
:param tag_name: Name of a tag to return
|
||||
|
||||
:raises keystone.exception.ProjectTagNotFound: If the tag name
|
||||
does not exist on the project
|
||||
:returns: The tag value
|
||||
"""
|
||||
project = self.driver.get_project(project_id)
|
||||
if tag_name not in project.get('tags'):
|
||||
raise exception.ProjectTagNotFound(project_tag=tag_name)
|
||||
return tag_name
|
||||
|
||||
def list_project_tags(self, project_id):
|
||||
"""List all tags on project.
|
||||
|
||||
:param project_id: The ID of a project
|
||||
|
||||
:returns: A list of tags from a project
|
||||
"""
|
||||
project = self.driver.get_project(project_id)
|
||||
return project.get('tags', [])
|
||||
|
||||
def update_project_tags(self, project_id, tags, initiator=None):
|
||||
"""Update all tags on a project.
|
||||
|
||||
:param project_id: The ID of the project to update
|
||||
:param tags: A list of tags to update on the project
|
||||
|
||||
:returns: A list of tags
|
||||
"""
|
||||
self.driver.get_project(project_id)
|
||||
tag_list = [t.strip() for t in tags]
|
||||
project = {'tags': tag_list}
|
||||
self.update_project(project_id, project)
|
||||
return tag_list
|
||||
|
||||
def delete_project_tag(self, project_id, tag):
|
||||
"""Delete single tag from project.
|
||||
|
||||
:param project_id: The ID of the project
|
||||
:param tag: The tag value to delete
|
||||
|
||||
:raises keystone.exception.ProjectTagNotFound: If the tag name
|
||||
does not exist on the project
|
||||
"""
|
||||
project = self.driver.get_project(project_id)
|
||||
try:
|
||||
project['tags'].remove(tag)
|
||||
except ValueError:
|
||||
raise exception.ProjectTagNotFound(project_tag=tag)
|
||||
self.update_project(project_id, project)
|
||||
notifications.Audit.deleted(self._PROJECT_TAG, tag)
|
||||
|
||||
MEMOIZE_CONFIG = cache.get_memoization_decorator(group='domain_config')
|
||||
|
||||
|
|
|
@ -1538,6 +1538,105 @@ class ResourceTests(object):
|
|||
user = self.identity_api.get_user(user['id'])
|
||||
self.assertNotIn('default_project_id', user)
|
||||
|
||||
def _create_project_and_tags(self, num_of_tags=1):
|
||||
"""Create a project and tags associated to that project.
|
||||
|
||||
:param num_of_tags: the desired number of tags attached to a
|
||||
project, default is 1.
|
||||
|
||||
:returns: A tuple of a new project and a list of random tags
|
||||
"""
|
||||
tags = [uuid.uuid4().hex for i in range(num_of_tags)]
|
||||
ref = unit.new_project_ref(
|
||||
domain_id=CONF.identity.default_domain_id, tags=tags)
|
||||
project = self.resource_api.create_project(ref['id'], ref)
|
||||
|
||||
return project, tags
|
||||
|
||||
def test_create_project_with_tags(self):
|
||||
project, tags = self._create_project_and_tags(num_of_tags=5)
|
||||
tag_ref = self.resource_api.get_project_tag(project['id'], tags[0])
|
||||
self.assertEqual(tags[0], tag_ref)
|
||||
|
||||
def test_get_project_contains_tags(self):
|
||||
project, _ = self._create_project_and_tags()
|
||||
tag = uuid.uuid4().hex
|
||||
self.resource_api.create_project_tag(project['id'], tag)
|
||||
ref = self.resource_api.get_project(project['id'])
|
||||
self.assertIn(tag, ref['tags'])
|
||||
|
||||
def test_list_project_tags(self):
|
||||
project, tags = self._create_project_and_tags(num_of_tags=1)
|
||||
tag_ref = self.resource_api.list_project_tags(project['id'])
|
||||
self.assertEqual(tags[0], tag_ref[0])
|
||||
|
||||
def test_list_project_tags_returns_not_found(self):
|
||||
self.assertRaises(exception.ProjectNotFound,
|
||||
self.resource_api.list_project_tags,
|
||||
uuid.uuid4().hex)
|
||||
|
||||
def test_get_project_tag(self):
|
||||
project, tags = self._create_project_and_tags()
|
||||
tag_ref = self.resource_api.get_project_tag(project['id'], tags[0])
|
||||
self.assertEqual(tags[0], tag_ref)
|
||||
|
||||
def test_create_project_tag_with_trailing_whitespace(self):
|
||||
project, _ = self._create_project_and_tags()
|
||||
tag = uuid.uuid4().hex + ' '
|
||||
tag_ref = self.resource_api.create_project_tag(project['id'], tag)
|
||||
self.assertEqual(tag.strip(), tag_ref)
|
||||
|
||||
def test_create_project_tag_is_case_sensitive(self):
|
||||
project, _ = self._create_project_and_tags()
|
||||
new_tags = ['aaa', 'AAA']
|
||||
|
||||
ref = self.resource_api.update_project_tags(project['id'], new_tags)
|
||||
for tag in new_tags:
|
||||
self.assertIn(tag, ref)
|
||||
|
||||
def test_update_project_tags(self):
|
||||
project, tags = self._create_project_and_tags(num_of_tags=2)
|
||||
project_tag_ref = self.resource_api.list_project_tags(project['id'])
|
||||
self.assertEqual(len(project_tag_ref), 2)
|
||||
|
||||
# Update project to only have one tag
|
||||
tags = ['one']
|
||||
self.resource_api.update_project_tags(project['id'], tags)
|
||||
project_tag_ref = self.resource_api.list_project_tags(project['id'])
|
||||
self.assertEqual(len(project_tag_ref), 1)
|
||||
|
||||
def test_update_project_tags_returns_not_found(self):
|
||||
_, tags = self._create_project_and_tags(num_of_tags=2)
|
||||
self.assertRaises(exception.ProjectNotFound,
|
||||
self.resource_api.update_project_tags,
|
||||
uuid.uuid4().hex,
|
||||
tags)
|
||||
|
||||
def test_delete_tag_from_project(self):
|
||||
project, tags = self._create_project_and_tags(num_of_tags=2)
|
||||
tag_to_delete = tags[-1]
|
||||
self.resource_api.delete_project_tag(project['id'], tag_to_delete)
|
||||
project_tag_ref = self.resource_api.list_project_tags(
|
||||
project['id'])
|
||||
self.assertEqual(len(project_tag_ref), 1)
|
||||
self.assertEqual(project_tag_ref[0], tags[0])
|
||||
|
||||
def test_delete_project_tag_returns_not_found(self):
|
||||
self.assertRaises(exception.ProjectNotFound,
|
||||
self.resource_api.delete_project_tag,
|
||||
uuid.uuid4().hex,
|
||||
uuid.uuid4().hex)
|
||||
|
||||
def test_delete_project_tags(self):
|
||||
project, tags = self._create_project_and_tags(num_of_tags=5)
|
||||
project_tag_ref = self.resource_api.list_project_tags(
|
||||
project['id'])
|
||||
self.assertEqual(len(project_tag_ref), 5)
|
||||
|
||||
self.resource_api.update_project_tags(project['id'], [])
|
||||
project_tag_ref = self.resource_api.list_project_tags(project['id'])
|
||||
self.assertEqual(project_tag_ref, [])
|
||||
|
||||
|
||||
class ResourceDriverTests(object):
|
||||
"""Test for the resource driver.
|
||||
|
|
Loading…
Reference in New Issue