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
.. option:: --parent <project>
Parent of the project (name or ID)
.. versionadded:: 3
.. option:: --description <description>
Project description

View File

@ -46,6 +46,11 @@ class CreateProject(show.ShowOne):
metavar='<domain>',
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(
'--description',
metavar='<description>',
@ -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)))

View File

@ -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'

View File

@ -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):