Skip to content

Commit 6c224f5

Browse files
author
Colleen Murphy
committed
Add project and domain params to network create
Without this patch, openstackclient has no way to specify to which project a network belongs upon creation. Instead, it uses the project ID that the user is authenticating with to fill the tenant_id column. This is a problem because an admin user is unable to specify a project for a non-admin network. To fix this and to improve feature parity with the neutron client, this patch adds project and domain parameters to the network create command and uses the given project name to look up the project ID. Neutron does not allow the project to be changed after creation, so no such parameter has been added to the neutron set command. Neutron calls the field 'tenant_id', but this change exposes the parameter as '--project' to support the newer terminology. If no project is specified, the client defaults to the previous behavior of using the auth project. Change-Id: Ia33ff7d599542c5b88baf2a69b063a23089a3cc4
1 parent 9400eff commit 6c224f5

4 files changed

Lines changed: 138 additions & 0 deletions

File tree

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,20 @@ Create new network
1313
.. code:: bash
1414
1515
os network create
16+
[--domain <domain>]
1617
[--enable | --disable]
18+
[--project <project>]
1719
[--share | --no-share]
1820
<name>
1921
22+
.. option:: --domain <domain>
23+
24+
Owner's domain (name or ID)"
25+
26+
.. option:: --project <project>
27+
28+
Owner's project (name or ID)
29+
2030
.. option:: --enable
2131

2232
Enable network (default)

‎openstackclient/network/v2/network.py‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
from openstackclient.common import exceptions
2424
from openstackclient.common import utils
25+
from openstackclient.identity import common as identity_common
2526
from openstackclient.network import common
2627

2728

@@ -82,6 +83,14 @@ def get_parser(self, prog_name):
8283
action='store_false',
8384
help='Do not share the network between projects',
8485
)
86+
parser.add_argument(
87+
'--project',
88+
metavar='<project>',
89+
help="Owner's project (name or ID)")
90+
parser.add_argument(
91+
'--domain',
92+
metavar='<domain>',
93+
help="Owner's domain (name or ID)")
8594
return parser
8695

8796
def take_action(self, parsed_args):
@@ -101,6 +110,18 @@ def get_body(self, parsed_args):
101110
'admin_state_up': parsed_args.admin_state}
102111
if parsed_args.shared is not None:
103112
body['shared'] = parsed_args.shared
113+
if parsed_args.project is not None:
114+
identity_client = self.app.client_manager.identity
115+
if parsed_args.domain is not None:
116+
domain = identity_common.find_domain(identity_client,
117+
parsed_args.domain)
118+
project_id = utils.find_resource(identity_client.projects,
119+
parsed_args.project,
120+
domain_id=domain.id).id
121+
else:
122+
project_id = utils.find_resource(identity_client.projects,
123+
parsed_args.project).id
124+
body['tenant_id'] = project_id
104125
return {'network': body}
105126

106127

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ def __init__(self, **kwargs):
142142
self.auth_token = kwargs['token']
143143
self.management_url = kwargs['endpoint']
144144

145+
def __getattr__(self, name):
146+
# Map v3 'projects' back to v2 'tenants'
147+
if name == "projects":
148+
return self.tenants
149+
else:
150+
raise AttributeError(name)
151+
145152

146153
class TestIdentityv2(utils.TestCommand):
147154
def setUp(self):

‎openstackclient/tests/network/v2/test_network.py‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
from openstackclient.common import exceptions
1818
from openstackclient.network.v2 import network
19+
from openstackclient.tests import fakes
20+
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2
21+
from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3
1922
from openstackclient.tests.network import common
2023

2124
RESOURCE = 'network'
@@ -65,6 +68,7 @@ def test_create_no_options(self):
6568
('name', FAKE_NAME),
6669
('admin_state', True),
6770
('shared', None),
71+
('project', None),
6872
]
6973
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
7074
self.app.client_manager.network.create_network = mocker
@@ -85,15 +89,36 @@ def test_create_all_options(self):
8589
arglist = [
8690
"--disable",
8791
"--share",
92+
"--project", identity_fakes_v3.project_name,
93+
"--domain", identity_fakes_v3.domain_name,
8894
FAKE_NAME,
8995
] + self.given_show_options
9096
verifylist = [
9197
('admin_state', False),
9298
('shared', True),
99+
('project', identity_fakes_v3.project_name),
100+
('domain', identity_fakes_v3.domain_name),
93101
('name', FAKE_NAME),
94102
] + self.then_show_options
95103
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
96104
self.app.client_manager.network.create_network = mocker
105+
identity_client = identity_fakes_v3.FakeIdentityv3Client(
106+
endpoint=fakes.AUTH_URL,
107+
token=fakes.AUTH_TOKEN,
108+
)
109+
self.app.client_manager.identity = identity_client
110+
self.projects_mock = self.app.client_manager.identity.projects
111+
self.projects_mock.get.return_value = fakes.FakeResource(
112+
None,
113+
copy.deepcopy(identity_fakes_v3.PROJECT),
114+
loaded=True,
115+
)
116+
self.domains_mock = self.app.client_manager.identity.domains
117+
self.domains_mock.get.return_value = fakes.FakeResource(
118+
None,
119+
copy.deepcopy(identity_fakes_v3.DOMAIN),
120+
loaded=True,
121+
)
97122
cmd = network.CreateNetwork(self.app, self.namespace)
98123

99124
parsed_args = self.check_parser(cmd, arglist, verifylist)
@@ -104,6 +129,7 @@ def test_create_all_options(self):
104129
'admin_state_up': False,
105130
'name': FAKE_NAME,
106131
'shared': True,
132+
'tenant_id': identity_fakes_v3.project_id,
107133
}
108134
})
109135
self.assertEqual(FILTERED, result)
@@ -135,6 +161,80 @@ def test_create_other_options(self):
135161
})
136162
self.assertEqual(FILTERED, result)
137163

164+
def test_create_with_project_identityv2(self):
165+
arglist = [
166+
"--project", identity_fakes_v2.project_name,
167+
FAKE_NAME,
168+
169+
]
170+
verifylist = [
171+
('admin_state', True),
172+
('shared', None),
173+
('name', FAKE_NAME),
174+
('project', identity_fakes_v2.project_name),
175+
]
176+
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
177+
self.app.client_manager.network.create_network = mocker
178+
identity_client = identity_fakes_v2.FakeIdentityv2Client(
179+
endpoint=fakes.AUTH_URL,
180+
token=fakes.AUTH_TOKEN,
181+
)
182+
self.app.client_manager.identity = identity_client
183+
self.projects_mock = self.app.client_manager.identity.tenants
184+
self.projects_mock.get.return_value = fakes.FakeResource(
185+
None,
186+
copy.deepcopy(identity_fakes_v2.PROJECT),
187+
loaded=True,
188+
)
189+
cmd = network.CreateNetwork(self.app, self.namespace)
190+
191+
parsed_args = self.check_parser(cmd, arglist, verifylist)
192+
result = list(cmd.take_action(parsed_args))
193+
194+
mocker.assert_called_with({
195+
RESOURCE: {
196+
'admin_state_up': True,
197+
'name': FAKE_NAME,
198+
'tenant_id': identity_fakes_v2.project_id,
199+
}
200+
})
201+
self.assertEqual(FILTERED, result)
202+
203+
def test_create_with_domain_identityv2(self):
204+
arglist = [
205+
"--project", identity_fakes_v3.project_name,
206+
"--domain", identity_fakes_v3.domain_name,
207+
FAKE_NAME,
208+
]
209+
verifylist = [
210+
('admin_state', True),
211+
('shared', None),
212+
('project', identity_fakes_v3.project_name),
213+
('domain', identity_fakes_v3.domain_name),
214+
('name', FAKE_NAME),
215+
]
216+
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
217+
self.app.client_manager.network.create_network = mocker
218+
identity_client = identity_fakes_v2.FakeIdentityv2Client(
219+
endpoint=fakes.AUTH_URL,
220+
token=fakes.AUTH_TOKEN,
221+
)
222+
self.app.client_manager.identity = identity_client
223+
self.projects_mock = self.app.client_manager.identity.tenants
224+
self.projects_mock.get.return_value = fakes.FakeResource(
225+
None,
226+
copy.deepcopy(identity_fakes_v2.PROJECT),
227+
loaded=True,
228+
)
229+
cmd = network.CreateNetwork(self.app, self.namespace)
230+
parsed_args = self.check_parser(cmd, arglist, verifylist)
231+
232+
self.assertRaises(
233+
AttributeError,
234+
cmd.take_action,
235+
parsed_args,
236+
)
237+
138238

139239
class TestDeleteNetwork(common.TestNetworkBase):
140240
def test_delete(self):

0 commit comments

Comments
 (0)