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:
Colleen Murphy 2015-02-16 23:21:00 -08:00
parent 9400effd4b
commit 6c224f5acf
4 changed files with 138 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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