Add support for ERSPAN

Add support for ERSPAN extension on the openstack port resource.

Change-Id: Ie9c5b429dfab774faef92d514be9110f6795991b
This commit is contained in:
Thomas Bachman 2021-01-24 17:56:47 +00:00
parent 35d30bcf3d
commit f13e3d5764
3 changed files with 267 additions and 3 deletions

111
gbpclient/gbp/v2_0/port.py Normal file
View File

@ -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="<apic_synchronization_state>",
dest='apic_synchronization_state',
help=_("Apic synchronization state")
)
parser.add_argument(
'--apic-erspan-config',
metavar="<apic_erspan_config>",
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

View File

@ -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)

View File

@ -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