Add 'port create' command

This patch adds usage of 'port create' in CLI

Change-Id: I888af50784c3b6c7ec30552ade79f05a5e974711
Partial-bug: #1519909
Partially-implements: blueprint neutron-client
This commit is contained in:
Jas 2016-01-28 11:08:17 -06:00
parent fc8b4cfcae
commit d1d4a40808
5 changed files with 400 additions and 53 deletions

View File

@ -4,6 +4,84 @@ port
Network v2 Network v2
port create
-----------
Create new port
.. program:: port create
.. code:: bash
os port create
--network <network>
[--fixed-ip subnet=<subnet>,ip-address=<ip-address>]
[--device-id <device-id>]
[--device-owner <device-owner>]
[--vnic-type <vnic-type>]
[--binding-profile <binding-profile>]
[--host-id <host-id>]
[--enable | --disable]
[--mac-address <mac-address>]
[--project <project> [--project-domain <project-domain>]]
<name>
.. option:: --network <network>
Network this port belongs to (name or ID)
.. option:: --fixed-ip subnet=<subnet>,ip-address=<ip-address>
Desired IP and/or subnet (name or ID) for this port:
subnet=<subnet>,ip-address=<ip-address>
(this option can be repeated)
.. option:: --device-id <device-id>
Device ID of this port
.. option:: --device-owner <device-owner>
Device owner of this port
.. option:: --vnic-type <vnic-type>
VNIC type for this port (direct | direct-physical | macvtap | normal(default) | baremetal)
.. option:: --binding-profile <binding-profile>
Custom data to be passed as binding:profile: <key>=<value>
(this option can be repeated)
.. option:: --host-id <host-id>
The ID of the host where the port is allocated
.. option:: --enable
Enable port (default)
.. option:: --disable
Disable port
.. option:: --mac-address <mac-address>
MAC address of this port
.. option:: --project <project>
Owner's project (name or ID)
.. option:: --project-domain <project-domain>
Domain the project belongs to (name or ID).
This can be used in case collisions between project names exist.
.. _port_create-name:
.. describe:: <name>
Name of this port
port delete port delete
----------- -----------

View File

@ -14,13 +14,14 @@
"""Port action implementations""" """Port action implementations"""
from openstackclient.common import command from openstackclient.common import command
from openstackclient.common import parseractions
from openstackclient.common import utils from openstackclient.common import utils
from openstackclient.identity import common as identity_common
def _format_admin_state(state): def _format_admin_state(state):
return 'UP' if state else 'DOWN' return 'UP' if state else 'DOWN'
_formatters = { _formatters = {
'admin_state_up': _format_admin_state, 'admin_state_up': _format_admin_state,
'allowed_address_pairs': utils.format_list_of_dicts, 'allowed_address_pairs': utils.format_list_of_dicts,
@ -49,7 +50,171 @@ def _get_columns(item):
if binding_column in columns: if binding_column in columns:
columns.remove(binding_column) columns.remove(binding_column)
columns.append(binding_column.replace('binding:', 'binding_', 1)) columns.append(binding_column.replace('binding:', 'binding_', 1))
return sorted(columns) return tuple(sorted(columns))
def _get_attrs(client_manager, parsed_args):
attrs = {}
if parsed_args.name is not None:
attrs['name'] = str(parsed_args.name)
if parsed_args.fixed_ip is not None:
attrs['fixed_ips'] = parsed_args.fixed_ip
if parsed_args.device_id is not None:
attrs['device_id'] = parsed_args.device_id
if parsed_args.device_owner is not None:
attrs['device_owner'] = parsed_args.device_owner
if parsed_args.admin_state is not None:
attrs['admin_state_up'] = parsed_args.admin_state
if parsed_args.binding_profile is not None:
attrs['binding:profile'] = parsed_args.binding_profile
if parsed_args.vnic_type is not None:
attrs['binding:vnic_type'] = parsed_args.vnic_type
if parsed_args.host_id is not None:
attrs['binding:host_id'] = parsed_args.host_id
# The remaining options do not support 'port set' command, so they require
# additional check
if 'mac_address' in parsed_args and parsed_args.mac_address is not None:
attrs['mac_address'] = parsed_args.mac_address
if 'network' in parsed_args and parsed_args.network is not None:
attrs['network_id'] = parsed_args.network
if 'project' in parsed_args and parsed_args.project is not None:
# TODO(singhj): since 'project' logic is common among
# router, network, port etc., maybe move it to a common file.
identity_client = client_manager.identity
project_id = identity_common.find_project(
identity_client,
parsed_args.project,
parsed_args.project_domain,
).id
attrs['tenant_id'] = project_id
return attrs
def _prepare_fixed_ips(client_manager, parsed_args):
"""Fix and properly format fixed_ip option.
Appropriately convert any subnet names to their respective ids.
Convert fixed_ips in parsed args to be in valid dictionary format:
{'subnet': 'foo'}.
"""
client = client_manager.network
ips = []
if parsed_args.fixed_ip:
for ip_spec in parsed_args.fixed_ip:
if 'subnet' in ip_spec:
subnet_name_id = ip_spec['subnet']
if subnet_name_id:
_subnet = client.find_subnet(subnet_name_id,
ignore_missing=False)
ip_spec['subnet_id'] = _subnet.id
del ip_spec['subnet']
if 'ip-address' in ip_spec:
ip_spec['ip_address'] = ip_spec['ip-address']
del ip_spec['ip-address']
ips.append(ip_spec)
if ips:
parsed_args.fixed_ip = ips
def _add_updatable_args(parser):
parser.add_argument(
'--fixed-ip',
metavar='subnet=<subnet>,ip-address=<ip-address>',
action=parseractions.MultiKeyValueAction,
optional_keys=['subnet', 'ip-address'],
help='Desired IP and/or subnet (name or ID) for this port: '
'subnet=<subnet>,ip-address=<ip-address> '
'(this option can be repeated)')
parser.add_argument(
'--device-id',
metavar='<device-id>',
help='Device ID of this port')
parser.add_argument(
'--device-owner',
metavar='<device-owner>',
help='Device owner of this port')
parser.add_argument(
'--vnic-type',
metavar='<vnic-type>',
choices=['direct', 'direct-physical', 'macvtap',
'normal', 'baremetal'],
help='VNIC type for this port (direct | direct-physical |'
' macvtap | normal(default) | baremetal)')
parser.add_argument(
'--binding-profile',
metavar='<binding-profile>',
action=parseractions.KeyValueAction,
help='Custom data to be passed as binding:profile: <key>=<value> '
'(this option can be repeated)')
parser.add_argument(
'--host-id',
metavar='<host-id>',
help='The ID of the host where the port is allocated'
)
class CreatePort(command.ShowOne):
"""Create a new port"""
def get_parser(self, prog_name):
parser = super(CreatePort, self).get_parser(prog_name)
parser.add_argument(
'--network',
metavar='<network>',
required=True,
help='Network this port belongs to (name or ID)')
_add_updatable_args(parser)
admin_group = parser.add_mutually_exclusive_group()
admin_group.add_argument(
'--enable',
dest='admin_state',
action='store_true',
default=True,
help='Enable port (default)',
)
admin_group.add_argument(
'--disable',
dest='admin_state',
action='store_false',
help='Disable port',
)
parser.add_argument(
'--mac-address',
metavar='<mac-address>',
help='MAC address of this port')
parser.add_argument(
'--project',
metavar='<project>',
help="Owner's project (name or ID)")
parser.add_argument(
'name',
metavar='<name>',
help='Name of this port')
identity_common.add_project_domain_option_to_parser(parser)
# TODO(singhj): Add support for extended options:
# qos,security groups,dhcp, address pairs
return parser
def take_action(self, parsed_args):
client = self.app.client_manager.network
_network = client.find_network(parsed_args.network,
ignore_missing=False)
parsed_args.network = _network.id
_prepare_fixed_ips(self.app.client_manager, parsed_args)
attrs = _get_attrs(self.app.client_manager, parsed_args)
obj = client.create_port(**attrs)
columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters)
return columns, data
class DeletePort(command.Command): class DeletePort(command.Command):
@ -90,4 +255,4 @@ class ShowPort(command.ShowOne):
obj = client.find_port(parsed_args.port, ignore_missing=False) obj = client.find_port(parsed_args.port, ignore_missing=False)
columns = _get_columns(obj) columns = _get_columns(obj)
data = utils.get_item_properties(obj, columns, formatters=_formatters) data = utils.get_item_properties(obj, columns, formatters=_formatters)
return (tuple(columns), data) return columns, data

View File

@ -27,6 +27,150 @@ class TestPort(network_fakes.TestNetworkV2):
# Get a shortcut to the network client # Get a shortcut to the network client
self.network = self.app.client_manager.network self.network = self.app.client_manager.network
def _get_common_cols_data(self, fake_port):
columns = (
'admin_state_up',
'allowed_address_pairs',
'binding_host_id',
'binding_profile',
'binding_vif_details',
'binding_vif_type',
'binding_vnic_type',
'device_id',
'device_owner',
'dns_assignment',
'dns_name',
'extra_dhcp_opts',
'fixed_ips',
'id',
'mac_address',
'name',
'network_id',
'port_security_enabled',
'project_id',
'security_groups',
'status',
)
data = (
port._format_admin_state(fake_port.admin_state_up),
utils.format_list_of_dicts(fake_port.allowed_address_pairs),
fake_port.binding_host_id,
utils.format_dict(fake_port.binding_profile),
utils.format_dict(fake_port.binding_vif_details),
fake_port.binding_vif_type,
fake_port.binding_vnic_type,
fake_port.device_id,
fake_port.device_owner,
utils.format_list_of_dicts(fake_port.dns_assignment),
fake_port.dns_name,
utils.format_list_of_dicts(fake_port.extra_dhcp_opts),
utils.format_list_of_dicts(fake_port.fixed_ips),
fake_port.id,
fake_port.mac_address,
fake_port.name,
fake_port.network_id,
fake_port.port_security_enabled,
fake_port.project_id,
utils.format_list(fake_port.security_groups),
fake_port.status,
)
return columns, data
class TestCreatePort(TestPort):
_port = network_fakes.FakePort.create_one_port()
def setUp(self):
super(TestCreatePort, self).setUp()
self.network.create_port = mock.Mock(return_value=self._port)
fake_net = network_fakes.FakeNetwork.create_one_network({
'id': self._port.network_id,
})
self.network.find_network = mock.Mock(return_value=fake_net)
self.fake_subnet = network_fakes.FakeSubnet.create_one_subnet()
self.network.find_subnet = mock.Mock(return_value=self.fake_subnet)
# Get the command object to test
self.cmd = port.CreatePort(self.app, self.namespace)
def test_create_default_options(self):
arglist = [
'--network', self._port.network_id,
'test-port',
]
verifylist = [
('network', self._port.network_id,),
('admin_state', True),
('name', 'test-port'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = (self.cmd.take_action(parsed_args))
self.network.create_port.assert_called_with(**{
'admin_state_up': True,
'network_id': self._port.network_id,
'name': 'test-port',
})
ref_columns, ref_data = self._get_common_cols_data(self._port)
self.assertEqual(ref_columns, columns)
self.assertEqual(ref_data, data)
def test_create_full_options(self):
arglist = [
'--mac-address', 'aa:aa:aa:aa:aa:aa',
'--fixed-ip', 'subnet=%s,ip-address=10.0.0.2'
% self.fake_subnet.id,
'--device-id', 'deviceid',
'--device-owner', 'fakeowner',
'--disable',
'--vnic-type', 'macvtap',
'--binding-profile', 'foo=bar',
'--binding-profile', 'foo2=bar2',
'--network', self._port.network_id,
'test-port',
]
verifylist = [
('mac_address', 'aa:aa:aa:aa:aa:aa'),
(
'fixed_ip',
[{'subnet': self.fake_subnet.id, 'ip-address': '10.0.0.2'}]
),
('device_id', 'deviceid'),
('device_owner', 'fakeowner'),
('admin_state', False),
('vnic_type', 'macvtap'),
('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}),
('network', self._port.network_id),
('name', 'test-port'),
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = (self.cmd.take_action(parsed_args))
self.network.create_port.assert_called_with(**{
'mac_address': 'aa:aa:aa:aa:aa:aa',
'fixed_ips': [{'subnet_id': self.fake_subnet.id,
'ip_address': '10.0.0.2'}],
'device_id': 'deviceid',
'device_owner': 'fakeowner',
'admin_state_up': False,
'binding:vnic_type': 'macvtap',
'binding:profile': {'foo': 'bar', 'foo2': 'bar2'},
'network_id': self._port.network_id,
'name': 'test-port',
})
ref_columns, ref_data = self._get_common_cols_data(self._port)
self.assertEqual(ref_columns, columns)
self.assertEqual(ref_data, data)
class TestDeletePort(TestPort): class TestDeletePort(TestPort):
@ -60,54 +204,6 @@ class TestShowPort(TestPort):
# The port to show. # The port to show.
_port = network_fakes.FakePort.create_one_port() _port = network_fakes.FakePort.create_one_port()
columns = (
'admin_state_up',
'allowed_address_pairs',
'binding_host_id',
'binding_profile',
'binding_vif_details',
'binding_vif_type',
'binding_vnic_type',
'device_id',
'device_owner',
'dns_assignment',
'dns_name',
'extra_dhcp_opts',
'fixed_ips',
'id',
'mac_address',
'name',
'network_id',
'port_security_enabled',
'project_id',
'security_groups',
'status',
)
data = (
port._format_admin_state(_port.admin_state_up),
utils.format_list_of_dicts(_port.allowed_address_pairs),
_port.binding_host_id,
utils.format_dict(_port.binding_profile),
utils.format_dict(_port.binding_vif_details),
_port.binding_vif_type,
_port.binding_vnic_type,
_port.device_id,
_port.device_owner,
utils.format_list_of_dicts(_port.dns_assignment),
_port.dns_name,
utils.format_list_of_dicts(_port.extra_dhcp_opts),
utils.format_list_of_dicts(_port.fixed_ips),
_port.id,
_port.mac_address,
_port.name,
_port.network_id,
_port.port_security_enabled,
_port.project_id,
utils.format_list(_port.security_groups),
_port.status,
)
def setUp(self): def setUp(self):
super(TestShowPort, self).setUp() super(TestShowPort, self).setUp()
@ -136,5 +232,7 @@ class TestShowPort(TestPort):
self.network.find_port.assert_called_with(self._port.name, self.network.find_port.assert_called_with(self._port.name,
ignore_missing=False) ignore_missing=False)
self.assertEqual(tuple(self.columns), columns)
self.assertEqual(self.data, data) ref_columns, ref_data = self._get_common_cols_data(self._port)
self.assertEqual(ref_columns, columns)
self.assertEqual(ref_data, data)

View File

@ -0,0 +1,5 @@
---
features:
- |
Add support for the ``port create`` command.
[Bug `1519909 <https://bugs.launchpad.net/python-openstackclient/+bug/1519909>`_]

View File

@ -331,6 +331,7 @@ openstack.network.v2 =
network_list = openstackclient.network.v2.network:ListNetwork network_list = openstackclient.network.v2.network:ListNetwork
network_set = openstackclient.network.v2.network:SetNetwork network_set = openstackclient.network.v2.network:SetNetwork
network_show = openstackclient.network.v2.network:ShowNetwork network_show = openstackclient.network.v2.network:ShowNetwork
port_create = openstackclient.network.v2.port:CreatePort
port_delete = openstackclient.network.v2.port:DeletePort port_delete = openstackclient.network.v2.port:DeletePort
port_show = openstackclient.network.v2.port:ShowPort port_show = openstackclient.network.v2.port:ShowPort
router_create = openstackclient.network.v2.router:CreateRouter router_create = openstackclient.network.v2.router:CreateRouter