From f13e3d5764f116cb8cafd89672801048f315e67a Mon Sep 17 00:00:00 2001 From: Thomas Bachman Date: Sun, 24 Jan 2021 17:56:47 +0000 Subject: [PATCH] Add support for ERSPAN Add support for ERSPAN extension on the openstack port resource. Change-Id: Ie9c5b429dfab774faef92d514be9110f6795991b --- gbpclient/gbp/v2_0/port.py | 111 +++++++++++++++++++++++ gbpclient/tests/unit/test_port.py | 145 ++++++++++++++++++++++++++++++ setup.cfg | 14 ++- 3 files changed, 267 insertions(+), 3 deletions(-) create mode 100644 gbpclient/gbp/v2_0/port.py create mode 100644 gbpclient/tests/unit/test_port.py diff --git a/gbpclient/gbp/v2_0/port.py b/gbpclient/gbp/v2_0/port.py new file mode 100644 index 0000000..46ab6d6 --- /dev/null +++ b/gbpclient/gbp/v2_0/port.py @@ -0,0 +1,111 @@ +# 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. +# + +""" +Port extension implementations +""" + +from cliff import hooks +from osc_lib.cli import parseractions + +from openstack.network.v2 import port as port_sdk +from openstack import resource + +from openstackclient.i18n import _ +from openstackclient.network.v2 import port + + +_get_attrs_port_new = port._get_attrs + + +def _convert_erspan_config(parsed_args): + ops = [] + for opt in parsed_args.apic_erspan_config: + addr = {} + addr['dest_ip'] = opt['dest-ip'] + addr['flow_id'] = opt['flow-id'] + if 'direction' in opt: + addr['direction'] = opt['direction'] + ops.append(addr) + return ops + + +def _get_attrs_port_extension(client_manager, parsed_args): + attrs = _get_attrs_port_new(client_manager, parsed_args) + if parsed_args.apic_synchronization_state: + attrs['apic:synchronization_state' + ] = parsed_args.apic_synchronization_state + if parsed_args.apic_erspan_config: + attrs['apic:erspan_config' + ] = _convert_erspan_config(parsed_args) + if parsed_args.no_apic_erspan_config: + attrs['apic:erspan_config'] = [] + return attrs + + +port._get_attrs = _get_attrs_port_extension + +port_sdk.Port.apic_synchronization_state = resource.Body( + 'apic:synchronization_state') +port_sdk.Port.apic_erspan_config = resource.Body('apic:erspan_config') + + +class CreateAndSetPortExtension(hooks.CommandHook): + + def get_parser(self, parser): + parser.add_argument( + '--apic-synchronization-state', + metavar="", + dest='apic_synchronization_state', + help=_("Apic synchronization state") + ) + parser.add_argument( + '--apic-erspan-config', + metavar="", + dest='apic_erspan_config', + action=parseractions.MultiKeyValueAction, + required_keys=['flow-id', 'dest-ip'], + optional_keys=['direction'], + help=_("Apic ERSPAN configuration") + ) + parser.add_argument( + '--no-apic-erspan-config', + dest='no_apic_erspan_config', + action='store_true', + help=_("Apic ERSPAN configuration") + ) + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code + + +class ShowPortExtension(hooks.CommandHook): + + def get_parser(self, parser): + return parser + + def get_epilog(self): + return '' + + def before(self, parsed_args): + return parsed_args + + def after(self, parsed_args, return_code): + return return_code diff --git a/gbpclient/tests/unit/test_port.py b/gbpclient/tests/unit/test_port.py new file mode 100644 index 0000000..97d2b61 --- /dev/null +++ b/gbpclient/tests/unit/test_port.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. +# + +from unittest import mock + +from gbpclient.gbp.v2_0 import port as port_ext +from gbpclient.tests.unit import test_cli20 +from openstackclient.network.v2 import port +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.network.v2 import test_port + + +# Tests for port create with APIC extensions +# +class TestPortCreate(test_port.TestPort, test_cli20.CLITestV20Base): + + _port = test_port.TestCreatePort._port + extension_details = ( + network_fakes.FakeExtension.create_one_extension() + ) + + def setUp(self): + super(TestPortCreate, self).setUp() + self.app.client_manager.network.find_extension = mock.Mock( + return_value=self.extension_details) + fake_net = network_fakes.FakeNetwork.create_one_network({ + 'id': self._port.network_id, + }) + self.network.find_network = mock.Mock(return_value=fake_net) + self.network.create_port = mock.Mock( + return_value=self._port) + self.cmd = port.CreatePort(self.app, self.namespace) + + def test_create_default_options(self): + arglist = [ + self._port.name, + "--network", self._port.network_id, + ] + verifylist = [ + ('name', self._port.name), + ('apic_erspan_config', None), + ('no_apic_erspan_config', False), + ] + create_ext = port_ext.CreateAndSetPortExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self._port.name, + 'network_id': self._port.network_id, + }) + + def test_create_all_options(self): + arglist = [ + self._port.name, + "--network", self._port.network_id, + "--apic-erspan-config", "dest-ip=10.0.0.0,flow-id=1,direction=in", + ] + verifylist = [ + ('name', self._port.name), + ('apic_erspan_config', [{'dest-ip': '10.0.0.0', + 'flow-id': '1', + 'direction': 'in'}]), + ] + create_ext = port_ext.CreateAndSetPortExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, create_ext) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_port.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self._port.name, + 'apic:erspan_config': [{"dest_ip": "10.0.0.0", + "flow_id": "1", + "direction": "in"}], + 'network_id': self._port.network_id, + }) + + +# Tests for port set with APIC extensions +# +class TestPortSet(test_port.TestPort, test_cli20.CLITestV20Base): + + _port = test_port.TestSetPort._port + + def setUp(self): + super(TestPortSet, self).setUp() + self.network.update_port = mock.Mock(return_value=None) + self.network.find_port = mock.Mock(return_value=self._port) + self.cmd = port.SetPort(self.app, self.namespace) + + def test_set_no_options(self): + arglist = [ + self._port.name, + ] + verifylist = [ + ('port', self._port.name), + ('apic_erspan_config', None), + ('no_apic_erspan_config', False), + ] + set_ext = port_ext.CreateAndSetPortExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_port.called) + self.assertIsNone(result) + + def test_set_all_valid_options(self): + arglist = [ + self._port.name, + "--apic-erspan-config", "dest-ip=10.0.0.0,flow-id=1,direction=in", + ] + verifylist = [ + ('port', self._port.name), + ('apic_erspan_config', [{'dest-ip': '10.0.0.0', + 'flow-id': '1', + 'direction': 'in'}]), + ] + set_ext = port_ext.CreateAndSetPortExtension(self.app) + parsed_args = self.check_parser_ext( + self.cmd, arglist, verifylist, set_ext) + result = self.cmd.take_action(parsed_args) + + attrs = { + 'apic:erspan_config': [{"dest_ip": "10.0.0.0", + "flow_id": "1", + "direction": "in"}], + } + + self.network.update_port.assert_called_once_with( + self._port, **attrs) + self.assertIsNone(result) diff --git a/setup.cfg b/setup.cfg index 6577343..0e79e10 100644 --- a/setup.cfg +++ b/setup.cfg @@ -14,9 +14,8 @@ classifier = License :: OSI Approved :: Apache Software License Operating System :: POSIX :: Linux Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 [files] packages = @@ -30,6 +29,15 @@ setup-hooks = console_scripts = gbp = gbpclient.gbpshell:main +openstack.cli.port_create = + port_create_extension = gbpclient.gbp.v2_0.port:CreateAndSetPortExtension + +openstack.cli.port_show = + port_show_extension = gbpclient.gbp.v2_0.port:ShowPortExtension + +openstack.cli.port_set = + port_set_extension = gbpclient.gbp.v2_0.port:CreateAndSetPortExtension + openstack.cli.network_create = network_create_extension = gbpclient.gbp.v2_0.network:CreateAndSetNetworkExtension