diff options
author | Steven <sluong@cisco.com> | 2017-12-20 12:43:01 -0800 |
---|---|---|
committer | Damjan Marion <dmarion.lists@gmail.com> | 2018-03-21 21:02:15 +0000 |
commit | 9cd2d7a5a4fafadb65d772c48109d55d1e19d425 (patch) | |
tree | 4a9e0665be0096ee6bfc2235388f90b276b23814 /test | |
parent | 43ebe29b6ea1107c30311cfb3dbd8190282903d0 (diff) |
bond: Add bonding driver and LACP protocol
Add bonding driver to support creation of bond interface which composes of
multiple slave interfaces. The slave interfaces could be physical interfaces,
or just any virtual interfaces. For example, memif interfaces.
The syntax to create a bond interface is
create bond mode <lacp | xor | acitve-backup | broadcast | round-robin>
To enslave an interface to the bond interface,
enslave interface TenGigabitEthernet6/0/0 to BondEthernet0
Please see src/plugins/lacp/lacp_doc.md for more examples and additional
options.
LACP is a control plane protocol which manages and monitors the status of
the slave interfaces. The protocol is part of 802.3ad standard. This patch
implements LACPv1. LACPv2 is not supported.
To enable LACP on the bond interface, specify "mode lacp" when the bond
interface is created. The syntax to enslave a slave interface is the same as
other bonding modes.
Change-Id: I06581d3b87635972f9f0e1ec50b67560fc13e26c
Signed-off-by: Steven <sluong@cisco.com>
Diffstat (limited to 'test')
-rw-r--r-- | test/test_bond.py | 282 | ||||
-rw-r--r-- | test/vpp_bond_interface.py | 48 | ||||
-rw-r--r-- | test/vpp_papi_provider.py | 75 |
3 files changed, 405 insertions, 0 deletions
diff --git a/test/test_bond.py b/test/test_bond.py new file mode 100644 index 00000000000..b54a1f1deb5 --- /dev/null +++ b/test/test_bond.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python + +import socket +import unittest + +from framework import VppTestCase, VppTestRunner +from scapy.packet import Raw +from scapy.layers.l2 import Ether +from scapy.layers.inet import IP, UDP +from util import mactobinary +from vpp_bond_interface import VppBondInterface + + +class TestBondInterface(VppTestCase): + """Bond Test Case + + """ + + @classmethod + def setUpClass(cls): + super(TestBondInterface, cls).setUpClass() + # Test variables + cls.pkts_per_burst = 257 # Number of packets per burst + # create 3 pg interfaces + cls.create_pg_interfaces(range(4)) + + # packet sizes + cls.pg_if_packet_sizes = [64, 512, 1518] # , 9018] + + # setup all interfaces + for i in cls.pg_interfaces: + i.admin_up() + + def setUp(self): + super(TestBondInterface, self).setUp() + + def tearDown(self): + super(TestBondInterface, self).tearDown() + if not self.vpp_dead: + self.logger.info(self.vapi.ppcli("show interface")) + + def test_bond_traffic(self): + """ Bond traffic test """ + + # topology + # + # RX-> TX-> + # + # pg2 ------+ +------pg0 (slave) + # | | + # BondEthernet0 (10.10.10.1) + # | | + # pg3 ------+ +------pg1 (slave) + # + + # create interface (BondEthernet0) + # self.logger.info("create bond") + bond0_mac = "02:fe:38:30:59:3c" + mac = mactobinary(bond0_mac) + bond0 = VppBondInterface(self, + mode=3, + lb=1, + use_custom_mac=1, + mac_address=mac) + bond0.add_vpp_config() + bond0.admin_up() + bond0_addr = socket.inet_pton(socket.AF_INET, "10.10.10.1") + self.vapi.sw_interface_add_del_address(bond0.sw_if_index, + bond0_addr, + 24) + + self.pg2.config_ip4() + self.pg2.resolve_arp() + self.pg3.config_ip4() + self.pg3.resolve_arp() + + self.logger.info(self.vapi.cli("show interface")) + self.logger.info(self.vapi.cli("show interface address")) + self.logger.info(self.vapi.cli("show ip arp")) + + # enslave pg0 and pg1 to BondEthernet0 + self.logger.info("bond enslave interface pg0 to BondEthernet0") + bond0.enslave_vpp_bond_interface(sw_if_index=self.pg0.sw_if_index, + is_passive=0, + is_long_timeout=0) + self.logger.info("bond enslave interface pg1 to BondEthernet0") + bond0.enslave_vpp_bond_interface(sw_if_index=self.pg1.sw_if_index, + is_passive=0, + is_long_timeout=0) + + # verify both slaves in BondEthernet0 + if_dump = self.vapi.sw_interface_slave_dump(bond0.sw_if_index) + self.assertTrue(self.pg0.is_interface_config_in_dump(if_dump)) + self.assertTrue(self.pg1.is_interface_config_in_dump(if_dump)) + + # generate a packet from pg2 -> BondEthernet0 -> pg1 + # BondEthernet0 TX hashes this packet to pg1 + p2 = (Ether(src=bond0_mac, dst=self.pg2.local_mac) / + IP(src=self.pg2.local_ip4, dst="10.10.10.12") / + UDP(sport=1235, dport=1235) / + Raw('\xa5' * 100)) + self.pg2.add_stream(p2) + + # generate a packet from pg3 -> BondEthernet0 -> pg0 + # BondEthernet0 TX hashes this packet to pg0 + # notice the ip address and ports are different than p2 packet + p3 = (Ether(src=bond0_mac, dst=self.pg3.local_mac) / + IP(src=self.pg3.local_ip4, dst="10.10.10.11") / + UDP(sport=1234, dport=1234) / + Raw('\xa5' * 100)) + self.pg3.add_stream(p3) + + self.pg_enable_capture(self.pg_interfaces) + + # set up the static arp entries pointing to the BondEthernet0 interface + # so that it does not try to resolve the ip address + self.logger.info(self.vapi.cli( + "set ip arp static BondEthernet0 10.10.10.12 abcd.abcd.0002")) + self.logger.info(self.vapi.cli( + "set ip arp static BondEthernet0 10.10.10.11 abcd.abcd.0004")) + + # clear the interface counters + self.logger.info(self.vapi.cli("clear interfaces")) + + self.pg_start() + + self.logger.info("check the interface counters") + + # verify counters + + # BondEthernet0 tx bytes = 284 + intfs = self.vapi.cli("show interface BondEthernet0").split("\n") + found = 0 + for intf in intfs: + if "tx bytes" in intf and "284" in intf: + found = 1 + self.assertEqual(found, 1) + + # pg0 tx bytes = 142 + intfs = self.vapi.cli("show interface pg0").split("\n") + found = 0 + for intf in intfs: + if "tx bytes" in intf and "142" in intf: + found = 1 + self.assertEqual(found, 1) + + # pg0 tx bytes = 142 + intfs = self.vapi.cli("show interface pg1").split("\n") + found = 0 + for intf in intfs: + if "tx bytes" in intf and "142" in intf: + found = 1 + self.assertEqual(found, 1) + + # pg2 rx bytes = 142 + intfs = self.vapi.cli("show interface pg2").split("\n") + found = 0 + for intf in intfs: + if "rx bytes" in intf and "142" in intf: + found = 1 + self.assertEqual(found, 1) + + # pg3 rx bytes = 142 + intfs = self.vapi.cli("show interface pg3").split("\n") + found = 0 + for intf in intfs: + if "rx bytes" in intf and "142" in intf: + found = 1 + self.assertEqual(found, 1) + + bond0.remove_vpp_config() + + def test_bond_enslave(self): + """ Bond enslave/detach slave test """ + + # create interface (BondEthernet0) + self.logger.info("create bond") + bond0 = VppBondInterface(self, mode=3) + bond0.add_vpp_config() + bond0.admin_up() + + # verify pg0 and pg1 not in BondEthernet0 + if_dump = self.vapi.sw_interface_slave_dump(bond0.sw_if_index) + self.assertFalse(self.pg0.is_interface_config_in_dump(if_dump)) + self.assertFalse(self.pg1.is_interface_config_in_dump(if_dump)) + + # enslave pg0 and pg1 to BondEthernet0 + self.logger.info("bond enslave interface pg0 to BondEthernet0") + bond0.enslave_vpp_bond_interface(sw_if_index=self.pg0.sw_if_index, + is_passive=0, + is_long_timeout=0) + + self.logger.info("bond enslave interface pg1 to BondEthernet0") + bond0.enslave_vpp_bond_interface(sw_if_index=self.pg1.sw_if_index, + is_passive=0, + is_long_timeout=0) + + # verify both slaves in BondEthernet0 + if_dump = self.vapi.sw_interface_slave_dump(bond0.sw_if_index) + self.assertTrue(self.pg0.is_interface_config_in_dump(if_dump)) + self.assertTrue(self.pg1.is_interface_config_in_dump(if_dump)) + + # detach interface pg0 + self.logger.info("detach interface pg0") + bond0.detach_vpp_bond_interface(sw_if_index=self.pg0.sw_if_index) + + # verify pg0 is not in BondEthernet0, but pg1 is + if_dump = self.vapi.sw_interface_slave_dump(bond0.sw_if_index) + self.assertFalse(self.pg0.is_interface_config_in_dump(if_dump)) + self.assertTrue(self.pg1.is_interface_config_in_dump(if_dump)) + + # detach interface pg1 + self.logger.info("detach interface pg1") + bond0.detach_vpp_bond_interface(sw_if_index=self.pg1.sw_if_index) + + # verify pg0 and pg1 not in BondEthernet0 + if_dump = self.vapi.sw_interface_slave_dump(bond0.sw_if_index) + self.assertFalse(self.pg0.is_interface_config_in_dump(if_dump)) + self.assertFalse(self.pg1.is_interface_config_in_dump(if_dump)) + + bond0.remove_vpp_config() + + def test_bond(self): + """ Bond add/delete interface test """ + self.logger.info("Bond add interfaces") + + # create interface 1 (BondEthernet0) + bond0 = VppBondInterface(self, mode=5) + bond0.add_vpp_config() + bond0.admin_up() + + # create interface 2 (BondEthernet1) + bond1 = VppBondInterface(self, mode=3) + bond1.add_vpp_config() + bond1.admin_up() + + # verify both interfaces in the show + ifs = self.vapi.cli("show interface") + self.assertNotEqual(ifs.find('BondEthernet0'), -1) + self.assertNotEqual(ifs.find('BondEthernet1'), -1) + + # verify they are in the dump also + if_dump = self.vapi.sw_interface_bond_dump() + self.assertTrue(bond0.is_interface_config_in_dump(if_dump)) + self.assertTrue(bond1.is_interface_config_in_dump(if_dump)) + + # delete BondEthernet1 + self.logger.info("Deleting BondEthernet1") + bond1.remove_vpp_config() + + self.logger.info("Verifying BondEthernet1 is deleted") + + ifs = self.vapi.cli("show interface") + # verify BondEthernet0 still in the show + self.assertNotEqual(ifs.find('BondEthernet0'), -1) + + # verify BondEthernet1 not in the show + self.assertEqual(ifs.find('BondEthernet1'), -1) + + # verify BondEthernet1 is not in the dump + if_dump = self.vapi.sw_interface_bond_dump() + self.assertFalse(bond1.is_interface_config_in_dump(if_dump)) + + # verify BondEthernet0 is still in the dump + self.assertTrue(bond0.is_interface_config_in_dump(if_dump)) + + # delete BondEthernet0 + self.logger.info("Deleting BondEthernet0") + bond0.remove_vpp_config() + + self.logger.info("Verifying BondEthernet0 is deleted") + + # verify BondEthernet0 not in the show + ifs = self.vapi.cli("show interface") + self.assertEqual(ifs.find('BondEthernet0'), -1) + + # verify BondEthernet0 is not in the dump + if_dump = self.vapi.sw_interface_bond_dump() + self.assertFalse(bond0.is_interface_config_in_dump(if_dump)) + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/test/vpp_bond_interface.py b/test/vpp_bond_interface.py new file mode 100644 index 00000000000..1c33e1cecd6 --- /dev/null +++ b/test/vpp_bond_interface.py @@ -0,0 +1,48 @@ +from vpp_object import VppObject +from vpp_interface import VppInterface + + +class VppBondInterface(VppInterface): + """VPP bond interface.""" + + def __init__(self, test, mode, lb=0, + use_custom_mac=0, mac_address=''): + + """ Create VPP Bond interface """ + self._test = test + self.mode = mode + self.lb = lb + self.use_custom_mac = use_custom_mac + self.mac_address = mac_address + self._sw_if_index = 0 + super(VppBondInterface, self).__init__(test) + + def add_vpp_config(self): + r = self.test.vapi.bond_create(self.mode, + self.lb, + self.use_custom_mac, + self.mac_address) + self._sw_if_index = r.sw_if_index + + def remove_vpp_config(self): + self.test.vapi.bond_delete(self.sw_if_index) + + def enslave_vpp_bond_interface(self, + sw_if_index, + is_passive, + is_long_timeout): + self.test.vapi.bond_enslave(sw_if_index, + self.sw_if_index, + is_passive, + is_long_timeout) + + def detach_vpp_bond_interface(self, + sw_if_index): + self.test.vapi.bond_detach_slave(sw_if_index) + + def is_interface_config_in_dump(self, dump): + for i in dump: + if i.sw_if_index == self.sw_if_index: + return True + else: + return False diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index 65cf766ffff..5517174590d 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -3341,3 +3341,78 @@ class VppPapiProvider(object): def want_igmp_events(self, enable=1): return self.api(self.papi.want_igmp_events, {'enable': enable, 'pid': os.getpid()}) + + def bond_create( + self, + mode, + lb, + use_custom_mac, + mac_address=''): + """ + :param mode: mode + :param lb: load balance + :param use_custom_mac: use custom mac + :param mac_address: mac address + """ + return self.api( + self.papi.bond_create, + {'mode': mode, + 'lb': lb, + 'use_custom_mac': use_custom_mac, + 'mac_address': mac_address + }) + + def bond_delete( + self, + sw_if_index): + """ + :param sw_if_index: interface the operation is applied to + """ + return self.api(self.papi.bond_delete, + {'sw_if_index': sw_if_index}) + + def bond_enslave( + self, + sw_if_index, + bond_sw_if_index, + is_passive, + is_long_timeout): + """ + :param sw_if_index: slave sw_if_index + :param bond_sw_if_index: bond sw_if_index + :param is_passive: is passive lacp speaker + :param is_long_time: 90 seconds timeout instead of 3 seconds timeout + """ + return self.api( + self.papi.bond_enslave, + {'sw_if_index': sw_if_index, + 'bond_sw_if_index': bond_sw_if_index, + 'is_passive': is_passive, + 'is_long_timeout': is_long_timeout + }) + + def bond_detach_slave( + self, + sw_if_index): + """ + :param sw_if_index: slave interface the operation is applied to + """ + return self.api(self.papi.bond_detach_slave, + {'sw_if_index': sw_if_index}) + + def sw_interface_slave_dump( + self, + sw_if_index): + """ + :param sw_if_index: bond sw_if_index + """ + return self.api(self.papi.sw_interface_slave_dump, + {'sw_if_index': sw_if_index}) + + def sw_interface_bond_dump( + self): + """ + + """ + return self.api(self.papi.sw_interface_bond_dump, + {}) |