Fix projects list/search/get interface

Similar to users, projects was off in neverland. Bring it back in to the
fold so that the ansible modules can work sanely.

Change-Id: I4c3715f2de91db73b3f06c483de20f108a7d62fe
This commit is contained in:
Monty Taylor 2015-06-14 01:12:47 +03:00
parent 3d66d1f9fb
commit 8cd3b51728
5 changed files with 127 additions and 46 deletions

View File

@ -458,15 +458,6 @@ class OpenStackCloud(object):
# We don't need to track validity here, just get_token() each time.
return self.keystone_session.get_token()
@property
def project_cache(self):
return self.get_project_cache()
@_cache_on_arguments()
def get_project_cache(self):
return {project.id: project for project in
self._project_manager.list()}
@property
def _project_manager(self):
# Keystone v2 calls this attribute tenants
@ -479,11 +470,13 @@ class OpenStackCloud(object):
def _get_project_param_dict(self, name_or_id):
project_dict = dict()
if name_or_id:
project_id = self._get_project(name_or_id).id
project = self.get_project(name_or_id)
if not project:
return project_dict
if self.cloud_config.get_api_version('identity') == '3':
project_dict['default_project'] = project_id
project_dict['default_project'] = project['id']
else:
project_dict['tenant_id'] = project_id
project_dict['tenant_id'] = project['id']
return project_dict
def _get_domain_param_dict(self, domain_id):
@ -513,53 +506,102 @@ class OpenStackCloud(object):
ret.update(self._get_project_param_dict(project))
return ret
def _get_project(self, name_or_id):
"""Retrieve a project by name or id."""
@_cache_on_arguments()
def list_projects(self):
"""List Keystone Projects.
# TODO(mordred): This, and other keystone operations, need to have
# domain information passed in. When there is no
# available domain information, we should default to
# the currently scoped domain which we can request from
# the session.
for id, project in self.project_cache.items():
if name_or_id in (id, project.name):
return project
return None
:returns: a list of dicts containing the project description.
def get_project(self, name_or_id):
"""Retrieve a project by name or id."""
project = self._get_project(name_or_id)
if project:
return meta.obj_to_dict(project)
return None
:raises: ``OpenStackCloudException``: if something goes wrong during
the openstack API call.
"""
try:
projects = self.manager.submitTask(_tasks.ProjectList())
except Exception as e:
self.log.debug("Failed to list projects", exc_info=True)
raise OpenStackCloudException(str(e))
return meta.obj_list_to_dict(projects)
def search_projects(self, name_or_id=None, filters=None):
"""Seach Keystone projects.
:param name: project name or id.
:param filters: a dict containing additional filters to use.
:returns: a list of dict containing the role description
:raises: ``OpenStackCloudException``: if something goes wrong during
the openstack API call.
"""
projects = self.list_projects()
return _utils._filter_list(projects, name_or_id, filters)
def get_project(self, name_or_id, filters=None):
"""Get exactly one Keystone project.
:param id: project name or id.
:param filters: a dict containing additional filters to use.
:returns: a list of dicts containing the project description.
:raises: ``OpenStackCloudException``: if something goes wrong during
the openstack API call.
"""
return _utils._get_entity(self.search_projects, name_or_id, filters)
def update_project(self, name_or_id, description=None, enabled=True):
try:
project = self._get_project(name_or_id)
return meta.obj_to_dict(
project.update(description=description, enabled=enabled))
proj = self.get_project(name_or_id)
if not proj:
raise OpenStackCloudException(
"Project %s not found." % name_or_id)
params = {}
if self.api_versions['identity'] == '3':
params['project'] = proj['id']
else:
params['tenant_id'] = proj['id']
project = self.manager.submitTask(_tasks.ProjectUpdate(
description=description,
enabled=enabled,
**params))
except Exception as e:
raise OpenStackCloudException(
"Error in updating project {project}: {message}".format(
project=name_or_id, message=str(e)))
self.list_projects.invalidate()
return meta.obj_to_dict(project)
def create_project(
self, name, description=None, domain_id=None, enabled=True):
"""Create a project."""
try:
domain_params = self._get_domain_param_dict(domain_id)
self._project_manager.create(
params = self._get_domain_param_dict(domain)
if self.api_versions['identity'] == '3':
params['name'] = name
else:
params['tenant_name'] = name
project = self.manager.submitTask(_tasks.ProjectCreate(
project_name=name, description=description, enabled=enabled,
**domain_params)
**params))
except Exception as e:
raise OpenStackCloudException(
"Error in creating project {project}: {message}".format(
project=name, message=str(e)))
self.list_projects.invalidate()
return meta.obj_to_dict(project)
def delete_project(self, name_or_id):
try:
project = self.update_project(name_or_id, enabled=False)
self._project_manager.delete(project.id)
params = {}
if self.api_versions['identity'] == '3':
params['project'] = project['id']
else:
params['tenant'] = project['id']
self.manager.submitTask(_tasks.ProjectDelete(**params))
except Exception as e:
raise OpenStackCloudException(
"Error in deleting project {project}: {message}".format(

View File

@ -42,6 +42,26 @@ class UserGet(task_manager.Task):
return client.keystone_client.users.get(**self.args)
class ProjectList(task_manager.Task):
def main(self, client):
return client._project_manager.list()
class ProjectCreate(task_manager.Task):
def main(self, client):
return client._project_manager.create(**self.args)
class ProjectDelete(task_manager.Task):
def main(self, client):
return client._project_manager.delete(**self.args)
class ProjectUpdate(task_manager.Task):
def main(self, client):
return client._project_manager.update(**self.args)
class FlavorList(task_manager.Task):
def main(self, client):
return client.nova_client.flavors.list(**self.args)

View File

@ -110,6 +110,7 @@ class TestFlavor(base.TestCase):
# We need the tenant ID for the 'demo' user
project = self.operator_cloud.get_project('demo')
self.assertIsNotNone(project)
# Now give 'demo' access
self.operator_cloud.add_flavor_access(new_flavor['id'], project['id'])

View File

@ -69,18 +69,36 @@ class TestMemoryCache(base.TestCase):
self.assertIsInstance(self.cloud, shade.OpenStackCloud)
@mock.patch('shade.OpenStackCloud.keystone_client')
def test_project_cache(self, keystone_mock):
def test_list_projects_v3(self, keystone_mock):
project = fakes.FakeProject('project_a')
keystone_mock.projects.list.return_value = [project]
self.cloud.cloud_config.config['identity_api_version'] = '3'
self.assertEqual(
meta.obj_list_to_dict([project]), self.cloud.list_projects())
project_b = fakes.FakeProject('project_b')
keystone_mock.projects.list.return_value = [project, project_b]
self.assertEqual(
meta.obj_list_to_dict([project]), self.cloud.list_projects())
self.cloud.list_projects.invalidate(self.cloud)
self.assertEqual(
meta.obj_list_to_dict([project, project_b]),
self.cloud.list_projects())
@mock.patch('shade.OpenStackCloud.keystone_client')
def test_list_projects_v2(self, keystone_mock):
project = fakes.FakeProject('project_a')
keystone_mock.tenants.list.return_value = [project]
self.assertEqual({'project_a': project}, self.cloud.project_cache)
self.cloud.cloud_config.config['identity_api_version'] = '2'
self.assertEqual(
meta.obj_list_to_dict([project]), self.cloud.list_projects())
project_b = fakes.FakeProject('project_b')
keystone_mock.tenants.list.return_value = [project, project_b]
self.assertEqual(
{'project_a': project}, self.cloud.project_cache)
self.cloud.get_project_cache.invalidate(self.cloud)
meta.obj_list_to_dict([project]), self.cloud.list_projects())
self.cloud.list_projects.invalidate(self.cloud)
self.assertEqual(
{'project_a': project,
'project_b': project_b}, self.cloud.project_cache)
meta.obj_list_to_dict([project, project_b]),
self.cloud.list_projects())
@mock.patch('shade.OpenStackCloud.cinder_client')
def test_list_volumes(self, cinder_mock):

View File

@ -29,7 +29,7 @@ class TestDomainParams(base.TestCase):
self.cloud = shade.openstack_cloud(validate=False)
@mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version')
@mock.patch.object(shade.OpenStackCloud, '_get_project')
@mock.patch.object(shade.OpenStackCloud, 'get_project')
def test_identity_params_v3(self, mock_get_project, mock_api_version):
mock_get_project.return_value = munch.Munch(id=1234)
mock_api_version.return_value = '3'
@ -41,7 +41,7 @@ class TestDomainParams(base.TestCase):
self.assertEqual(ret['domain'], '5678')
@mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version')
@mock.patch.object(shade.OpenStackCloud, '_get_project')
@mock.patch.object(shade.OpenStackCloud, 'get_project')
def test_identity_params_v3_no_domain(
self, mock_get_project, mock_api_version):
mock_get_project.return_value = munch.Munch(id=1234)
@ -53,7 +53,7 @@ class TestDomainParams(base.TestCase):
domain_id=None, project='bar')
@mock.patch.object(occ.cloud_config.CloudConfig, 'get_api_version')
@mock.patch.object(shade.OpenStackCloud, '_get_project')
@mock.patch.object(shade.OpenStackCloud, 'get_project')
def test_identity_params_v2(self, mock_get_project, mock_api_version):
mock_get_project.return_value = munch.Munch(id=1234)
mock_api_version.return_value = '2'
@ -63,7 +63,7 @@ class TestDomainParams(base.TestCase):
self.assertEqual(ret['tenant_id'], 1234)
self.assertNotIn('domain', ret)
@mock.patch.object(shade.OpenStackCloud, '_get_project')
@mock.patch.object(shade.OpenStackCloud, 'get_project')
def test_identity_params_v2_no_domain(self, mock_get_project):
mock_get_project.return_value = munch.Munch(id=1234)