Skip to content

Commit 634004f

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Add parent field to project creation"
2 parents 9b9c396 + 2ed0e22 commit 634004f

4 files changed

Lines changed: 129 additions & 2 deletions

File tree

‎doc/source/command-objects/project.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ Create new project
2525

2626
.. versionadded:: 3
2727

28+
.. option:: --parent <project>
29+
30+
Parent of the project (name or ID)
31+
32+
.. versionadded:: 3
33+
2834
.. option:: --description <description>
2935

3036
Project description

‎openstackclient/identity/v3/project.py‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ def get_parser(self, prog_name):
4646
metavar='<domain>',
4747
help='Domain owning the project (name or ID)',
4848
)
49+
parser.add_argument(
50+
'--parent',
51+
metavar='<project>',
52+
help='Parent of the project (name or ID)',
53+
)
4954
parser.add_argument(
5055
'--description',
5156
metavar='<description>',
@@ -85,6 +90,13 @@ def take_action(self, parsed_args):
8590
domain = common.find_domain(identity_client,
8691
parsed_args.domain).id
8792

93+
parent = None
94+
if parsed_args.parent:
95+
parent = utils.find_resource(
96+
identity_client.projects,
97+
parsed_args.parent,
98+
).id
99+
88100
enabled = True
89101
if parsed_args.disable:
90102
enabled = False
@@ -96,6 +108,7 @@ def take_action(self, parsed_args):
96108
project = identity_client.projects.create(
97109
name=parsed_args.name,
98110
domain=domain,
111+
parent=parent,
99112
description=parsed_args.description,
100113
enabled=enabled,
101114
**kwargs
@@ -110,8 +123,6 @@ def take_action(self, parsed_args):
110123
raise e
111124

112125
project._info.pop('links')
113-
# TODO(stevemar): Remove the line below when we support multitenancy
114-
project._info.pop('parent_id', None)
115126
return zip(*sorted(six.iteritems(project._info)))
116127

117128

‎openstackclient/tests/identity/v3/fakes.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,16 @@
135135
'links': base_url + 'regions/' + region_id,
136136
}
137137

138+
PROJECT_WITH_PARENT = {
139+
'id': project_id + '-with-parent',
140+
'name': project_name + ' and their parents',
141+
'description': project_description + ' plus another four',
142+
'enabled': True,
143+
'domain_id': domain_id,
144+
'parent_id': project_id,
145+
'links': base_url + 'projects/' + (project_id + '-with-parent'),
146+
}
147+
138148
role_id = 'r1'
139149
role_name = 'roller'
140150

‎openstackclient/tests/identity/v3/test_project.py‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import copy
1717
import mock
1818

19+
from openstackclient.common import exceptions
1920
from openstackclient.identity.v3 import project
2021
from openstackclient.tests import fakes
2122
from openstackclient.tests.identity.v3 import fakes as identity_fakes
@@ -60,6 +61,7 @@ def test_project_create_no_options(self):
6061
identity_fakes.project_name,
6162
]
6263
verifylist = [
64+
('parent', None),
6365
('enable', False),
6466
('disable', False),
6567
('name', identity_fakes.project_name),
@@ -75,6 +77,7 @@ def test_project_create_no_options(self):
7577
'domain': None,
7678
'description': None,
7779
'enabled': True,
80+
'parent': None,
7881
}
7982
# ProjectManager.create(name=, domain=, description=,
8083
# enabled=, **kwargs)
@@ -103,6 +106,7 @@ def test_project_create_description(self):
103106
('enable', False),
104107
('disable', False),
105108
('name', identity_fakes.project_name),
109+
('parent', None),
106110
]
107111
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
108112

@@ -115,6 +119,7 @@ def test_project_create_description(self):
115119
'domain': None,
116120
'description': 'new desc',
117121
'enabled': True,
122+
'parent': None,
118123
}
119124
# ProjectManager.create(name=, domain=, description=,
120125
# enabled=, **kwargs)
@@ -143,6 +148,7 @@ def test_project_create_domain(self):
143148
('enable', False),
144149
('disable', False),
145150
('name', identity_fakes.project_name),
151+
('parent', None),
146152
]
147153
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
148154

@@ -155,6 +161,7 @@ def test_project_create_domain(self):
155161
'domain': identity_fakes.domain_id,
156162
'description': None,
157163
'enabled': True,
164+
'parent': None,
158165
}
159166
# ProjectManager.create(name=, domain=, description=,
160167
# enabled=, **kwargs)
@@ -183,6 +190,7 @@ def test_project_create_domain_no_perms(self):
183190
('enable', False),
184191
('disable', False),
185192
('name', identity_fakes.project_name),
193+
('parent', None),
186194
]
187195
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
188196
mocker = mock.Mock()
@@ -197,6 +205,7 @@ def test_project_create_domain_no_perms(self):
197205
'domain': identity_fakes.domain_id,
198206
'description': None,
199207
'enabled': True,
208+
'parent': None,
200209
}
201210
self.projects_mock.create.assert_called_with(
202211
**kwargs
@@ -221,6 +230,7 @@ def test_project_create_enable(self):
221230
('enable', True),
222231
('disable', False),
223232
('name', identity_fakes.project_name),
233+
('parent', None),
224234
]
225235
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
226236

@@ -233,6 +243,7 @@ def test_project_create_enable(self):
233243
'domain': None,
234244
'description': None,
235245
'enabled': True,
246+
'parent': None,
236247
}
237248
# ProjectManager.create(name=, domain=, description=,
238249
# enabled=, **kwargs)
@@ -260,6 +271,7 @@ def test_project_create_disable(self):
260271
('enable', False),
261272
('disable', True),
262273
('name', identity_fakes.project_name),
274+
('parent', None),
263275
]
264276
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
265277

@@ -272,6 +284,7 @@ def test_project_create_disable(self):
272284
'domain': None,
273285
'description': None,
274286
'enabled': False,
287+
'parent': None,
275288
}
276289
# ProjectManager.create(name=, domain=,
277290
# description=, enabled=, **kwargs)
@@ -311,6 +324,7 @@ def test_project_create_property(self):
311324
'domain': None,
312325
'description': None,
313326
'enabled': True,
327+
'parent': None,
314328
'fee': 'fi',
315329
'fo': 'fum',
316330
}
@@ -331,6 +345,92 @@ def test_project_create_property(self):
331345
)
332346
self.assertEqual(datalist, data)
333347

348+
def test_project_create_parent(self):
349+
self.projects_mock.get.return_value = fakes.FakeResource(
350+
None,
351+
copy.deepcopy(identity_fakes.PROJECT),
352+
loaded=True,
353+
)
354+
self.projects_mock.create.return_value = fakes.FakeResource(
355+
None,
356+
copy.deepcopy(identity_fakes.PROJECT_WITH_PARENT),
357+
loaded=True,
358+
)
359+
360+
arglist = [
361+
'--domain', identity_fakes.PROJECT_WITH_PARENT['domain_id'],
362+
'--parent', identity_fakes.PROJECT['name'],
363+
identity_fakes.PROJECT_WITH_PARENT['name'],
364+
]
365+
verifylist = [
366+
('domain', identity_fakes.PROJECT_WITH_PARENT['domain_id']),
367+
('parent', identity_fakes.PROJECT['name']),
368+
('enable', False),
369+
('disable', False),
370+
('name', identity_fakes.PROJECT_WITH_PARENT['name']),
371+
]
372+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
373+
374+
columns, data = self.cmd.take_action(parsed_args)
375+
376+
kwargs = {
377+
'name': identity_fakes.PROJECT_WITH_PARENT['name'],
378+
'domain': identity_fakes.PROJECT_WITH_PARENT['domain_id'],
379+
'parent': identity_fakes.PROJECT['id'],
380+
'description': None,
381+
'enabled': True,
382+
}
383+
384+
self.projects_mock.create.assert_called_with(
385+
**kwargs
386+
)
387+
388+
collist = (
389+
'description',
390+
'domain_id',
391+
'enabled',
392+
'id',
393+
'name',
394+
'parent_id',
395+
)
396+
self.assertEqual(columns, collist)
397+
datalist = (
398+
identity_fakes.PROJECT_WITH_PARENT['description'],
399+
identity_fakes.PROJECT_WITH_PARENT['domain_id'],
400+
identity_fakes.PROJECT_WITH_PARENT['enabled'],
401+
identity_fakes.PROJECT_WITH_PARENT['id'],
402+
identity_fakes.PROJECT_WITH_PARENT['name'],
403+
identity_fakes.PROJECT['id'],
404+
)
405+
self.assertEqual(data, datalist)
406+
407+
def test_project_create_invalid_parent(self):
408+
self.projects_mock.resource_class.__name__ = 'Project'
409+
self.projects_mock.get.side_effect = exceptions.NotFound(
410+
'Invalid parent')
411+
self.projects_mock.find.side_effect = exceptions.NotFound(
412+
'Invalid parent')
413+
414+
arglist = [
415+
'--domain', identity_fakes.domain_name,
416+
'--parent', 'invalid',
417+
identity_fakes.project_name,
418+
]
419+
verifylist = [
420+
('domain', identity_fakes.domain_name),
421+
('parent', 'invalid'),
422+
('enable', False),
423+
('disable', False),
424+
('name', identity_fakes.project_name),
425+
]
426+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
427+
428+
self.assertRaises(
429+
exceptions.CommandError,
430+
self.cmd.take_action,
431+
parsed_args,
432+
)
433+
334434

335435
class TestProjectDelete(TestProject):
336436

0 commit comments

Comments
 (0)