diff --git a/doc/source/command-objects/network-segment.rst b/doc/source/command-objects/network-segment.rst new file mode 100644 index 000000000..8e177d6af --- /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 a6d940479..2d38c8a11 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 000000000..a99809381 --- /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 000000000..d8a91fd20 --- /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 fb7a957af..7fb1daa98 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 ce6f63e8c..587fdc1ab 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -275,6 +275,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 000000000..2822581c6 --- /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 319c1c114..8dead7184 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 000000000..82080f7e6 --- /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 f52e8bc62..78450c007 100644 --- a/setup.cfg +++ b/setup.cfg @@ -344,6 +344,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