From 2ed0e220490da5c6ee8f34754d70540890b17921 Mon Sep 17 00:00:00 2001 From: Rodrigo Duarte Date: Mon, 22 Sep 2014 14:04:01 +0000 Subject: [PATCH] Add parent field to project creation Adding the possibility to create projects hierarchies by adding the parent field in the create project call. Co-Authored-By: Victor Silva Implements: bp hierarchical-multitenancy Change-Id: I4eac4f5bc067634cc38c305dacc59ab1da63c153 --- doc/source/command-objects/project.rst | 6 ++ openstackclient/identity/v3/project.py | 15 ++- openstackclient/tests/identity/v3/fakes.py | 10 ++ .../tests/identity/v3/test_project.py | 100 ++++++++++++++++++ 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/doc/source/command-objects/project.rst b/doc/source/command-objects/project.rst index 422e239c72..b8607f0b62 100644 --- a/doc/source/command-objects/project.rst +++ b/doc/source/command-objects/project.rst @@ -25,6 +25,12 @@ Create new project .. versionadded:: 3 +.. option:: --parent + + Parent of the project (name or ID) + + .. versionadded:: 3 + .. option:: --description Project description diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 1c93ad5d6c..49979763c0 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -46,6 +46,11 @@ class CreateProject(show.ShowOne): metavar='', help='Domain owning the project (name or ID)', ) + parser.add_argument( + '--parent', + metavar='', + help='Parent of the project (name or ID)', + ) parser.add_argument( '--description', metavar='', @@ -86,6 +91,13 @@ class CreateProject(show.ShowOne): else: domain = None + parent = None + if parsed_args.parent: + parent = utils.find_resource( + identity_client.projects, + parsed_args.parent, + ).id + enabled = True if parsed_args.disable: enabled = False @@ -97,6 +109,7 @@ class CreateProject(show.ShowOne): project = identity_client.projects.create( name=parsed_args.name, domain=domain, + parent=parent, description=parsed_args.description, enabled=enabled, **kwargs @@ -111,8 +124,6 @@ class CreateProject(show.ShowOne): raise e project._info.pop('links') - # TODO(stevemar): Remove the line below when we support multitenancy - project._info.pop('parent_id', None) return zip(*sorted(six.iteritems(project._info))) diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index c868401aa1..4056db46cf 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -135,6 +135,16 @@ REGION = { 'links': base_url + 'regions/' + region_id, } +PROJECT_WITH_PARENT = { + 'id': project_id + '-with-parent', + 'name': project_name + ' and their parents', + 'description': project_description + ' plus another four', + 'enabled': True, + 'domain_id': domain_id, + 'parent_id': project_id, + 'links': base_url + 'projects/' + (project_id + '-with-parent'), +} + role_id = 'r1' role_name = 'roller' diff --git a/openstackclient/tests/identity/v3/test_project.py b/openstackclient/tests/identity/v3/test_project.py index 874908daab..ec50da0c30 100644 --- a/openstackclient/tests/identity/v3/test_project.py +++ b/openstackclient/tests/identity/v3/test_project.py @@ -16,6 +16,7 @@ import copy import mock +from openstackclient.common import exceptions from openstackclient.identity.v3 import project from openstackclient.tests import fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes @@ -60,6 +61,7 @@ class TestProjectCreate(TestProject): identity_fakes.project_name, ] verifylist = [ + ('parent', None), ('enable', False), ('disable', False), ('name', identity_fakes.project_name), @@ -75,6 +77,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -103,6 +106,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -115,6 +119,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': 'new desc', 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -143,6 +148,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -155,6 +161,7 @@ class TestProjectCreate(TestProject): 'domain': identity_fakes.domain_id, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -183,6 +190,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) mocker = mock.Mock() @@ -197,6 +205,7 @@ class TestProjectCreate(TestProject): 'domain': identity_fakes.domain_id, 'description': None, 'enabled': True, + 'parent': None, } self.projects_mock.create.assert_called_with( **kwargs @@ -221,6 +230,7 @@ class TestProjectCreate(TestProject): ('enable', True), ('disable', False), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -233,6 +243,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, } # ProjectManager.create(name=, domain=, description=, # enabled=, **kwargs) @@ -260,6 +271,7 @@ class TestProjectCreate(TestProject): ('enable', False), ('disable', True), ('name', identity_fakes.project_name), + ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -272,6 +284,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': None, 'enabled': False, + 'parent': None, } # ProjectManager.create(name=, domain=, # description=, enabled=, **kwargs) @@ -311,6 +324,7 @@ class TestProjectCreate(TestProject): 'domain': None, 'description': None, 'enabled': True, + 'parent': None, 'fee': 'fi', 'fo': 'fum', } @@ -331,6 +345,92 @@ class TestProjectCreate(TestProject): ) self.assertEqual(datalist, data) + def test_project_create_parent(self): + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + self.projects_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT), + loaded=True, + ) + + arglist = [ + '--domain', identity_fakes.PROJECT_WITH_PARENT['domain_id'], + '--parent', identity_fakes.PROJECT['name'], + identity_fakes.PROJECT_WITH_PARENT['name'], + ] + verifylist = [ + ('domain', identity_fakes.PROJECT_WITH_PARENT['domain_id']), + ('parent', identity_fakes.PROJECT['name']), + ('enable', False), + ('disable', False), + ('name', identity_fakes.PROJECT_WITH_PARENT['name']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'name': identity_fakes.PROJECT_WITH_PARENT['name'], + 'domain': identity_fakes.PROJECT_WITH_PARENT['domain_id'], + 'parent': identity_fakes.PROJECT['id'], + 'description': None, + 'enabled': True, + } + + self.projects_mock.create.assert_called_with( + **kwargs + ) + + collist = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'name', + 'parent_id', + ) + self.assertEqual(columns, collist) + datalist = ( + identity_fakes.PROJECT_WITH_PARENT['description'], + identity_fakes.PROJECT_WITH_PARENT['domain_id'], + identity_fakes.PROJECT_WITH_PARENT['enabled'], + identity_fakes.PROJECT_WITH_PARENT['id'], + identity_fakes.PROJECT_WITH_PARENT['name'], + identity_fakes.PROJECT['id'], + ) + self.assertEqual(data, datalist) + + def test_project_create_invalid_parent(self): + self.projects_mock.resource_class.__name__ = 'Project' + self.projects_mock.get.side_effect = exceptions.NotFound( + 'Invalid parent') + self.projects_mock.find.side_effect = exceptions.NotFound( + 'Invalid parent') + + arglist = [ + '--domain', identity_fakes.domain_name, + '--parent', 'invalid', + identity_fakes.project_name, + ] + verifylist = [ + ('domain', identity_fakes.domain_name), + ('parent', 'invalid'), + ('enable', False), + ('disable', False), + ('name', identity_fakes.project_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + class TestProjectDelete(TestProject):