From fa8c8d26a7696d169b0b9d5aaf6b723d8feee08a Mon Sep 17 00:00:00 2001 From: Slawek Kaplonski Date: Wed, 7 Apr 2021 23:40:16 +0200 Subject: [PATCH] Add support for Neutron's L3 conntrack helper resource Neutron has got CRUD API for L3 conntrack helper since some time. This patch adds support for it in the OSC. OpenStack SDK supports that since [1] This patch also bumps minimum OpenStack SDK version to the 0.56.0 as that version introduced support for the Neutron's L3 conntrack helper. [1] https://review.opendev.org/c/openstack/openstacksdk/+/782870 Change-Id: I55604182ae50b6ad70c8bc1f7efad8859f191269 --- .zuul.yaml | 1 + .../network-l3-conntrack-helper.rst | 8 + .../network/v2/l3_conntrack_helper.py | 255 ++++++++++++++ .../network/v2/test_l3_conntrack_helper.py | 145 ++++++++ .../tests/unit/network/v2/fakes.py | 75 +++++ .../network/v2/test_l3_conntrack_helper.py | 316 ++++++++++++++++++ .../L3-conntrack-helper-bd0d9da041747e84.yaml | 8 + setup.cfg | 6 + 8 files changed, 814 insertions(+) create mode 100644 doc/source/cli/command-objects/network-l3-conntrack-helper.rst create mode 100644 openstackclient/network/v2/l3_conntrack_helper.py create mode 100644 openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py create mode 100644 openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py create mode 100644 releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml diff --git a/.zuul.yaml b/.zuul.yaml index e7b01da409..e1c1f970af 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -106,6 +106,7 @@ q-metering: true q-qos: true neutron-tag-ports-during-bulk-creation: true + neutron-conntrack-helper: true devstack_localrc: Q_AGENT: openvswitch Q_ML2_TENANT_NETWORK_TYPE: vxlan diff --git a/doc/source/cli/command-objects/network-l3-conntrack-helper.rst b/doc/source/cli/command-objects/network-l3-conntrack-helper.rst new file mode 100644 index 0000000000..badbab6337 --- /dev/null +++ b/doc/source/cli/command-objects/network-l3-conntrack-helper.rst @@ -0,0 +1,8 @@ +=========================== +network l3 conntrack helper +=========================== + +Network v2 + +.. autoprogram-cliff:: openstack.network.v2 + :command: network l3 conntrack helper * diff --git a/openstackclient/network/v2/l3_conntrack_helper.py b/openstackclient/network/v2/l3_conntrack_helper.py new file mode 100644 index 0000000000..dae259273f --- /dev/null +++ b/openstackclient/network/v2/l3_conntrack_helper.py @@ -0,0 +1,255 @@ +# 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. +# + +"""L3 Conntrack Helper action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.network import sdk_utils + + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = {} + return sdk_utils.get_osc_show_columns_for_sdk_resource(item, column_map) + + +def _get_attrs(client, parsed_args): + router = client.find_router(parsed_args.router, ignore_missing=False) + attrs = {'router_id': router.id} + if parsed_args.helper: + attrs['helper'] = parsed_args.helper + if parsed_args.protocol: + attrs['protocol'] = parsed_args.protocol + if parsed_args.port: + attrs['port'] = parsed_args.port + + return attrs + + +class CreateConntrackHelper(command.ShowOne): + _description = _("Create a new L3 conntrack helper") + + def get_parser(self, prog_name): + parser = super(CreateConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router for which conntrack helper will be created') + ) + parser.add_argument( + '--helper', + required=True, + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + required=True, + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + required=True, + metavar='', + type=int, + help=_('The network port for the netfilter conntrack target rule') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + attrs = _get_attrs(client, parsed_args) + obj = client.create_conntrack_helper(attrs.pop('router_id'), **attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteConntrackHelper(command.Command): + _description = _("Delete L3 conntrack helper") + + def get_parser(self, prog_name): + parser = super(DeleteConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_ids', + metavar='', + nargs='+', + help=_('The ID of the conntrack helper(s) to delete') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + router = client.find_router(parsed_args.router, ignore_missing=False) + for ct_helper in parsed_args.conntrack_helper_ids: + try: + client.delete_conntrack_helper( + ct_helper, router.id, ignore_missing=False) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete L3 conntrack helper with " + "ID '%(ct_helper)s': %(e)s"), + {'ct_helper': ct_helper, 'e': e}) + + if result > 0: + total = len(parsed_args.conntrack_helper_ids) + msg = (_("%(result)s of %(total)s L3 conntrack helpers failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListConntrackHelper(command.Lister): + _description = _("List L3 conntrack helpers") + + def get_parser(self, prog_name): + parser = super(ListConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + '--helper', + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + metavar='', + help=_('The network port for the netfilter conntrack target rule') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'router_id', + 'helper', + 'protocol', + 'port', + ) + column_headers = ( + 'ID', + 'Router ID', + 'Helper', + 'Protocol', + 'Port', + ) + attrs = _get_attrs(client, parsed_args) + data = client.conntrack_helpers(attrs.pop('router_id'), **attrs) + + return (column_headers, + (utils.get_item_properties( + s, columns, formatters={}, + ) for s in data)) + + +class SetConntrackHelper(command.Command): + _description = _("Set L3 conntrack helper properties") + + def get_parser(self, prog_name): + parser = super(SetConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_id', + metavar='', + help=_('The ID of the conntrack helper(s)') + ) + parser.add_argument( + '--helper', + metavar='', + help=_('The netfilter conntrack helper module') + ) + parser.add_argument( + '--protocol', + metavar='', + help=_('The network protocol for the netfilter conntrack target ' + 'rule') + ) + parser.add_argument( + '--port', + metavar='', + type=int, + help=_('The network port for the netfilter conntrack target rule') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(client, parsed_args) + if attrs: + client.update_conntrack_helper( + parsed_args.conntrack_helper_id, attrs.pop('router_id'), + **attrs) + + +class ShowConntrackHelper(command.ShowOne): + _description = _("Display L3 conntrack helper details") + + def get_parser(self, prog_name): + parser = super(ShowConntrackHelper, self).get_parser(prog_name) + parser.add_argument( + 'router', + metavar='', + help=_('Router that the conntrack helper belong to') + ) + parser.add_argument( + 'conntrack_helper_id', + metavar='', + help=_('The ID of the conntrack helper') + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + router = client.find_router(parsed_args.router, ignore_missing=False) + obj = client.get_conntrack_helper( + parsed_args.conntrack_helper_id, router.id) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) diff --git a/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py b/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py new file mode 100644 index 0000000000..bbb9a7cd97 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_l3_conntrack_helper.py @@ -0,0 +1,145 @@ +# 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 json +import uuid + +from openstackclient.tests.functional.network.v2 import common + + +class L3ConntrackHelperTests(common.NetworkTests): + + def setUp(self): + super(L3ConntrackHelperTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + if not self.is_extension_enabled('l3-conntrack-helper'): + self.skipTest("No l3-conntrack-helper extension present") + if not self.is_extension_enabled('expose-l3-conntrack-helper'): + self.skipTest("No expose-l3-conntrack-helper extension present") + + def _create_router(self): + router_name = uuid.uuid4().hex + json_output = json.loads(self.openstack( + 'router create -f json ' + router_name + )) + self.assertIsNotNone(json_output['id']) + router_id = json_output['id'] + self.addCleanup(self.openstack, 'router delete ' + router_id) + return router_id + + def _create_helpers(self, router_id, helpers): + created_helpers = [] + for helper in helpers: + output = json.loads(self.openstack( + 'network l3 conntrack helper create %(router)s ' + '--helper %(helper)s --protocol %(protocol)s --port %(port)s ' + '-f json' % {'router': router_id, + 'helper': helper['helper'], + 'protocol': helper['protocol'], + 'port': helper['port']})) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) + self.assertEqual(helper['port'], output['port']) + created_helpers.append(output) + return created_helpers + + def test_l3_conntrack_helper_create_and_delete(self): + """Test create, delete multiple""" + + helpers = [ + { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69 + }, { + 'helper': 'ftp', + 'protocol': 'tcp', + 'port': 21 + } + ] + router_id = self._create_router() + created_helpers = self._create_helpers(router_id, helpers) + ct_ids = " ".join([ct['id'] for ct in created_helpers]) + + raw_output = self.openstack( + '--debug network l3 conntrack helper delete %(router)s ' + '%(ct_ids)s' % { + 'router': router_id, 'ct_ids': ct_ids}) + self.assertOutput('', raw_output) + + def test_l3_conntrack_helper_list(self): + helpers = [ + { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69 + }, { + 'helper': 'ftp', + 'protocol': 'tcp', + 'port': 21 + } + ] + expected_helpers = [ + { + 'Helper': 'tftp', + 'Protocol': 'udp', + 'Port': 69 + }, { + 'Helper': 'ftp', + 'Protocol': 'tcp', + 'Port': 21 + } + ] + router_id = self._create_router() + self._create_helpers(router_id, helpers) + output = json.loads(self.openstack( + 'network l3 conntrack helper list %s -f json ' % router_id + )) + for ct in output: + self.assertEqual(router_id, ct.pop('Router ID')) + ct.pop("ID") + self.assertIn(ct, expected_helpers) + + def test_l3_conntrack_helper_set_and_show(self): + helper = { + 'helper': 'tftp', + 'protocol': 'udp', + 'port': 69} + router_id = self._create_router() + created_helper = self._create_helpers(router_id, [helper])[0] + output = json.loads(self.openstack( + 'network l3 conntrack helper show %(router_id)s %(ct_id)s ' + '-f json' % { + 'router_id': router_id, 'ct_id': created_helper['id']})) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) + self.assertEqual(helper['port'], output['port']) + + raw_output = self.openstack( + 'network l3 conntrack helper set %(router_id)s %(ct_id)s ' + '--port %(port)s ' % { + 'router_id': router_id, + 'ct_id': created_helper['id'], + 'port': helper['port'] + 1}) + self.assertOutput('', raw_output) + + output = json.loads(self.openstack( + 'network l3 conntrack helper show %(router_id)s %(ct_id)s ' + '-f json' % { + 'router_id': router_id, 'ct_id': created_helper['id']})) + self.assertEqual(helper['port'] + 1, output['port']) + self.assertEqual(helper['helper'], output['helper']) + self.assertEqual(helper['protocol'], output['protocol']) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index e5023d43c5..ab77d719c0 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1973,3 +1973,78 @@ class FakeFloatingIPPortForwarding(object): ) return mock.Mock(side_effect=port_forwardings) + + +class FakeL3ConntrackHelper(object): + """"Fake one or more L3 conntrack helper""" + + @staticmethod + def create_one_l3_conntrack_helper(attrs=None): + """Create a fake L3 conntrack helper. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with protocol, port, etc. + """ + attrs = attrs or {} + router_id = ( + attrs.get('router_id') or 'router-id-' + uuid.uuid4().hex + ) + # Set default attributes. + ct_attrs = { + 'id': uuid.uuid4().hex, + 'router_id': router_id, + 'helper': 'tftp', + 'protocol': 'tcp', + 'port': randint(1, 65535), + } + + # Overwrite default attributes. + ct_attrs.update(attrs) + + ct = fakes.FakeResource( + info=copy.deepcopy(ct_attrs), + loaded=True + ) + return ct + + @staticmethod + def create_l3_conntrack_helpers(attrs=None, count=2): + """Create multiple fake L3 Conntrack helpers. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of L3 Conntrack helper rule to fake + :return: + A list of FakeResource objects faking the Conntrack helpers + """ + ct_helpers = [] + for i in range(0, count): + ct_helpers.append( + FakeL3ConntrackHelper.create_one_l3_conntrack_helper(attrs) + ) + return ct_helpers + + @staticmethod + def get_l3_conntrack_helpers(ct_helpers=None, count=2): + """Get a list of faked L3 Conntrack helpers. + + If ct_helpers list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List ct_helpers: + A list of FakeResource objects faking conntrack helpers + :param int count: + The number of L3 conntrack helpers to fake + :return: + An iterable Mock object with side_effect set to a list of faked + L3 conntrack helpers + """ + if ct_helpers is None: + ct_helpers = ( + FakeL3ConntrackHelper.create_l3_conntrack_helpers(count) + ) + + return mock.Mock(side_effect=ct_helpers) diff --git a/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py b/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py new file mode 100644 index 0000000000..1676c9ffad --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_l3_conntrack_helper.py @@ -0,0 +1,316 @@ +# 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. +# + +from unittest import mock + +from osc_lib import exceptions + +from openstackclient.network.v2 import l3_conntrack_helper +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestConntrackHelper(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestConntrackHelper, self).setUp() + # Get a shortcut to the network client + self.network = self.app.client_manager.network + self.router = network_fakes.FakeRouter.create_one_router() + self.network.find_router = mock.Mock(return_value=self.router) + + +class TestCreateL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestCreateL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.columns = ( + 'helper', + 'id', + 'port', + 'protocol', + 'router_id' + ) + + self.data = ( + self.ct_helper.helper, + self.ct_helper.id, + self.ct_helper.port, + self.ct_helper.protocol, + self.ct_helper.router_id + ) + self.network.create_conntrack_helper = mock.Mock( + return_value=self.ct_helper) + + # Get the command object to test + self.cmd = l3_conntrack_helper.CreateConntrackHelper(self.app, + self.namespace) + + def test_create_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_default_options(self): + arglist = [ + '--helper', 'tftp', + '--protocol', 'udp', + '--port', '69', + self.router.id, + ] + + verifylist = [ + ('helper', 'tftp'), + ('protocol', 'udp'), + ('port', 69), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_conntrack_helper.assert_called_once_with( + self.router.id, + **{'helper': 'tftp', 'protocol': 'udp', + 'port': 69} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_wrong_options(self): + arglist = [ + '--protocol', 'udp', + '--port', '69', + self.router.id, + ] + + self.assertRaises( + tests_utils.ParserException, + self.check_parser, + self.cmd, arglist, None) + + +class TestDeleteL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestDeleteL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.network.delete_conntrack_helper = mock.Mock( + return_value=None) + + # Get the command object to test + self.cmd = l3_conntrack_helper.DeleteConntrackHelper(self.app, + self.namespace) + + def test_delete(self): + arglist = [ + self.ct_helper.router_id, + self.ct_helper.id + ] + verifylist = [ + ('conntrack_helper_ids', [self.ct_helper.id]), + ('router', self.ct_helper.router_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.delete_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id, + ignore_missing=False) + self.assertIsNone(result) + + def test_delete_error(self): + arglist = [ + self.router.id, + self.ct_helper.id + ] + verifylist = [ + ('conntrack_helper_ids', [self.ct_helper.id]), + ('router', self.router.id), + ] + self.network.delete_conntrack_helper.side_effect = Exception( + 'Error message') + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, parsed_args) + + +class TestListL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestListL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + ct_helpers = ( + network_fakes.FakeL3ConntrackHelper.create_l3_conntrack_helpers( + attrs, count=3)) + self.columns = ( + 'ID', + 'Router ID', + 'Helper', + 'Protocol', + 'Port', + ) + self.data = [] + for ct_helper in ct_helpers: + self.data.append(( + ct_helper.id, + ct_helper.router_id, + ct_helper.helper, + ct_helper.protocol, + ct_helper.port, + )) + self.network.conntrack_helpers = mock.Mock( + return_value=ct_helpers) + + # Get the command object to test + self.cmd = l3_conntrack_helper.ListConntrackHelper(self.app, + self.namespace) + + def test_conntrack_helpers_list(self): + arglist = [ + self.router.id + ] + verifylist = [ + ('router', self.router.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.conntrack_helpers.assert_called_once_with( + self.router.id) + self.assertEqual(self.columns, columns) + list_data = list(data) + self.assertEqual(len(self.data), len(list_data)) + for index in range(len(list_data)): + self.assertEqual(self.data[index], list_data[index]) + + +class TestSetL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestSetL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.network.update_conntrack_helper = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = l3_conntrack_helper.SetConntrackHelper(self.app, + self.namespace) + + def test_set_nothing(self): + arglist = [ + self.router.id, + self.ct_helper.id, + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = (self.cmd.take_action(parsed_args)) + + self.network.update_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id + ) + self.assertIsNone(result) + + def test_set_port(self): + arglist = [ + self.router.id, + self.ct_helper.id, + '--port', '124', + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ('port', 124), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = (self.cmd.take_action(parsed_args)) + + self.network.update_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id, port=124 + ) + self.assertIsNone(result) + + +class TestShowL3ConntrackHelper(TestConntrackHelper): + + def setUp(self): + super(TestShowL3ConntrackHelper, self).setUp() + attrs = {'router_id': self.router.id} + self.ct_helper = ( + network_fakes.FakeL3ConntrackHelper.create_one_l3_conntrack_helper( + attrs)) + self.columns = ( + 'helper', + 'id', + 'port', + 'protocol', + 'router_id' + ) + + self.data = ( + self.ct_helper.helper, + self.ct_helper.id, + self.ct_helper.port, + self.ct_helper.protocol, + self.ct_helper.router_id + ) + self.network.get_conntrack_helper = mock.Mock( + return_value=self.ct_helper) + + # Get the command object to test + self.cmd = l3_conntrack_helper.ShowConntrackHelper(self.app, + self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_default_options(self): + arglist = [ + self.router.id, + self.ct_helper.id, + ] + verifylist = [ + ('router', self.router.id), + ('conntrack_helper_id', self.ct_helper.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.get_conntrack_helper.assert_called_once_with( + self.ct_helper.id, self.router.id + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml b/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml new file mode 100644 index 0000000000..de5348dc16 --- /dev/null +++ b/releasenotes/notes/L3-conntrack-helper-bd0d9da041747e84.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add new commands ``network l3 conntrack helper create``, + ``network l3 conntrack helper set``, ``network l3 conntrack helper show``, + ``network l3 conntrack helper set`` and + ``network l3 conntrack helper delete`` to support Neutron L3 conntrack + helper CRUD operations. diff --git a/setup.cfg b/setup.cfg index d6d7a3d2b0..7084c70bf3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -448,6 +448,12 @@ openstack.network.v2 = network_show = openstackclient.network.v2.network:ShowNetwork network_unset = openstackclient.network.v2.network:UnsetNetwork + network_l3_conntrack_helper_create = openstackclient.network.v2.l3_conntrack_helper:CreateConntrackHelper + network_l3_conntrack_helper_delete = openstackclient.network.v2.l3_conntrack_helper:DeleteConntrackHelper + network_l3_conntrack_helper_list = openstackclient.network.v2.l3_conntrack_helper:ListConntrackHelper + network_l3_conntrack_helper_set = openstackclient.network.v2.l3_conntrack_helper:SetConntrackHelper + network_l3_conntrack_helper_show = openstackclient.network.v2.l3_conntrack_helper:ShowConntrackHelper + network_meter_create = openstackclient.network.v2.network_meter:CreateMeter network_meter_delete = openstackclient.network.v2.network_meter:DeleteMeter network_meter_list = openstackclient.network.v2.network_meter:ListMeter