From 6a55e05cbf72509898ccc490ca894d766f34d9dc Mon Sep 17 00:00:00 2001 From: Richard Theis Date: Tue, 8 Mar 2016 15:18:16 -0600 Subject: [PATCH] Add network segment command object Add network segment command object in support of routed networks. This patch set includes documentation, unit tests and functional tests (currently skipped until segments enabled in neutron by default) for the following new commands: - "os network segment list" - "os network segment show" These new commands are currently marked as beta commands. Change-Id: I1a79b48dc6820fe2a39fcceb11c8cae3bda413a0 Partially-Implements: blueprint routed-networks --- .../command-objects/network-segment.rst | 54 +++++ doc/source/commands.rst | 1 + .../tests/network/v2/test_network_segment.py | 60 ++++++ openstackclient/network/v2/network_segment.py | 124 +++++++++++ openstackclient/tests/fakes.py | 5 + openstackclient/tests/network/v2/fakes.py | 52 +++++ .../tests/network/v2/test_network_segment.py | 199 ++++++++++++++++++ openstackclient/tests/utils.py | 1 + .../bp-routed-networks-3eea4978c93aa126.yaml | 7 + setup.cfg | 3 + 10 files changed, 506 insertions(+) create mode 100644 doc/source/command-objects/network-segment.rst create mode 100644 functional/tests/network/v2/test_network_segment.py create mode 100644 openstackclient/network/v2/network_segment.py create mode 100644 openstackclient/tests/network/v2/test_network_segment.py create mode 100644 releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/command-objects/network-segment.rst new file mode 100644 index 0000000000..8e177d6af5 --- /dev/null +++ b/doc/source/command-objects/network-segment.rst @@ -0,0 +1,54 @@ +=============== +network segment +=============== + +A **network segment** is an isolated Layer 2 segment within a network. +A network may contain multiple network segments. Depending on the +network configuration, Layer 2 connectivity between network segments +within a network may not be guaranteed. + +Network v2 + +network segment list +-------------------- + +List network segments + +.. caution:: This is a beta command and subject to change. + Use global option ``--enable-beta-commands`` to + enable this command. + +.. program:: network segment list +.. code:: bash + + os network segment list + [--long] + [--network ] + +.. option:: --long + + List additional fields in output + +.. option:: --network + + List network segments that belong to this network (name or ID) + +network segment show +-------------------- + +Display network segment details + +.. caution:: This is a beta command and subject to change. + Use global option ``--enable-beta-commands`` to + enable this command. + +.. program:: network segment show +.. code:: bash + + os network segment show + + +.. _network_segment_show-segment: +.. describe:: + + Network segment to display (ID only) diff --git a/doc/source/commands.rst b/doc/source/commands.rst index 12542d1c73..118fd5d5ba 100644 --- a/doc/source/commands.rst +++ b/doc/source/commands.rst @@ -104,6 +104,7 @@ referring to both Compute and Volume quotas. * ``mapping``: (**Identity**) a definition to translate identity provider attributes to Identity concepts * ``module``: (**Internal**) - installed Python modules in the OSC process * ``network``: (**Compute**, **Network**) - a virtual network for connecting servers and other resources +* ``network segment``: (**Network**) - a segment of a virtual network * ``object``: (**Object Storage**) a single file in the Object Storage * ``object store account``: (**Object Storage**) owns a group of Object Storage resources * ``policy``: (**Identity**) determines authorization diff --git a/functional/tests/network/v2/test_network_segment.py b/functional/tests/network/v2/test_network_segment.py new file mode 100644 index 0000000000..a998093811 --- /dev/null +++ b/functional/tests/network/v2/test_network_segment.py @@ -0,0 +1,60 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import testtools +import uuid + +from functional.common import test + + +# NOTE(rtheis): Routed networks is still a WIP and not enabled by default. +@testtools.skip("bp/routed-networks") +class NetworkSegmentTests(test.TestCase): + """Functional tests for network segment. """ + NETWORK_NAME = uuid.uuid4().hex + PHYSICAL_NETWORK_NAME = uuid.uuid4().hex + NETWORK_SEGMENT_ID = None + NETWORK_ID = None + + @classmethod + def setUpClass(cls): + # Create a network for the segment. + opts = cls.get_show_opts(['id']) + raw_output = cls.openstack('network create ' + cls.NETWORK_NAME + opts) + cls.NETWORK_ID = raw_output.strip('\n') + + # Get the segment for the network. + opts = cls.get_show_opts(['ID', 'Network']) + raw_output = cls.openstack('--enable-beta-commands ' + 'network segment list ' + ' --network ' + cls.NETWORK_NAME + + ' ' + opts) + raw_output_row = raw_output.split('\n')[0] + cls.NETWORK_SEGMENT_ID = raw_output_row.split(' ')[0] + + @classmethod + def tearDownClass(cls): + raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) + cls.assertOutput('', raw_output) + + def test_network_segment_list(self): + opts = self.get_list_opts(['ID']) + raw_output = self.openstack('--enable-beta-commands ' + 'network segment list' + opts) + self.assertIn(self.NETWORK_SEGMENT_ID, raw_output) + + def test_network_segment_show(self): + opts = self.get_show_opts(['network_id']) + raw_output = self.openstack('--enable-beta-commands ' + 'network segment show ' + + self.NETWORK_SEGMENT_ID + opts) + self.assertEqual(self.NETWORK_ID + "\n", raw_output) diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py new file mode 100644 index 0000000000..d8a91fd200 --- /dev/null +++ b/openstackclient/network/v2/network_segment.py @@ -0,0 +1,124 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Network segment action implementations""" + +# TODO(rtheis): Add description and name properties when support is available. + +from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.i18n import _ + + +class ListNetworkSegment(command.Lister): + """List network segments + + (Caution: This is a beta command and subject to change. + Use global option --enable-beta-commands to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(ListNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_('List additional fields in output'), + ) + parser.add_argument( + '--network', + metavar='', + help=_('List network segments that belong to this ' + 'network (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + if not self.app.options.enable_beta_commands: + msg = _('Caution: This is a beta command and subject to ' + 'change. Use global option --enable-beta-commands ' + 'to enable this command.') + raise exceptions.CommandError(msg) + + network_client = self.app.client_manager.network + + filters = {} + if parsed_args.network: + _network = network_client.find_network( + parsed_args.network, + ignore_missing=False + ) + filters = {'network_id': _network.id} + data = network_client.segments(**filters) + + headers = ( + 'ID', + 'Network', + 'Network Type', + 'Segment', + ) + columns = ( + 'id', + 'network_id', + 'network_type', + 'segmentation_id', + ) + if parsed_args.long: + headers = headers + ( + 'Physical Network', + ) + columns = columns + ( + 'physical_network', + ) + + return (headers, + (utils.get_item_properties( + s, columns, + formatters={}, + ) for s in data)) + + +class ShowNetworkSegment(command.ShowOne): + """Display network segment details + + (Caution: This is a beta command and subject to change. + Use global option --enable-beta-commands to enable + this command) + """ + + def get_parser(self, prog_name): + parser = super(ShowNetworkSegment, self).get_parser(prog_name) + parser.add_argument( + 'network_segment', + metavar='', + help=_('Network segment to display (ID only)'), + ) + return parser + + def take_action(self, parsed_args): + if not self.app.options.enable_beta_commands: + msg = _('Caution: This is a beta command and subject to ' + 'change. Use global option --enable-beta-commands ' + 'to enable this command.') + raise exceptions.CommandError(msg) + + client = self.app.client_manager.network + obj = client.find_segment( + parsed_args.network_segment, + ignore_missing=False + ) + columns = tuple(sorted(obj.keys())) + data = utils.get_item_properties(obj, columns) + return (columns, data) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 229b46529c..ac91257ebe 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -97,6 +97,11 @@ class FakeApp(object): self.log = _log +class FakeOptions(object): + def __init__(self, **kwargs): + self.enable_beta_commands = False + + class FakeClient(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 417cf26ee7..8507e27804 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -256,6 +256,58 @@ class FakeNetwork(object): return mock.MagicMock(side_effect=networks) +class FakeNetworkSegment(object): + """Fake one or more network segments.""" + + @staticmethod + def create_one_network_segment(attrs=None): + """Create a fake network segment. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object faking the network segment + """ + attrs = attrs or {} + + # Set default attributes. + network_segment_attrs = { + 'id': 'segment-id-' + uuid.uuid4().hex, + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'network_type': 'vlan', + 'physical_network': 'physical-network-name-' + uuid.uuid4().hex, + 'segmentation_id': 1024, + } + + # Overwrite default attributes. + network_segment_attrs.update(attrs) + + network_segment = fakes.FakeResource( + info=copy.deepcopy(network_segment_attrs), + loaded=True + ) + + return network_segment + + @staticmethod + def create_network_segments(attrs=None, count=2): + """Create multiple fake network segments. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of network segments to fake + :return: + A list of FakeResource objects faking the network segments + """ + network_segments = [] + for i in range(0, count): + network_segments.append( + FakeNetworkSegment.create_one_network_segment(attrs) + ) + return network_segments + + class FakePort(object): """Fake one or more ports.""" diff --git a/openstackclient/tests/network/v2/test_network_segment.py b/openstackclient/tests/network/v2/test_network_segment.py new file mode 100644 index 0000000000..2822581c67 --- /dev/null +++ b/openstackclient/tests/network/v2/test_network_segment.py @@ -0,0 +1,199 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock + +from openstackclient.common import exceptions +from openstackclient.network.v2 import network_segment +from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils + + +class TestNetworkSegment(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestNetworkSegment, self).setUp() + + # Enable beta commands. + self.app.options.enable_beta_commands = True + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + +class TestListNetworkSegment(TestNetworkSegment): + _network = network_fakes.FakeNetwork.create_one_network() + _network_segments = \ + network_fakes.FakeNetworkSegment.create_network_segments(count=3) + + columns = ( + 'ID', + 'Network', + 'Network Type', + 'Segment', + ) + columns_long = columns + ( + 'Physical Network', + ) + + data = [] + for _network_segment in _network_segments: + data.append(( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.segmentation_id, + )) + + data_long = [] + for _network_segment in _network_segments: + data_long.append(( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.segmentation_id, + _network_segment.physical_network, + )) + + def setUp(self): + super(TestListNetworkSegment, self).setUp() + + # Get the command object to test + self.cmd = network_segment.ListNetworkSegment(self.app, self.namespace) + + self.network.find_network = mock.Mock(return_value=self._network) + self.network.segments = mock.Mock(return_value=self._network_segments) + + def test_list_no_option(self): + arglist = [] + verifylist = [ + ('long', False), + ('network', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_list_no_beta_commands(self): + self.app.options.enable_beta_commands = False + parsed_args = self.check_parser(self.cmd, [], []) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('network', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + def test_list_network(self): + arglist = [ + '--network', + self._network.id, + ] + verifylist = [ + ('long', False), + ('network', self._network.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.segments.assert_called_once_with( + **{'network_id': self._network.id} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestShowNetworkSegment(TestNetworkSegment): + + # The network segment to show. + _network_segment = \ + network_fakes.FakeNetworkSegment.create_one_network_segment() + + columns = ( + 'id', + 'network_id', + 'network_type', + 'physical_network', + 'segmentation_id', + ) + + data = ( + _network_segment.id, + _network_segment.network_id, + _network_segment.network_type, + _network_segment.physical_network, + _network_segment.segmentation_id, + ) + + def setUp(self): + super(TestShowNetworkSegment, self).setUp() + + self.network.find_segment = mock.Mock( + return_value=self._network_segment + ) + + # Get the command object to test + self.cmd = network_segment.ShowNetworkSegment(self.app, self.namespace) + + def test_show_no_options(self): + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, [], []) + + def test_show_no_beta_commands(self): + arglist = [ + self._network_segment.id, + ] + verifylist = [ + ('network_segment', self._network_segment.id), + ] + self.app.options.enable_beta_commands = False + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises(exceptions.CommandError, self.cmd.take_action, + parsed_args) + + def test_show_all_options(self): + arglist = [ + self._network_segment.id, + ] + verifylist = [ + ('network_segment', self._network_segment.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_segment.assert_called_once_with( + self._network_segment.id, + ignore_missing=False + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py index 319c1c1142..8dead71840 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/utils.py @@ -60,6 +60,7 @@ class TestCommand(TestCase): self.fake_log = fakes.FakeLog() self.app = fakes.FakeApp(self.fake_stdout, self.fake_log) self.app.client_manager = fakes.FakeClientManager() + self.app.options = fakes.FakeOptions() def check_parser(self, cmd, args, verify_args): cmd_parser = cmd.get_parser('check_parser') diff --git a/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml new file mode 100644 index 0000000000..82080f7e6a --- /dev/null +++ b/releasenotes/notes/bp-routed-networks-3eea4978c93aa126.yaml @@ -0,0 +1,7 @@ +--- +features: + - Add support for the ``network segment`` command object via the + ``network segment list`` and ``network segment show`` commands. + These are beta commands and subject to change. Use global option + ``--enable-beta-commands`` to enable these commands. + [Blueprint `routed-networks `_] diff --git a/setup.cfg b/setup.cfg index a62f5d25cd..9492de70df 100644 --- a/setup.cfg +++ b/setup.cfg @@ -341,6 +341,9 @@ openstack.network.v2 = network_set = openstackclient.network.v2.network:SetNetwork network_show = openstackclient.network.v2.network:ShowNetwork + network_segment_list = openstackclient.network.v2.network_segment:ListNetworkSegment + network_segment_show = openstackclient.network.v2.network_segment:ShowNetworkSegment + port_create = openstackclient.network.v2.port:CreatePort port_delete = openstackclient.network.v2.port:DeletePort port_list = openstackclient.network.v2.port:ListPort