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
This commit is contained in:
parent
9400effd4b
commit
6c224f5acf
@ -13,10 +13,20 @@ Create new network
|
||||
.. code:: bash
|
||||
|
||||
os network create
|
||||
[--domain <domain>]
|
||||
[--enable | --disable]
|
||||
[--project <project>]
|
||||
[--share | --no-share]
|
||||
<name>
|
||||
|
||||
.. option:: --domain <domain>
|
||||
|
||||
Owner's domain (name or ID)"
|
||||
|
||||
.. option:: --project <project>
|
||||
|
||||
Owner's project (name or ID)
|
||||
|
||||
.. option:: --enable
|
||||
|
||||
Enable network (default)
|
||||
|
@ -22,6 +22,7 @@ from cliff import show
|
||||
|
||||
from openstackclient.common import exceptions
|
||||
from openstackclient.common import utils
|
||||
from openstackclient.identity import common as identity_common
|
||||
from openstackclient.network import common
|
||||
|
||||
|
||||
@ -82,6 +83,14 @@ class CreateNetwork(show.ShowOne):
|
||||
action='store_false',
|
||||
help='Do not share the network between projects',
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help="Owner's project (name or ID)")
|
||||
parser.add_argument(
|
||||
'--domain',
|
||||
metavar='<domain>',
|
||||
help="Owner's domain (name or ID)")
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
@ -101,6 +110,18 @@ class CreateNetwork(show.ShowOne):
|
||||
'admin_state_up': parsed_args.admin_state}
|
||||
if parsed_args.shared is not None:
|
||||
body['shared'] = parsed_args.shared
|
||||
if parsed_args.project is not None:
|
||||
identity_client = self.app.client_manager.identity
|
||||
if parsed_args.domain is not None:
|
||||
domain = identity_common.find_domain(identity_client,
|
||||
parsed_args.domain)
|
||||
project_id = utils.find_resource(identity_client.projects,
|
||||
parsed_args.project,
|
||||
domain_id=domain.id).id
|
||||
else:
|
||||
project_id = utils.find_resource(identity_client.projects,
|
||||
parsed_args.project).id
|
||||
body['tenant_id'] = project_id
|
||||
return {'network': body}
|
||||
|
||||
|
||||
|
@ -142,6 +142,13 @@ class FakeIdentityv2Client(object):
|
||||
self.auth_token = kwargs['token']
|
||||
self.management_url = kwargs['endpoint']
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Map v3 'projects' back to v2 'tenants'
|
||||
if name == "projects":
|
||||
return self.tenants
|
||||
else:
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
class TestIdentityv2(utils.TestCommand):
|
||||
def setUp(self):
|
||||
|
@ -16,6 +16,9 @@ import mock
|
||||
|
||||
from openstackclient.common import exceptions
|
||||
from openstackclient.network.v2 import network
|
||||
from openstackclient.tests import fakes
|
||||
from openstackclient.tests.identity.v2_0 import fakes as identity_fakes_v2
|
||||
from openstackclient.tests.identity.v3 import fakes as identity_fakes_v3
|
||||
from openstackclient.tests.network import common
|
||||
|
||||
RESOURCE = 'network'
|
||||
@ -65,6 +68,7 @@ class TestCreateNetwork(common.TestNetworkBase):
|
||||
('name', FAKE_NAME),
|
||||
('admin_state', True),
|
||||
('shared', None),
|
||||
('project', None),
|
||||
]
|
||||
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
|
||||
self.app.client_manager.network.create_network = mocker
|
||||
@ -85,15 +89,36 @@ class TestCreateNetwork(common.TestNetworkBase):
|
||||
arglist = [
|
||||
"--disable",
|
||||
"--share",
|
||||
"--project", identity_fakes_v3.project_name,
|
||||
"--domain", identity_fakes_v3.domain_name,
|
||||
FAKE_NAME,
|
||||
] + self.given_show_options
|
||||
verifylist = [
|
||||
('admin_state', False),
|
||||
('shared', True),
|
||||
('project', identity_fakes_v3.project_name),
|
||||
('domain', identity_fakes_v3.domain_name),
|
||||
('name', FAKE_NAME),
|
||||
] + self.then_show_options
|
||||
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
|
||||
self.app.client_manager.network.create_network = mocker
|
||||
identity_client = identity_fakes_v3.FakeIdentityv3Client(
|
||||
endpoint=fakes.AUTH_URL,
|
||||
token=fakes.AUTH_TOKEN,
|
||||
)
|
||||
self.app.client_manager.identity = identity_client
|
||||
self.projects_mock = self.app.client_manager.identity.projects
|
||||
self.projects_mock.get.return_value = fakes.FakeResource(
|
||||
None,
|
||||
copy.deepcopy(identity_fakes_v3.PROJECT),
|
||||
loaded=True,
|
||||
)
|
||||
self.domains_mock = self.app.client_manager.identity.domains
|
||||
self.domains_mock.get.return_value = fakes.FakeResource(
|
||||
None,
|
||||
copy.deepcopy(identity_fakes_v3.DOMAIN),
|
||||
loaded=True,
|
||||
)
|
||||
cmd = network.CreateNetwork(self.app, self.namespace)
|
||||
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
@ -104,6 +129,7 @@ class TestCreateNetwork(common.TestNetworkBase):
|
||||
'admin_state_up': False,
|
||||
'name': FAKE_NAME,
|
||||
'shared': True,
|
||||
'tenant_id': identity_fakes_v3.project_id,
|
||||
}
|
||||
})
|
||||
self.assertEqual(FILTERED, result)
|
||||
@ -135,6 +161,80 @@ class TestCreateNetwork(common.TestNetworkBase):
|
||||
})
|
||||
self.assertEqual(FILTERED, result)
|
||||
|
||||
def test_create_with_project_identityv2(self):
|
||||
arglist = [
|
||||
"--project", identity_fakes_v2.project_name,
|
||||
FAKE_NAME,
|
||||
|
||||
]
|
||||
verifylist = [
|
||||
('admin_state', True),
|
||||
('shared', None),
|
||||
('name', FAKE_NAME),
|
||||
('project', identity_fakes_v2.project_name),
|
||||
]
|
||||
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
|
||||
self.app.client_manager.network.create_network = mocker
|
||||
identity_client = identity_fakes_v2.FakeIdentityv2Client(
|
||||
endpoint=fakes.AUTH_URL,
|
||||
token=fakes.AUTH_TOKEN,
|
||||
)
|
||||
self.app.client_manager.identity = identity_client
|
||||
self.projects_mock = self.app.client_manager.identity.tenants
|
||||
self.projects_mock.get.return_value = fakes.FakeResource(
|
||||
None,
|
||||
copy.deepcopy(identity_fakes_v2.PROJECT),
|
||||
loaded=True,
|
||||
)
|
||||
cmd = network.CreateNetwork(self.app, self.namespace)
|
||||
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
result = list(cmd.take_action(parsed_args))
|
||||
|
||||
mocker.assert_called_with({
|
||||
RESOURCE: {
|
||||
'admin_state_up': True,
|
||||
'name': FAKE_NAME,
|
||||
'tenant_id': identity_fakes_v2.project_id,
|
||||
}
|
||||
})
|
||||
self.assertEqual(FILTERED, result)
|
||||
|
||||
def test_create_with_domain_identityv2(self):
|
||||
arglist = [
|
||||
"--project", identity_fakes_v3.project_name,
|
||||
"--domain", identity_fakes_v3.domain_name,
|
||||
FAKE_NAME,
|
||||
]
|
||||
verifylist = [
|
||||
('admin_state', True),
|
||||
('shared', None),
|
||||
('project', identity_fakes_v3.project_name),
|
||||
('domain', identity_fakes_v3.domain_name),
|
||||
('name', FAKE_NAME),
|
||||
]
|
||||
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
|
||||
self.app.client_manager.network.create_network = mocker
|
||||
identity_client = identity_fakes_v2.FakeIdentityv2Client(
|
||||
endpoint=fakes.AUTH_URL,
|
||||
token=fakes.AUTH_TOKEN,
|
||||
)
|
||||
self.app.client_manager.identity = identity_client
|
||||
self.projects_mock = self.app.client_manager.identity.tenants
|
||||
self.projects_mock.get.return_value = fakes.FakeResource(
|
||||
None,
|
||||
copy.deepcopy(identity_fakes_v2.PROJECT),
|
||||
loaded=True,
|
||||
)
|
||||
cmd = network.CreateNetwork(self.app, self.namespace)
|
||||
parsed_args = self.check_parser(cmd, arglist, verifylist)
|
||||
|
||||
self.assertRaises(
|
||||
AttributeError,
|
||||
cmd.take_action,
|
||||
parsed_args,
|
||||
)
|
||||
|
||||
|
||||
class TestDeleteNetwork(common.TestNetworkBase):
|
||||
def test_delete(self):
|
||||
|
Loading…
Reference in New Issue
Block a user