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 <victor@lsd.ufcg.edu.br>

Implements: bp hierarchical-multitenancy
Change-Id: I4eac4f5bc067634cc38c305dacc59ab1da63c153
This commit is contained in:
Rodrigo Duarte
2014-09-22 14:04:01 +00:00
committed by Rodrigo Duarte Sousa
parent a8c44074f9
commit 2ed0e22049
4 changed files with 129 additions and 2 deletions

View File

@@ -25,6 +25,12 @@ Create new project
.. versionadded:: 3 .. versionadded:: 3
.. option:: --parent <project>
Parent of the project (name or ID)
.. versionadded:: 3
.. option:: --description <description> .. option:: --description <description>
Project description Project description

View File

@@ -46,6 +46,11 @@ class CreateProject(show.ShowOne):
metavar='<domain>', metavar='<domain>',
help='Domain owning the project (name or ID)', help='Domain owning the project (name or ID)',
) )
parser.add_argument(
'--parent',
metavar='<project>',
help='Parent of the project (name or ID)',
)
parser.add_argument( parser.add_argument(
'--description', '--description',
metavar='<description>', metavar='<description>',
@@ -86,6 +91,13 @@ class CreateProject(show.ShowOne):
else: else:
domain = None domain = None
parent = None
if parsed_args.parent:
parent = utils.find_resource(
identity_client.projects,
parsed_args.parent,
).id
enabled = True enabled = True
if parsed_args.disable: if parsed_args.disable:
enabled = False enabled = False
@@ -97,6 +109,7 @@ class CreateProject(show.ShowOne):
project = identity_client.projects.create( project = identity_client.projects.create(
name=parsed_args.name, name=parsed_args.name,
domain=domain, domain=domain,
parent=parent,
description=parsed_args.description, description=parsed_args.description,
enabled=enabled, enabled=enabled,
**kwargs **kwargs
@@ -111,8 +124,6 @@ class CreateProject(show.ShowOne):
raise e raise e
project._info.pop('links') 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))) return zip(*sorted(six.iteritems(project._info)))

View File

@@ -135,6 +135,16 @@ REGION = {
'links': base_url + 'regions/' + region_id, '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_id = 'r1'
role_name = 'roller' role_name = 'roller'

View File

@@ -16,6 +16,7 @@
import copy import copy
import mock import mock
from openstackclient.common import exceptions
from openstackclient.identity.v3 import project from openstackclient.identity.v3 import project
from openstackclient.tests import fakes from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -60,6 +61,7 @@ class TestProjectCreate(TestProject):
identity_fakes.project_name, identity_fakes.project_name,
] ]
verifylist = [ verifylist = [
('parent', None),
('enable', False), ('enable', False),
('disable', False), ('disable', False),
('name', identity_fakes.project_name), ('name', identity_fakes.project_name),
@@ -75,6 +77,7 @@ class TestProjectCreate(TestProject):
'domain': None, 'domain': None,
'description': None, 'description': None,
'enabled': True, 'enabled': True,
'parent': None,
} }
# ProjectManager.create(name=, domain=, description=, # ProjectManager.create(name=, domain=, description=,
# enabled=, **kwargs) # enabled=, **kwargs)
@@ -103,6 +106,7 @@ class TestProjectCreate(TestProject):
('enable', False), ('enable', False),
('disable', False), ('disable', False),
('name', identity_fakes.project_name), ('name', identity_fakes.project_name),
('parent', None),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -115,6 +119,7 @@ class TestProjectCreate(TestProject):
'domain': None, 'domain': None,
'description': 'new desc', 'description': 'new desc',
'enabled': True, 'enabled': True,
'parent': None,
} }
# ProjectManager.create(name=, domain=, description=, # ProjectManager.create(name=, domain=, description=,
# enabled=, **kwargs) # enabled=, **kwargs)
@@ -143,6 +148,7 @@ class TestProjectCreate(TestProject):
('enable', False), ('enable', False),
('disable', False), ('disable', False),
('name', identity_fakes.project_name), ('name', identity_fakes.project_name),
('parent', None),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -155,6 +161,7 @@ class TestProjectCreate(TestProject):
'domain': identity_fakes.domain_id, 'domain': identity_fakes.domain_id,
'description': None, 'description': None,
'enabled': True, 'enabled': True,
'parent': None,
} }
# ProjectManager.create(name=, domain=, description=, # ProjectManager.create(name=, domain=, description=,
# enabled=, **kwargs) # enabled=, **kwargs)
@@ -183,6 +190,7 @@ class TestProjectCreate(TestProject):
('enable', False), ('enable', False),
('disable', False), ('disable', False),
('name', identity_fakes.project_name), ('name', identity_fakes.project_name),
('parent', None),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
mocker = mock.Mock() mocker = mock.Mock()
@@ -197,6 +205,7 @@ class TestProjectCreate(TestProject):
'domain': identity_fakes.domain_id, 'domain': identity_fakes.domain_id,
'description': None, 'description': None,
'enabled': True, 'enabled': True,
'parent': None,
} }
self.projects_mock.create.assert_called_with( self.projects_mock.create.assert_called_with(
**kwargs **kwargs
@@ -221,6 +230,7 @@ class TestProjectCreate(TestProject):
('enable', True), ('enable', True),
('disable', False), ('disable', False),
('name', identity_fakes.project_name), ('name', identity_fakes.project_name),
('parent', None),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -233,6 +243,7 @@ class TestProjectCreate(TestProject):
'domain': None, 'domain': None,
'description': None, 'description': None,
'enabled': True, 'enabled': True,
'parent': None,
} }
# ProjectManager.create(name=, domain=, description=, # ProjectManager.create(name=, domain=, description=,
# enabled=, **kwargs) # enabled=, **kwargs)
@@ -260,6 +271,7 @@ class TestProjectCreate(TestProject):
('enable', False), ('enable', False),
('disable', True), ('disable', True),
('name', identity_fakes.project_name), ('name', identity_fakes.project_name),
('parent', None),
] ]
parsed_args = self.check_parser(self.cmd, arglist, verifylist) parsed_args = self.check_parser(self.cmd, arglist, verifylist)
@@ -272,6 +284,7 @@ class TestProjectCreate(TestProject):
'domain': None, 'domain': None,
'description': None, 'description': None,
'enabled': False, 'enabled': False,
'parent': None,
} }
# ProjectManager.create(name=, domain=, # ProjectManager.create(name=, domain=,
# description=, enabled=, **kwargs) # description=, enabled=, **kwargs)
@@ -311,6 +324,7 @@ class TestProjectCreate(TestProject):
'domain': None, 'domain': None,
'description': None, 'description': None,
'enabled': True, 'enabled': True,
'parent': None,
'fee': 'fi', 'fee': 'fi',
'fo': 'fum', 'fo': 'fum',
} }
@@ -331,6 +345,92 @@ class TestProjectCreate(TestProject):
) )
self.assertEqual(datalist, data) 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): class TestProjectDelete(TestProject):