Esempio n. 1
0
    def test_stopped(self):
        with monitor.Monitor(timeout=self.TIMEOUT) as mon:
            dummy = Dummy()
            dummy_name = dummy.create()
            dummy.remove()

        found = any(event.get('name') == dummy_name for event in mon)
        self.assertTrue(found, 'Expected event was not caught.')
Esempio n. 2
0
 def test_iterate_after_events(self):
     with monitor.Monitor(timeout=self.TIMEOUT) as mon:
         dummy = Dummy()
         dummy_name = dummy.create()
         dummy.remove()
         for event in mon:
             if event.get('name') == dummy_name:
                 break
Esempio n. 3
0
 def test_iterate_after_events(self):
     with monitor.Monitor(timeout=self.TIMEOUT) as mon:
         dummy = Dummy()
         dummy_name = dummy.create()
         dummy.remove()
         for event in mon:
             if event.get('name') == dummy_name:
                 break
Esempio n. 4
0
    def test_stopped(self):
        with monitor.Monitor(timeout=self.TIMEOUT) as mon:
            dummy = Dummy()
            dummy_name = dummy.create()
            dummy.remove()

        found = any(event.get('name') == dummy_name for event in mon)
        self.assertTrue(found, 'Expected event was not caught.')
Esempio n. 5
0
    def test_timeout_not_triggered(self):
        time_start = monotonic_time()
        with monitor.Monitor(timeout=self.TIMEOUT) as mon:
            dummy = Dummy()
            dummy.create()
            dummy.remove()

            for event in mon:
                break

        self.assertTrue((monotonic_time() - time_start) <= self.TIMEOUT)
        self.assertTrue(mon.is_stopped())
Esempio n. 6
0
    def test_iteration(self):
        with monitor.Monitor(timeout=self.TIMEOUT) as mon:
            iterator = iter(mon)

            # Generate events to avoid blocking
            dummy = Dummy()
            dummy.create()
            iterator.next()

            dummy.remove()
            iterator.next()

        with self.assertRaises(StopIteration):
            while True:
                iterator.next()
Esempio n. 7
0
    def test_events_keys(self):
        def _simplify_event(event):
            """ Strips event keys except event, address, name, destination,
            family.
            """
            allow = set(['event', 'address', 'name', 'destination', 'family'])
            return {k: v for (k, v) in event.items() if k in allow}

        def _expected_events(nic, address, cidr):
            events_add = [
                {'event': 'new_link', 'name': nic},
                {'event': 'new_addr', 'address': address + '/' + cidr},
                {'event': 'new_link', 'name': nic}]
            events_del = [
                {'address': address + '/' + cidr, 'event': 'del_addr'},
                {'destination': address, 'event': 'del_route'},
                {'event': 'del_link', 'name': nic}]
            events_ipv6 = [
                {'event': 'new_addr', 'family': 'inet6'},
                {'event': 'del_neigh'},
                {'event': 'del_addr', 'family': 'inet6'}]
            if is_disabled_ipv6():
                return deque(events_add + events_del)
            else:
                return deque(events_add + events_ipv6 + events_del)

        with monitor.Monitor(timeout=self.TIMEOUT,
                             silent_timeout=True) as mon:
            dummy = Dummy()
            dummy_name = dummy.create()
            dummy.set_ip(IP_ADDRESS, IP_CIDR)
            dummy.up()
            dummy.remove()

            expected_events = _expected_events(dummy_name, IP_ADDRESS, IP_CIDR)
            _expected = list(expected_events)
            _caught = []

            expected = expected_events.popleft()
            for event in mon:
                _caught.append(event)
                if _is_subdict(expected, event):
                    expected = expected_events.popleft()
                    if len(expected_events) == 0:
                        break

        self.assertEqual(0, len(expected_events), 'Expected events have not '
                         'been caught (in the right order).\n'
                         'Expected:\n%s.\nCaught:\n%s.' %
                         ('\n'.join([str(d) for d in _expected]),
                          '\n'.join([str(_simplify_event(d))
                                     for d in _caught])))
Esempio n. 8
0
    def test_iterate_while_events(self):
        """Tests if monitor is able to catch event while iterating. Before the
        iteration we start _set_and_remove_device, which is delayed for .2
        seconds. Then iteration starts and wait for new dummy.
        """
        dummy = Dummy()
        dummy_name = dummy.create()

        def _set_and_remove_device():
            time.sleep(.2)
            dummy.up()
            dummy.remove()
        add_device_thread = threading.Thread(target=_set_and_remove_device)

        with monitor.Monitor(timeout=self.TIMEOUT) as mon:
            add_device_thread.start()
            for event in mon:
                if event.get('name') == dummy_name:
                    break
            add_device_thread.join()
Esempio n. 9
0
    def test_iterate_while_events(self):
        """Tests if monitor is able to catch event while iterating. Before the
        iteration we start _set_and_remove_device, which is delayed for .2
        seconds. Then iteration starts and wait for new dummy.
        """
        dummy = Dummy()
        dummy_name = dummy.create()

        def _set_and_remove_device():
            time.sleep(.2)
            dummy.up()
            dummy.remove()

        add_device_thread = threading.Thread(target=_set_and_remove_device)

        with monitor.Monitor(timeout=self.TIMEOUT) as mon:
            add_device_thread.start()
            for event in mon:
                if event.get('name') == dummy_name:
                    break
            add_device_thread.join()
Esempio n. 10
0
    def test_event_groups(self):
        with monitor.Monitor(timeout=self.TIMEOUT,
                             groups=('ipv4-ifaddr', )) as mon_a:
            with monitor.Monitor(timeout=self.TIMEOUT,
                                 groups=('link', 'ipv4-route')) as mon_l_r:
                dummy = Dummy()
                dummy.create()
                dummy.set_ip(IP_ADDRESS, IP_CIDR)
                dummy.up()
                dummy.remove()

        for event in mon_a:
            self.assertIn(
                '_addr', event['event'], "Caught event '%s' is not "
                "related to address." % event['event'])

        for event in mon_l_r:
            link_or_route = ('_link' in event['event']
                             or '_route' in event['event'])
            self.assertTrue(
                link_or_route, "Caught event '%s' is not related "
                "to link or route." % event['event'])
Esempio n. 11
0
    def test_event_groups(self):
        with monitor.Monitor(timeout=self.TIMEOUT,
                             groups=('ipv4-ifaddr',)) as mon_a:
            with monitor.Monitor(timeout=self.TIMEOUT,
                                 groups=('link', 'ipv4-route')) as mon_l_r:
                dummy = Dummy()
                dummy.create()
                dummy.set_ip(IP_ADDRESS, IP_CIDR)
                dummy.up()
                dummy.remove()

        for event in mon_a:
            self.assertIn('_addr', event['event'], "Caught event '%s' is not "
                          "related to address." % event['event'])

        for event in mon_l_r:
            link_or_route = ('_link' in event['event'] or
                             '_route' in event['event'])
            self.assertTrue(link_or_route, "Caught event '%s' is not related "
                            "to link or route." % event['event'])
Esempio n. 12
0
    def test_timeout_not_triggered(self):
        time_start = monotonic_time()
        with monitor.Monitor(timeout=self.TIMEOUT) as mon:
            dummy = Dummy()
            dummy.create()
            dummy.remove()

            for event in mon:
                break

        self.assertTrue((monotonic_time() - time_start) <= self.TIMEOUT)
        self.assertTrue(mon.is_stopped())
Esempio n. 13
0
    def test_iteration(self):
        with monitor.Monitor(timeout=self.TIMEOUT) as mon:
            iterator = iter(mon)

            # Generate events to avoid blocking
            dummy = Dummy()
            dummy.create()
            iterator.next()

            dummy.remove()
            iterator.next()

        with self.assertRaises(StopIteration):
            while True:
                iterator.next()
Esempio n. 14
0
 def setUp(self):
     self.device = Dummy()
     self.device.create()
     self.device.up()
     self.device_name = self.device.devName
Esempio n. 15
0
 def setUp(self):
     self.device = Dummy()
     self.device.create()
     self.device.up()
     self.device_name = self.device.devName
Esempio n. 16
0
    def test_events_keys(self):
        def _simplify_event(event):
            """ Strips event keys except event, address, name, destination,
            family.
            """
            allow = set(['event', 'address', 'name', 'destination', 'family'])
            return {k: v for (k, v) in event.items() if k in allow}

        def _expected_events(nic, address, cidr):
            events_add = [{
                'event': 'new_link',
                'name': nic
            }, {
                'event': 'new_addr',
                'address': address + '/' + cidr
            }, {
                'event': 'new_link',
                'name': nic
            }]
            events_del = [{
                'address': address + '/' + cidr,
                'event': 'del_addr'
            }, {
                'destination': address,
                'event': 'del_route'
            }, {
                'event': 'del_link',
                'name': nic
            }]
            events_ipv6 = [{
                'event': 'new_addr',
                'family': 'inet6'
            }, {
                'event': 'del_neigh'
            }, {
                'event': 'del_addr',
                'family': 'inet6'
            }]
            if is_disabled_ipv6():
                return deque(events_add + events_del)
            else:
                return deque(events_add + events_ipv6 + events_del)

        with monitor.Monitor(timeout=self.TIMEOUT, silent_timeout=True) as mon:
            dummy = Dummy()
            dummy_name = dummy.create()
            dummy.set_ip(IP_ADDRESS, IP_CIDR)
            dummy.up()
            dummy.remove()

            expected_events = _expected_events(dummy_name, IP_ADDRESS, IP_CIDR)
            _expected = list(expected_events)
            _caught = []

            expected = expected_events.popleft()
            for event in mon:
                _caught.append(event)
                if _is_subdict(expected, event):
                    expected = expected_events.popleft()
                    if len(expected_events) == 0:
                        break

        self.assertEqual(
            0, len(expected_events), 'Expected events have not '
            'been caught (in the right order).\n'
            'Expected:\n%s.\nCaught:\n%s.' %
            ('\n'.join([str(d) for d in _expected]), '\n'.join(
                [str(_simplify_event(d)) for d in _caught])))
Esempio n. 17
0
class TestConfigureOutbound(TestCaseBase):
    def setUp(self):
        self.device = Dummy()
        self.device.create()
        self.device.up()
        self.device_name = self.device.devName

    # TODO:
    # test remove_outbound

    def tearDown(self):
        self.device.remove()

    def test_single_non_vlan(self):
            qos.configure_outbound(HOST_QOS_OUTBOUND, self.device_name, None)
            tc_classes, tc_filters, tc_qdiscs = \
                self._analyse_qos_and_general_assertions()
            self.assertEqual(tc_classes.classes, [])

            self.assertEqual(len(tc_qdiscs.leaf_qdiscs), 1)
            self.assertIsNotNone(self._non_vlan_qdisc(tc_qdiscs.leaf_qdiscs))
            self._assert_parent(tc_qdiscs.leaf_qdiscs,
                                tc_classes.default_class)

            self.assertEqual(len(tc_filters.tagged_filters), 0)

    @permutations([[1], [2]])
    def test_single_vlan(self, repeating_calls):
        with vlan_device(self.device_name) as vlan:
            for _ in range(repeating_calls):
                qos.configure_outbound(HOST_QOS_OUTBOUND, self.device_name,
                                       vlan.tag)
            tc_classes, tc_filters, tc_qdiscs = \
                self._analyse_qos_and_general_assertions()
            self.assertEqual(len(tc_classes.classes), 1)

            self.assertEqual(len(tc_qdiscs.leaf_qdiscs), 2)
            vlan_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, vlan.tag)
            vlan_class = self._vlan_class(tc_classes.classes, vlan.tag)
            self._assert_parent([vlan_qdisc], vlan_class)

            self.assertEqual(len(tc_filters.tagged_filters), 1)
            self.assertEqual(
                int(tc_filters.tagged_filters[0]['basic']['value']),
                vlan.tag)

    def test_multiple_vlans(self):
        with vlan_device(self.device_name, tag=16) as vlan1:
            with vlan_device(self.device_name, tag=17) as vlan2:
                for v in (vlan1, vlan2):
                    qos.configure_outbound(HOST_QOS_OUTBOUND,
                                           self.device_name, v.tag)

                tc_classes, tc_filters, tc_qdiscs = \
                    self._analyse_qos_and_general_assertions()
                self.assertEqual(len(tc_classes.classes), 2)

                self.assertEqual(len(tc_qdiscs.leaf_qdiscs), 3)
                v1_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, vlan1.tag)
                v2_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, vlan2.tag)
                v1_class = self._vlan_class(tc_classes.classes, vlan1.tag)
                v2_class = self._vlan_class(tc_classes.classes, vlan2.tag)
                self._assert_parent([v1_qdisc], v1_class)
                self._assert_parent([v2_qdisc], v2_class)

                self.assertEqual(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, v.tag)
                    for v in (vlan1, vlan2))
                self.assertEqual(current_tagged_filters_flow_id,
                                 expected_flow_ids)

    @stresstest
    @requires_iperf3
    @requires_tc
    def test_iperf_upper_limit(self):
        # 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):
            linkSet(server_peer, ['up'])
            linkSet(client_peer, ['up'])
            # iperf server and its veth peer lie in a separate network
            # namespace
            link_set_netns(server_dev, ns)
            bridge.addIf(server_peer)
            bridge.addIf(client_peer)
            linkSet(client_dev, ['up'])
            netns_exec(ns, ['ip', 'link', 'set', 'dev', server_dev, 'up'])
            addrAdd(client_dev, client_ip, 24)
            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']])
                self.assertTrue(0 < max_rate < limit_kbps * 1.5)

    def _analyse_qos_and_general_assertions(self):
        tc_classes = self._analyse_classes()
        tc_qdiscs = self._analyse_qdiscs()
        tc_filters = self._analyse_filters()
        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):
        all_classes = list(tc.classes(self.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):
        all_qdiscs = list(tc._qdiscs(self.device_name))
        ingress_qdisc = self._ingress_qdisc(all_qdiscs)
        root_qdisc = self._root_qdisc(all_qdiscs)
        leaf_qdiscs = self._leaf_qdiscs(all_qdiscs)
        self.assertEqual(len(leaf_qdiscs) + 2, len(all_qdiscs))
        return TcQdiscs(leaf_qdiscs, ingress_qdisc, root_qdisc)

    def _analyse_filters(self):
        filters = list(tc._filters(self.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):
        self.assertTrue(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):
        self.assertEqual(root_qdisc['kind'], qos._SHAPING_QDISC_KIND)
        self._assert_root_handle(root_qdisc)
        self.assertEqual(ingress_qdisc['handle'], tc.QDISC_INGRESS)

    def _assertions_on_filters(self, untagged_filters, tagged_filters):
        self.assertTrue(all(f['protocol'] == 'all' for f in tagged_filters))
        self._assert_parent_handle(tagged_filters + untagged_filters,
                                   qos._ROOT_QDISC_HANDLE)
        self.assertEqual(len(untagged_filters), 1)
        self.assertEqual(untagged_filters[0]['protocol'], 'all')

    def _assert_upper_limit(self, default_class):
        self.assertEqual(
            default_class[qos._SHAPING_QDISC_KIND]['ul']['m2'],
            HOST_QOS_OUTBOUND['ul']['m2'])

    def _assertions_on_default_class(self, default_class):
        self._assert_parent_handle([default_class], qos._ROOT_QDISC_HANDLE)
        self.assertEqual(default_class['leaf'], qos._DEFAULT_CLASSID + ':')
        self.assertEqual(default_class[qos._SHAPING_QDISC_KIND]['ls'],
                         HOST_QOS_OUTBOUND['ls'])

    def _assertions_on_root_class(self, root_class):
        self.assertIsNotNone(root_class)
        self._assert_root_handle(root_class)

    def _assert_root_handle(self, entity):
        self.assertEqual(entity['handle'], qos._ROOT_QDISC_HANDLE)

    def _assert_parent(self, entities, parent):
        self.assertTrue(all(e['parent'] == parent['handle'] for e in entities))

    def _assert_parent_handle(self, entities, parent_handle):
        self.assertTrue(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 + qos._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 = qos._DEFAULT_CLASSID + ':'
        return _find_entity(lambda q: q['handle'] == handle, qdiscs)
Esempio n. 18
0
class TestConfigureOutbound(TestCaseBase):
    def setUp(self):
        self.device = Dummy()
        self.device.create()
        self.device.up()
        self.device_name = self.device.devName

    # TODO:
    # test remove_outbound

    def tearDown(self):
        self.device.remove()

    def test_single_non_vlan(self):
        qos.configure_outbound(HOST_QOS_OUTBOUND, self.device_name, None)
        tc_classes, tc_filters, tc_qdiscs = \
            self._analyse_qos_and_general_assertions()
        self.assertEqual(tc_classes.classes, [])

        self.assertEqual(len(tc_qdiscs.leaf_qdiscs), 1)
        self.assertIsNotNone(self._non_vlan_qdisc(tc_qdiscs.leaf_qdiscs))
        self._assert_parent(tc_qdiscs.leaf_qdiscs, tc_classes.default_class)

        self.assertEqual(len(tc_filters.tagged_filters), 0)

    @permutations([[1], [2]])
    def test_single_vlan(self, repeating_calls):
        with vlan_device(self.device_name) as vlan:
            for _ in range(repeating_calls):
                qos.configure_outbound(HOST_QOS_OUTBOUND, self.device_name,
                                       vlan.tag)
            tc_classes, tc_filters, tc_qdiscs = \
                self._analyse_qos_and_general_assertions()
            self.assertEqual(len(tc_classes.classes), 1)

            self.assertEqual(len(tc_qdiscs.leaf_qdiscs), 2)
            vlan_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, vlan.tag)
            vlan_class = self._vlan_class(tc_classes.classes, vlan.tag)
            self._assert_parent([vlan_qdisc], vlan_class)

            self.assertEqual(len(tc_filters.tagged_filters), 1)
            self.assertEqual(
                int(tc_filters.tagged_filters[0]['basic']['value']), vlan.tag)

    def test_multiple_vlans(self):
        with vlan_device(self.device_name, tag=16) as vlan1:
            with vlan_device(self.device_name, tag=17) as vlan2:
                for v in (vlan1, vlan2):
                    qos.configure_outbound(HOST_QOS_OUTBOUND, self.device_name,
                                           v.tag)

                tc_classes, tc_filters, tc_qdiscs = \
                    self._analyse_qos_and_general_assertions()
                self.assertEqual(len(tc_classes.classes), 2)

                self.assertEqual(len(tc_qdiscs.leaf_qdiscs), 3)
                v1_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, vlan1.tag)
                v2_qdisc = self._vlan_qdisc(tc_qdiscs.leaf_qdiscs, vlan2.tag)
                v1_class = self._vlan_class(tc_classes.classes, vlan1.tag)
                v2_class = self._vlan_class(tc_classes.classes, vlan2.tag)
                self._assert_parent([v1_qdisc], v1_class)
                self._assert_parent([v2_qdisc], v2_class)

                self.assertEqual(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, v.tag)
                                        for v in (vlan1, vlan2))
                self.assertEqual(current_tagged_filters_flow_id,
                                 expected_flow_ids)

    @stresstest
    @requires_iperf3
    @requires_tc
    def test_iperf_upper_limit(self):
        # 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):
            linkSet(server_peer, ['up'])
            linkSet(client_peer, ['up'])
            # iperf server and its veth peer lie in a separate network
            # namespace
            link_set_netns(server_dev, ns)
            bridge.addIf(server_peer)
            bridge.addIf(client_peer)
            linkSet(client_dev, ['up'])
            netns_exec(ns, ['ip', 'link', 'set', 'dev', server_dev, 'up'])
            addrAdd(client_dev, client_ip, 24)
            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']
                ])
                self.assertTrue(0 < max_rate < limit_kbps * 1.5)

    def _analyse_qos_and_general_assertions(self):
        tc_classes = self._analyse_classes()
        tc_qdiscs = self._analyse_qdiscs()
        tc_filters = self._analyse_filters()
        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):
        all_classes = list(tc.classes(self.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):
        all_qdiscs = list(tc.qdiscs(self.device_name))
        ingress_qdisc = self._ingress_qdisc(all_qdiscs)
        root_qdisc = self._root_qdisc(all_qdiscs)
        leaf_qdiscs = self._leaf_qdiscs(all_qdiscs)
        self.assertEqual(len(leaf_qdiscs) + 2, len(all_qdiscs))
        return TcQdiscs(leaf_qdiscs, ingress_qdisc, root_qdisc)

    def _analyse_filters(self):
        filters = list(tc._filters(self.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):
        self.assertTrue(
            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):
        self.assertEqual(root_qdisc['kind'], qos._SHAPING_QDISC_KIND)
        self._assert_root_handle(root_qdisc)
        self.assertEqual(ingress_qdisc['handle'], tc.QDISC_INGRESS)

    def _assertions_on_filters(self, untagged_filters, tagged_filters):
        self.assertTrue(all(f['protocol'] == 'all' for f in tagged_filters))
        self._assert_parent_handle(tagged_filters + untagged_filters,
                                   qos._ROOT_QDISC_HANDLE)
        self.assertEqual(len(untagged_filters), 1)
        self.assertEqual(untagged_filters[0]['protocol'], 'all')

    def _assert_upper_limit(self, default_class):
        self.assertEqual(default_class[qos._SHAPING_QDISC_KIND]['ul']['m2'],
                         HOST_QOS_OUTBOUND['ul']['m2'])

    def _assertions_on_default_class(self, default_class):
        self._assert_parent_handle([default_class], qos._ROOT_QDISC_HANDLE)
        self.assertEqual(default_class['leaf'], DEFAULT_CLASSID + ':')
        self.assertEqual(default_class[qos._SHAPING_QDISC_KIND]['ls'],
                         HOST_QOS_OUTBOUND['ls'])

    def _assertions_on_root_class(self, root_class):
        self.assertIsNotNone(root_class)
        self._assert_root_handle(root_class)

    def _assert_root_handle(self, entity):
        self.assertEqual(entity['handle'], qos._ROOT_QDISC_HANDLE)

    def _assert_parent(self, entities, parent):
        self.assertTrue(all(e['parent'] == parent['handle'] for e in entities))

    def _assert_parent_handle(self, entities, parent_handle):
        self.assertTrue(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)