Add "--network-segment" option to "subnet create"

Add "--network-segment" option to the "subnet create" command.
This is a beta command option and subject to change. Use global
option "--os-beta-command" to enable this option.

This patch set also provides a devref update for beta command
options.

Change-Id: I4d0fbe079b2a873307364c41c22ce9ba88e632e6
Partially-Implements: blueprint routed-networks
This commit is contained in:
Richard Theis 2016-06-10 16:01:31 -05:00
parent df71ae814e
commit 6a6b192dde
6 changed files with 157 additions and 25 deletions

View File

@ -12,23 +12,29 @@ To address these challenges, an OpenStackClient command may
be labeled as a beta command according to the guidelines be labeled as a beta command according to the guidelines
below. Such commands may introduce backwards incompatible below. Such commands may introduce backwards incompatible
changes and may use REST API enhancements not yet released. changes and may use REST API enhancements not yet released.
This also applies to command options associated with the beta
command object.
See the examples below on how to label a command as a beta See the examples below on how to label an entire command or
by updating the command documentation, help and implementation. a specific option as a beta by updating the documentation
and implementation.
The initial release note must label the new command as a beta. The initial release note must label the new command or option
No further release notes are required until the command as a beta. No further release notes are required until the command
is no longer a beta. At which time, the command beta label or option is no longer a beta. At which time, the beta label or
or the command itself must be removed and a new release note the command or option itself must be removed and a new release note
must be provided. must be provided.
Beta Command Example
--------------------
Documentation Documentation
------------- ~~~~~~~~~~~~~
The command documentation must label the command as a beta. The command documentation must label the command as a beta.
example list example list
~~~~~~~~~~~~ ++++++++++++
List examples List examples
@ -42,7 +48,7 @@ List examples
os example list os example list
Help Help
---- ~~~~
The command help must label the command as a beta. The command help must label the command as a beta.
@ -57,7 +63,7 @@ The command help must label the command as a beta.
""" """
Implementation Implementation
-------------- ~~~~~~~~~~~~~~
The command must raise a ``CommandError`` exception if beta commands The command must raise a ``CommandError`` exception if beta commands
are not enabled via ``--os-beta-command`` global option. are not enabled via ``--os-beta-command`` global option.
@ -66,3 +72,35 @@ are not enabled via ``--os-beta-command`` global option.
def take_action(self, parsed_args): def take_action(self, parsed_args):
self.validate_os_beta_command_enabled() self.validate_os_beta_command_enabled()
Beta Option Example
-------------------
Documentation
~~~~~~~~~~~~~
The option documentation must label the option as a beta.
.. option:: --example <example>
Example
.. caution:: This is a beta command option and subject
to change. Use global option ``--os-beta-command``
to enable this command option.
Implementation
~~~~~~~~~~~~~~
The option must not be added if beta commands are not
enabled via ``--os-beta-command`` global option.
.. code-block:: python
def get_parser(self, prog_name):
if self.app.options.os_beta_command:
parser.add_argument(
'--example',
metavar='<example>',
help=_("Example")
)

View File

@ -28,6 +28,7 @@ Create new subnet
[--ip-version {4,6}] [--ip-version {4,6}]
[--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}]
[--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}]
[--network-segment <network-segment>]
--network <network> --network <network>
<name> <name>
@ -107,6 +108,14 @@ Create new subnet
IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac] IPv6 address mode, valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]
.. option:: --network-segment <network-segment>
Network segment to associate with this subnet (ID only)
.. caution:: This is a beta command option and subject
to change. Use global option ``--os-beta-command``
to enable this command option.
.. option:: --network <network> .. option:: --network <network>
Network this subnet belongs to (name or ID) Network this subnet belongs to (name or ID)

View File

@ -136,6 +136,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True):
attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode
if parsed_args.ipv6_address_mode is not None: if parsed_args.ipv6_address_mode is not None:
attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode attrs['ipv6_address_mode'] = parsed_args.ipv6_address_mode
if 'network_segment' in parsed_args:
attrs['segment_id'] = client.find_segment(
parsed_args.network_segment, ignore_missing=False).id
if 'gateway' in parsed_args and parsed_args.gateway is not None: if 'gateway' in parsed_args and parsed_args.gateway is not None:
gateway = parsed_args.gateway.lower() gateway = parsed_args.gateway.lower()
@ -249,6 +252,13 @@ class CreateSubnet(command.ShowOne):
help=_("IPv6 address mode, " help=_("IPv6 address mode, "
"valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]") "valid modes: [dhcpv6-stateful, dhcpv6-stateless, slaac]")
) )
if self.app.options.os_beta_command:
parser.add_argument(
'--network-segment',
metavar='<network-segment>',
help=_("Network segment to associate with this subnet "
"(ID only)")
)
parser.add_argument( parser.add_argument(
'--network', '--network',
required=True, required=True,

View File

@ -352,7 +352,7 @@ class FakeNetworkSegment(object):
# Set default attributes. # Set default attributes.
network_segment_attrs = { network_segment_attrs = {
'id': 'segment-id-' + uuid.uuid4().hex, 'id': 'network-segment-id-' + uuid.uuid4().hex,
'network_id': 'network-id-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex,
'network_type': 'vlan', 'network_type': 'vlan',
'physical_network': 'physical-network-name-' + uuid.uuid4().hex, 'physical_network': 'physical-network-name-' + uuid.uuid4().hex,
@ -699,9 +699,10 @@ class FakeSubnet(object):
'host_routes': [], 'host_routes': [],
'ip_version': 4, 'ip_version': 4,
'gateway_ip': '10.10.10.1', 'gateway_ip': '10.10.10.1',
'ipv6_address_mode': 'None', 'ipv6_address_mode': None,
'ipv6_ra_mode': 'None', 'ipv6_ra_mode': None,
'subnetpool_id': 'None', 'segment_id': None,
'subnetpool_id': None,
} }
# Overwrite default attributes. # Overwrite default attributes.

View File

@ -88,6 +88,14 @@ class TestCreateSubnet(TestSubnet):
} }
) )
# The network segment to be returned from find_segment
_network_segment = \
network_fakes.FakeNetworkSegment.create_one_network_segment(
attrs={
'network_id': _subnet.network_id,
}
)
columns = ( columns = (
'allocation_pools', 'allocation_pools',
'cidr', 'cidr',
@ -102,6 +110,7 @@ class TestCreateSubnet(TestSubnet):
'name', 'name',
'network_id', 'network_id',
'project_id', 'project_id',
'segment_id',
'subnetpool_id', 'subnetpool_id',
) )
@ -119,6 +128,7 @@ class TestCreateSubnet(TestSubnet):
_subnet.name, _subnet.name,
_subnet.network_id, _subnet.network_id,
_subnet.project_id, _subnet.project_id,
_subnet.segment_id,
_subnet.subnetpool_id, _subnet.subnetpool_id,
) )
@ -136,6 +146,7 @@ class TestCreateSubnet(TestSubnet):
_subnet_from_pool.name, _subnet_from_pool.name,
_subnet_from_pool.network_id, _subnet_from_pool.network_id,
_subnet_from_pool.project_id, _subnet_from_pool.project_id,
_subnet_from_pool.segment_id,
_subnet_from_pool.subnetpool_id, _subnet_from_pool.subnetpool_id,
) )
@ -153,6 +164,7 @@ class TestCreateSubnet(TestSubnet):
_subnet_ipv6.name, _subnet_ipv6.name,
_subnet_ipv6.network_id, _subnet_ipv6.network_id,
_subnet_ipv6.project_id, _subnet_ipv6.project_id,
_subnet_ipv6.segment_id,
_subnet_ipv6.subnetpool_id, _subnet_ipv6.subnetpool_id,
) )
@ -186,6 +198,15 @@ class TestCreateSubnet(TestSubnet):
loaded=True, loaded=True,
) )
# Mock SDK calls for all tests.
self.network.find_network = mock.Mock(return_value=self._network)
self.network.find_segment = mock.Mock(
return_value=self._network_segment
)
self.network.find_subnet_pool = mock.Mock(
return_value=self._subnet_pool
)
def test_create_no_options(self): def test_create_no_options(self):
arglist = [] arglist = []
verifylist = [] verifylist = []
@ -196,11 +217,9 @@ class TestCreateSubnet(TestSubnet):
self.check_parser, self.cmd, arglist, verifylist) self.check_parser, self.cmd, arglist, verifylist)
def test_create_default_options(self): def test_create_default_options(self):
# Mock create_subnet and find_network sdk calls to return the # Mock SDK calls for this test.
# values we want for this test
self.network.create_subnet = mock.Mock(return_value=self._subnet) self.network.create_subnet = mock.Mock(return_value=self._subnet)
self._network.id = self._subnet.network_id self._network.id = self._subnet.network_id
self.network.find_network = mock.Mock(return_value=self._network)
arglist = [ arglist = [
"--subnet-range", self._subnet.cidr, "--subnet-range", self._subnet.cidr,
@ -230,14 +249,10 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.data, data) self.assertEqual(self.data, data)
def test_create_from_subnet_pool_options(self): def test_create_from_subnet_pool_options(self):
# Mock create_subnet, find_subnet_pool, and find_network sdk calls # Mock SDK calls for this test.
# to return the values we want for this test
self.network.create_subnet = \ self.network.create_subnet = \
mock.Mock(return_value=self._subnet_from_pool) mock.Mock(return_value=self._subnet_from_pool)
self._network.id = self._subnet_from_pool.network_id self._network.id = self._subnet_from_pool.network_id
self.network.find_network = mock.Mock(return_value=self._network)
self.network.find_subnet_pool = \
mock.Mock(return_value=self._subnet_pool)
arglist = [ arglist = [
self._subnet_from_pool.name, self._subnet_from_pool.name,
@ -290,11 +305,9 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.data_subnet_pool, data) self.assertEqual(self.data_subnet_pool, data)
def test_create_options_subnet_range_ipv6(self): def test_create_options_subnet_range_ipv6(self):
# Mock create_subnet and find_network sdk calls to return the # Mock SDK calls for this test.
# values we want for this test
self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6) self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6)
self._network.id = self._subnet_ipv6.network_id self._network.id = self._subnet_ipv6.network_id
self.network.find_network = mock.Mock(return_value=self._network)
arglist = [ arglist = [
self._subnet_ipv6.name, self._subnet_ipv6.name,
@ -357,6 +370,59 @@ class TestCreateSubnet(TestSubnet):
self.assertEqual(self.columns, columns) self.assertEqual(self.columns, columns)
self.assertEqual(self.data_ipv6, data) self.assertEqual(self.data_ipv6, data)
def test_create_no_beta_command_options(self):
arglist = [
"--subnet-range", self._subnet.cidr,
"--network-segment", self._network_segment.id,
"--network", self._subnet.network_id,
self._subnet.name,
]
verifylist = [
('name', self._subnet.name),
('subnet_range', self._subnet.cidr),
('network-segment', self._network_segment.id),
('network', self._subnet.network_id),
]
self.app.options.os_beta_command = False
self.assertRaises(tests_utils.ParserException,
self.check_parser, self.cmd, arglist, verifylist)
def test_create_with_network_segment(self):
# Mock SDK calls for this test.
self.network.create_subnet = mock.Mock(return_value=self._subnet)
self._network.id = self._subnet.network_id
arglist = [
"--subnet-range", self._subnet.cidr,
"--network-segment", self._network_segment.id,
"--network", self._subnet.network_id,
self._subnet.name,
]
verifylist = [
('name', self._subnet.name),
('subnet_range', self._subnet.cidr),
('network_segment', self._network_segment.id),
('network', self._subnet.network_id),
('ip_version', self._subnet.ip_version),
('gateway', 'auto'),
]
self.app.options.os_beta_command = True
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.network.create_subnet.assert_called_once_with(**{
'cidr': self._subnet.cidr,
'enable_dhcp': self._subnet.enable_dhcp,
'ip_version': self._subnet.ip_version,
'name': self._subnet.name,
'network_id': self._subnet.network_id,
'segment_id': self._network_segment.id,
})
self.assertEqual(self.columns, columns)
self.assertEqual(self.data, data)
class TestDeleteSubnet(TestSubnet): class TestDeleteSubnet(TestSubnet):
@ -593,6 +659,7 @@ class TestShowSubnet(TestSubnet):
'name', 'name',
'network_id', 'network_id',
'project_id', 'project_id',
'segment_id',
'subnetpool_id', 'subnetpool_id',
) )
@ -610,6 +677,7 @@ class TestShowSubnet(TestSubnet):
_subnet.name, _subnet.name,
_subnet.network_id, _subnet.network_id,
_subnet.tenant_id, _subnet.tenant_id,
_subnet.segment_id,
_subnet.subnetpool_id, _subnet.subnetpool_id,
) )

View File

@ -0,0 +1,6 @@
---
features:
- Add ``--network-segment`` option to the ``subnet create`` command.
This is a beta command option and subject to change. Use global option
``--os-beta-command`` to enable this option.
[Blueprint `routed-networks <https://blueprints.launchpad.net/neutron/+spec/routed-networks>`_]