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 .. code:: bash
os network create os network create
[--domain <domain>]
[--enable | --disable] [--enable | --disable]
[--project <project>]
[--share | --no-share] [--share | --no-share]
<name> <name>
.. option:: --domain <domain>
Owner's domain (name or ID)"
.. option:: --project <project>
Owner's project (name or ID)
.. option:: --enable .. option:: --enable
Enable network (default) Enable network (default)

View File

@ -22,6 +22,7 @@ from cliff import show
from openstackclient.common import exceptions from openstackclient.common import exceptions
from openstackclient.common import utils from openstackclient.common import utils
from openstackclient.identity import common as identity_common
from openstackclient.network import common from openstackclient.network import common
@ -82,6 +83,14 @@ class CreateNetwork(show.ShowOne):
action='store_false', action='store_false',
help='Do not share the network between projects', 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 return parser
def take_action(self, parsed_args): def take_action(self, parsed_args):
@ -101,6 +110,18 @@ class CreateNetwork(show.ShowOne):
'admin_state_up': parsed_args.admin_state} 'admin_state_up': parsed_args.admin_state}
if parsed_args.shared is not None: if parsed_args.shared is not None:
body['shared'] = parsed_args.shared 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} return {'network': body}

View File

@ -142,6 +142,13 @@ class FakeIdentityv2Client(object):
self.auth_token = kwargs['token'] self.auth_token = kwargs['token']
self.management_url = kwargs['endpoint'] 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): class TestIdentityv2(utils.TestCommand):
def setUp(self): def setUp(self):

View File

@ -16,6 +16,9 @@ import mock
from openstackclient.common import exceptions from openstackclient.common import exceptions
from openstackclient.network.v2 import network 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 from openstackclient.tests.network import common
RESOURCE = 'network' RESOURCE = 'network'
@ -65,6 +68,7 @@ class TestCreateNetwork(common.TestNetworkBase):
('name', FAKE_NAME), ('name', FAKE_NAME),
('admin_state', True), ('admin_state', True),
('shared', None), ('shared', None),
('project', None),
] ]
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
self.app.client_manager.network.create_network = mocker self.app.client_manager.network.create_network = mocker
@ -85,15 +89,36 @@ class TestCreateNetwork(common.TestNetworkBase):
arglist = [ arglist = [
"--disable", "--disable",
"--share", "--share",
"--project", identity_fakes_v3.project_name,
"--domain", identity_fakes_v3.domain_name,
FAKE_NAME, FAKE_NAME,
] + self.given_show_options ] + self.given_show_options
verifylist = [ verifylist = [
('admin_state', False), ('admin_state', False),
('shared', True), ('shared', True),
('project', identity_fakes_v3.project_name),
('domain', identity_fakes_v3.domain_name),
('name', FAKE_NAME), ('name', FAKE_NAME),
] + self.then_show_options ] + self.then_show_options
mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE)) mocker = mock.Mock(return_value=copy.deepcopy(RESPONSE))
self.app.client_manager.network.create_network = mocker 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) cmd = network.CreateNetwork(self.app, self.namespace)
parsed_args = self.check_parser(cmd, arglist, verifylist) parsed_args = self.check_parser(cmd, arglist, verifylist)
@ -104,6 +129,7 @@ class TestCreateNetwork(common.TestNetworkBase):
'admin_state_up': False, 'admin_state_up': False,
'name': FAKE_NAME, 'name': FAKE_NAME,
'shared': True, 'shared': True,
'tenant_id': identity_fakes_v3.project_id,
} }
}) })
self.assertEqual(FILTERED, result) self.assertEqual(FILTERED, result)
@ -135,6 +161,80 @@ class TestCreateNetwork(common.TestNetworkBase):
}) })
self.assertEqual(FILTERED, result) 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): class TestDeleteNetwork(common.TestNetworkBase):
def test_delete(self): def test_delete(self):