def setup(): check_sysfs_bond_permission() if running_on_travis_ci(): pytest.skip( 'The sysfs access is not working inside container without ' 'proper bonding module loading' )
def xfail_when_running_on_travis_with_centos(): try: yield except AssertionError: if running_on_travis_ci() and running_on_centos(): pytest.xfail('Unable to run OVS tests on travis-ci') else: raise
IPV6_ADDR = '2607:f0d0:1002:51::4' IPV6_ADDR_BASE = '2001:1:1:1' IPV6_ADDR1 = f'{IPV6_ADDR_BASE}::1' IPV6_NET_ADDR = f'{IPV6_ADDR_BASE}::' IPV6_PREFIX_LENGTH = 64 IPV4_ADDR1_CIDR = f'{IPV4_ADDR1}/{IPV4_PREFIX_LENGTH}' IPV4_ADDR2_CIDR = f'{IPV4_ADDR2}/{IPV4_PREFIX_LENGTH}' IPV4_ADDR3_CIDR = f'{IPV4_ADDR3}/{32}' IPV6_ADDR_CIDR = f'{IPV6_ADDR}/{IPV6_PREFIX_LENGTH}' # speeds defined in ethtool ETHTOOL_SPEEDS = set([10, 100, 1000, 2500, 10000]) ipv6_broken_on_travis_ci = pytest.mark.skipif( running_on_travis_ci(), reason='IPv6 not supported on travis') @pytest.fixture def nic0(): with dummy_device() as nic: yield nic @pytest.fixture def dynamic_ipv6_iface(): if running_on_ovirt_ci(): pytest.skip('Using dnsmasq for ipv6 RA is unstable on CI') with veth_pair() as (server, client): with wait_for_ipv6(server, IPV6_ADDR1, IPV6_PREFIX_LENGTH):
class TestConfigureOutbound(object): # TODO: # test remove_outbound def test_single_non_vlan(self, dummy): qos.configure_outbound(HOST_QOS_OUTBOUND, dummy, None) tc_entities = self._analyse_qos_and_general_assertions(dummy) tc_classes, tc_filters, tc_qdiscs = tc_entities assert tc_classes.classes == [] assert len(tc_qdiscs.leaf_qdiscs) == 1 assert self._non_vlan_qdisc(tc_qdiscs.leaf_qdiscs) is not None self._assert_parent(tc_qdiscs.leaf_qdiscs, tc_classes.default_class) assert len(tc_filters.tagged_filters) == 0 @pytest.mark.xfail( condition=running_on_ovirt_ci() or running_on_travis_ci(), reason='does not work on CI with nmstate', strict=False, ) @pytest.mark.parametrize('repeating_calls', [1, 2]) @mock.patch('vdsm.network.netinfo.bonding.permanent_address', lambda: {}) def test_single_vlan(self, dummy, vlan16, repeating_calls): for _ in range(repeating_calls): qos.configure_outbound(HOST_QOS_OUTBOUND, dummy, VLAN16_TAG) tc_entities = self._analyse_qos_and_general_assertions(dummy) tc_classes, tc_filters, tc_qdiscs = tc_entities assert len(tc_classes.classes) == 1 assert len(tc_qdiscs.leaf_qdiscs) == 2 vlan_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, VLAN16_TAG) vlan_class = self._vlan_class(tc_classes.classes, VLAN16_TAG) self._assert_parent([vlan_qdisc], vlan_class) tag_filters = tc_filters.tagged_filters assert len(tag_filters) == 1 assert int(tag_filters[0]['basic']['value']) == VLAN16_TAG @pytest.mark.xfail( condition=running_on_ovirt_ci() or running_on_travis_ci(), reason='does not work on CI with nmstate', strict=False, ) @mock.patch('vdsm.network.netinfo.bonding.permanent_address', lambda: {}) def test_multiple_vlans(self, dummy, vlan16, vlan17): for vlan_tag in (VLAN16_TAG, VLAN17_TAG): qos.configure_outbound(HOST_QOS_OUTBOUND, dummy, vlan_tag) tc_entities = self._analyse_qos_and_general_assertions(dummy) tc_classes, tc_filters, tc_qdiscs = tc_entities assert len(tc_classes.classes) == 2 assert len(tc_qdiscs.leaf_qdiscs) == 3 v1_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, VLAN16_TAG) v2_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, VLAN17_TAG) v1_class = self._vlan_class(tc_classes.classes, VLAN16_TAG) v2_class = self._vlan_class(tc_classes.classes, VLAN17_TAG) self._assert_parent([v1_qdisc], v1_class) self._assert_parent([v2_qdisc], v2_class) assert len(tc_filters.tagged_filters) == 2 current_tagged_filters_flow_id = set( f['basic']['flowid'] for f in tc_filters.tagged_filters) expected_flow_ids = set('%s%x' % (qos._ROOT_QDISC_HANDLE, vlan_tag) for vlan_tag in (VLAN16_TAG, VLAN17_TAG)) assert current_tagged_filters_flow_id == expected_flow_ids @requires_iperf3 @pytest.mark.xfail(reason='Not maintained stress test', run=False) def test_iperf_upper_limit(self, requires_tc): # Upper limit is not an accurate measure. This is because it converges # over time and depends on current machine hardware (CPU). # Hence, it is hard to make hard assertions on it. The test should run # at least 60 seconds (the longer the better) and the user should # inspect the computed average rate and optionally the additional # traffic data that was collected in client.out in order to be # convinced QOS is working properly. limit_kbps = 1000 # 1 Mbps (in kbps) server_ip = '192.0.2.1' client_ip = '192.0.2.10' qos_out = {'ul': {'m2': limit_kbps}, 'ls': {'m2': limit_kbps}} # using a network namespace is essential since otherwise the kernel # short-circuits the traffic and bypasses the veth devices and the # classfull qdisc. with network_namespace( 'server_ns') as ns, bridge_device() as bridge, veth_pair() as ( server_peer, server_dev, ), veth_pair() as ( client_dev, client_peer, ): # iperf server and its veth peer lie in a separate network # namespace link_set_netns(server_dev, ns) bridge.add_port(server_peer) bridge.add_port(client_peer) netns_exec(ns, ['ip', 'link', 'set', 'dev', server_dev, 'up']) Interface.from_existing_dev_name(client_dev).add_ip( client_ip, 24, IpFamily.IPv4) netns_exec( ns, [ 'ip', '-4', 'addr', 'add', 'dev', server_dev, '%s/24' % server_ip, ], ) qos.configure_outbound(qos_out, client_peer, None) with running(IperfServer(server_ip, network_ns=ns)): client = IperfClient(server_ip, client_ip, test_time=60) client.start() max_rate = max([ float(interval['streams'][0]['bits_per_second']) // (2**10) for interval in client.out['intervals'] ]) assert 0 < max_rate < limit_kbps * 1.5 def _analyse_qos_and_general_assertions(self, device_name): tc_classes = self._analyse_classes(device_name) tc_qdiscs = self._analyse_qdiscs(device_name) tc_filters = self._analyse_filters(device_name) self._assertions_on_classes(tc_classes.classes, tc_classes.default_class, tc_classes.root_class) self._assertions_on_qdiscs(tc_qdiscs.ingress_qdisc, tc_qdiscs.root_qdisc) self._assertions_on_filters(tc_filters.untagged_filters, tc_filters.tagged_filters) return tc_classes, tc_filters, tc_qdiscs def _analyse_classes(self, device_name): all_classes = list(tc.classes(device_name)) root_class = self._root_class(all_classes) default_class = self._default_class(all_classes) all_classes.remove(root_class) all_classes.remove(default_class) return TcClasses(all_classes, default_class, root_class) def _analyse_qdiscs(self, device_name): all_qdiscs = list(tc.qdiscs(device_name)) ingress_qdisc = self._ingress_qdisc(all_qdiscs) root_qdisc = self._root_qdisc(all_qdiscs) leaf_qdiscs = self._leaf_qdiscs(all_qdiscs) assert len(leaf_qdiscs) + 2 == len(all_qdiscs) return TcQdiscs(leaf_qdiscs, ingress_qdisc, root_qdisc) def _analyse_filters(self, device_name): filters = list(tc._filters(device_name)) untagged_filters = self._untagged_filters(filters) tagged_filters = self._tagged_filters(filters) return TcFilters(untagged_filters, tagged_filters) def _assertions_on_classes(self, all_classes, default_class, root_class): assert all( cls.get('kind') == qos._SHAPING_QDISC_KIND for cls in all_classes), str(all_classes) self._assertions_on_root_class(root_class) self._assertions_on_default_class(default_class) if not all_classes: # only a default class self._assert_upper_limit(default_class) else: for cls in all_classes: self._assert_upper_limit(cls) def _assertions_on_qdiscs(self, ingress_qdisc, root_qdisc): assert root_qdisc['kind'] == qos._SHAPING_QDISC_KIND self._assert_root_handle(root_qdisc) assert ingress_qdisc['handle'] == tc.QDISC_INGRESS def _assertions_on_filters(self, untagged_filters, tagged_filters): assert all(f['protocol'] == 'all' for f in tagged_filters) self._assert_parent_handle(tagged_filters + untagged_filters, qos._ROOT_QDISC_HANDLE) assert len(untagged_filters) == 1, untagged_filters assert untagged_filters[0]['protocol'] == 'all' def _assert_upper_limit(self, default_class): dclass = default_class[qos._SHAPING_QDISC_KIND]['ul']['m2'] assert dclass == HOST_QOS_OUTBOUND['ul']['m2'] def _assertions_on_default_class(self, default_class): self._assert_parent_handle([default_class], qos._ROOT_QDISC_HANDLE) assert default_class['leaf'] == DEFAULT_CLASSID + ':' dclass = default_class[qos._SHAPING_QDISC_KIND]['ls'] assert dclass == HOST_QOS_OUTBOUND['ls'] def _assertions_on_root_class(self, root_class): assert root_class is not None self._assert_root_handle(root_class) def _assert_root_handle(self, entity): assert entity['handle'] == qos._ROOT_QDISC_HANDLE def _assert_parent(self, entities, parent): assert all(e['parent'] == parent['handle'] for e in entities) def _assert_parent_handle(self, entities, parent_handle): assert all(e['parent'] == parent_handle for e in entities) def _root_class(self, classes): return _find_entity(lambda c: c.get('root'), classes) def _default_class(self, classes): default_cls_handle = qos._ROOT_QDISC_HANDLE + DEFAULT_CLASSID return _find_entity(lambda c: c['handle'] == default_cls_handle, classes) def _ingress_qdisc(self, qdiscs): return _find_entity(lambda q: q['kind'] == 'ingress', qdiscs) def _root_qdisc(self, qdiscs): return _find_entity(lambda q: q.get('root'), qdiscs) def _leaf_qdiscs(self, qdiscs): return [ qdisc for qdisc in qdiscs if qdisc['kind'] == qos._FAIR_QDISC_KIND ] def _untagged_filters(self, filters): predicate = lambda f: f.get('u32', {}).get('match', {}) == { 'mask': 0, 'value': 0, 'offset': 0, } return list(f for f in filters if predicate(f)) def _tagged_filters(self, filters): def tagged(f): return f.get('basic', {}).get('object') == 'vlan' return list(f for f in filters if tagged(f)) def _vlan_qdisc(self, qdiscs, vlan_tag): handle = '%x:' % vlan_tag return _find_entity(lambda q: q['handle'] == handle, qdiscs) def _vlan_class(self, classes, vlan_tag): handle = qos._ROOT_QDISC_HANDLE + '%x' % vlan_tag return _find_entity(lambda c: c['handle'] == handle, classes) def _non_vlan_qdisc(self, qdiscs): handle = DEFAULT_CLASSID + ':' return _find_entity(lambda q: q['handle'] == handle, qdiscs)
class TestPortMirror(object): """ Tests port mirroring of IP traffic between two bridges. This test brings up two tap devices and attaches every device to a separate bridge. Then mirroring of IP packets between the two bridges is enabled. If sent through _tap0 the packet _ICMP arrives on _tap1 the test succeeds. The tap devices are needed because the tc filter rules only become active when the bridge is ready, and the bridge only becomes ready when it is attached to an active device. """ # [ Ethernet ] # dst = 00:1c:c0:d0:44:dc # src = 00:21:5c:4d:42:75 # type = IPv4 # [ IP ] # version = 4L # ihl = 5L # tos = 0x0 # len = 33 # id = 1 # flags = # frag = 0L # ttl = 64 # proto = icmp # chksum = 0xf953 # src = 192.168.0.52 # dst = 192.168.0.3 # \options \ # [ ICMP ] # type = echo-request # code = 0 # chksum = 0x2875 # id = 0x0 # seq = 0x0 # [ Raw ] # load = '\x01#Eg\x89' _ICMP = unhexlify('001cc0d044dc' '00215c4d4275' '0800' # Ethernet '45000021000100004001f953' 'c0a80034' 'c0a80003' # IP '080028750000000' # ICMP '00123456789') # Payload @pytest.fixture(autouse=True) def setup(self, requires_tc): self._tap0 = Tap() self._tap1 = Tap() self._tap2 = Tap() self._bridge0 = Bridge('src-') self._bridge1 = Bridge('target-') self._bridge2 = Bridge('target2-') self._devices = [ self._tap0, self._tap1, self._tap2, self._bridge0, self._bridge1, self._bridge2, ] cleanup = [] try: for iface in self._devices: iface.create() cleanup.append(iface) self._bridge0.add_port(self._tap0.dev_name) self._bridge1.add_port(self._tap1.dev_name) self._bridge2.add_port(self._tap2.dev_name) yield finally: failed = [] for iface in cleanup: try: iface.remove() except Exception: failed.append(str(iface)) if failed: raise RuntimeError(f'Error removing devices: {failed}') def _send_ping(self): self._tap1.start_listener(self._ICMP) self._tap0.write_to_device(self._ICMP) if _wait_for_func(lambda: not self._tap1.is_listener_alive(), timeout=2): return True else: self._tap1.stop_listener() return False @pytest.mark.xfail( condition=running_on_travis_ci(), reason='does not work on Travis CI with nmstate', strict=False, ) def test_mirroring(self): tc.setPortMirroring(self._bridge0.dev_name, self._bridge1.dev_name) assert self._send_ping(), 'Bridge received no mirrored ping requests.' tc.unsetPortMirroring(self._bridge0.dev_name, self._bridge1.dev_name) msg = 'Bridge received mirrored ping requests, but mirroring is unset.' assert not self._send_ping(), msg def test_mirroring_with_distraction(self): # setting another mirror action should not obstract the first one tc.setPortMirroring(self._bridge0.dev_name, self._bridge2.dev_name) self.test_mirroring() tc.unsetPortMirroring(self._bridge0.dev_name, self._bridge2.dev_name)