diff --git a/doc/source/command-beta.rst b/doc/source/command-beta.rst index f0a4f85102..bc8c04c5e7 100644 --- a/doc/source/command-beta.rst +++ b/doc/source/command-beta.rst @@ -12,23 +12,29 @@ To address these challenges, an OpenStackClient command may be labeled as a beta command according to the guidelines below. Such commands may introduce backwards incompatible 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 -by updating the command documentation, help and implementation. +See the examples below on how to label an entire command or +a specific option as a beta by updating the documentation +and implementation. -The initial release note must label the new 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 the command itself must be removed and a new release note +The initial release note must label the new command or option +as a beta. No further release notes are required until the command +or option is no longer a beta. At which time, the beta label or +the command or option itself must be removed and a new release note must be provided. +Beta Command Example +-------------------- + Documentation -------------- +~~~~~~~~~~~~~ The command documentation must label the command as a beta. example list -~~~~~~~~~~~~ +++++++++++++ List examples @@ -42,7 +48,7 @@ List examples os example list Help ----- +~~~~ 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 --------------- +~~~~~~~~~~~~~~ The command must raise a ``CommandError`` exception if beta commands 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): 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") + ) diff --git a/doc/source/command-objects/subnet.rst b/doc/source/command-objects/subnet.rst index c52d73f932..fe77ccfde4 100644 --- a/doc/source/command-objects/subnet.rst +++ b/doc/source/command-objects/subnet.rst @@ -28,6 +28,7 @@ Create new subnet [--ip-version {4,6}] [--ipv6-ra-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] [--ipv6-address-mode {dhcpv6-stateful,dhcpv6-stateless,slaac}] + [--network-segment <network-segment>] --network <network> <name> @@ -107,6 +108,14 @@ Create new subnet 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> Network this subnet belongs to (name or ID) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index a2e3262262..752923f73e 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -142,6 +142,9 @@ def _get_attrs(client_manager, parsed_args, is_create=True): attrs['ipv6_ra_mode'] = parsed_args.ipv6_ra_mode if parsed_args.ipv6_address_mode is not None: 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: gateway = parsed_args.gateway.lower() @@ -255,6 +258,13 @@ class CreateSubnet(command.ShowOne): help=_("IPv6 address mode, " "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( '--network', required=True, diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index a23efc2d1d..50d9899cb0 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -352,7 +352,7 @@ class FakeNetworkSegment(object): # Set default attributes. network_segment_attrs = { - 'id': 'segment-id-' + uuid.uuid4().hex, + 'id': 'network-segment-id-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, 'network_type': 'vlan', 'physical_network': 'physical-network-name-' + uuid.uuid4().hex, @@ -738,9 +738,10 @@ class FakeSubnet(object): 'host_routes': [], 'ip_version': 4, 'gateway_ip': '10.10.10.1', - 'ipv6_address_mode': 'None', - 'ipv6_ra_mode': 'None', - 'subnetpool_id': 'None', + 'ipv6_address_mode': None, + 'ipv6_ra_mode': None, + 'segment_id': None, + 'subnetpool_id': None, } # Overwrite default attributes. diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index a57a030897..99b558c070 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -91,6 +91,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 = ( 'allocation_pools', 'cidr', @@ -105,6 +113,7 @@ class TestCreateSubnet(TestSubnet): 'name', 'network_id', 'project_id', + 'segment_id', 'subnetpool_id', ) @@ -122,6 +131,7 @@ class TestCreateSubnet(TestSubnet): _subnet.name, _subnet.network_id, _subnet.project_id, + _subnet.segment_id, _subnet.subnetpool_id, ) @@ -139,6 +149,7 @@ class TestCreateSubnet(TestSubnet): _subnet_from_pool.name, _subnet_from_pool.network_id, _subnet_from_pool.project_id, + _subnet_from_pool.segment_id, _subnet_from_pool.subnetpool_id, ) @@ -156,6 +167,7 @@ class TestCreateSubnet(TestSubnet): _subnet_ipv6.name, _subnet_ipv6.network_id, _subnet_ipv6.project_id, + _subnet_ipv6.segment_id, _subnet_ipv6.subnetpool_id, ) @@ -189,6 +201,15 @@ class TestCreateSubnet(TestSubnet): 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): arglist = [] verifylist = [] @@ -199,11 +220,9 @@ class TestCreateSubnet(TestSubnet): self.check_parser, self.cmd, arglist, verifylist) def test_create_default_options(self): - # Mock create_subnet and find_network sdk calls to return the - # values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = mock.Mock(return_value=self._subnet) self._network.id = self._subnet.network_id - self.network.find_network = mock.Mock(return_value=self._network) arglist = [ "--subnet-range", self._subnet.cidr, @@ -233,14 +252,10 @@ class TestCreateSubnet(TestSubnet): self.assertEqual(self.data, data) def test_create_from_subnet_pool_options(self): - # Mock create_subnet, find_subnet_pool, and find_network sdk calls - # to return the values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = \ mock.Mock(return_value=self._subnet_from_pool) 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 = [ self._subnet_from_pool.name, @@ -293,11 +308,9 @@ class TestCreateSubnet(TestSubnet): self.assertEqual(self.data_subnet_pool, data) def test_create_options_subnet_range_ipv6(self): - # Mock create_subnet and find_network sdk calls to return the - # values we want for this test + # Mock SDK calls for this test. self.network.create_subnet = mock.Mock(return_value=self._subnet_ipv6) self._network.id = self._subnet_ipv6.network_id - self.network.find_network = mock.Mock(return_value=self._network) arglist = [ self._subnet_ipv6.name, @@ -360,6 +373,59 @@ class TestCreateSubnet(TestSubnet): self.assertEqual(self.columns, columns) 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): @@ -646,6 +712,7 @@ class TestShowSubnet(TestSubnet): 'name', 'network_id', 'project_id', + 'segment_id', 'subnetpool_id', ) @@ -663,6 +730,7 @@ class TestShowSubnet(TestSubnet): _subnet.name, _subnet.network_id, _subnet.tenant_id, + _subnet.segment_id, _subnet.subnetpool_id, ) diff --git a/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml b/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml new file mode 100644 index 0000000000..6c4a185c7a --- /dev/null +++ b/releasenotes/notes/bp-routed-networks-86a24f34d86fca53.yaml @@ -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>`_]