1ead1e536SRenato Botelho do Couto#!/usr/bin/env python3
2f56b77a0SDamjan Marion
3acb9b8e8SKlement Sekerafrom __future__ import print_function
4acb9b8e8SKlement Sekeraimport gc
59beabd8aSPaul Vinciguerraimport logging
6162989e0SOle Trøanimport sys
7acb9b8e8SKlement Sekeraimport os
8acb9b8e8SKlement Sekeraimport select
9496b0deeSPaul Vinciguerraimport signal
10162989e0SOle Trøanimport unittest
11f62ae128SKlement Sekeraimport tempfile
12277b89c9SKlement Sekeraimport time
13162989e0SOle Trøanimport faulthandler
14162989e0SOle Trøanimport random
15162989e0SOle Trøanimport copy
16162989e0SOle Trøanimport psutil
1768ebc83eSjuraj.linkesimport platform
18e4504c63SKlement Sekerafrom collections import deque
1972f0004aSPaul Vinciguerrafrom threading import Thread, Event
20162989e0SOle Trøanfrom inspect import getdoc, isclass
2172f0004aSPaul Vinciguerrafrom traceback import format_exception
22162989e0SOle Trøanfrom logging import FileHandler, DEBUG, Formatter
23a7427ec6SPaul Vinciguerra
24a7427ec6SPaul Vinciguerraimport scapy.compat
25acb9b8e8SKlement Sekerafrom scapy.packet import Raw
26496b0deeSPaul Vinciguerraimport hook as hookmodule
27919efad2SPaul Vinciguerrafrom vpp_pg_interface import VppPGInterface
28a45dc07cSOle Troanfrom vpp_sub_interface import VppSubInterface
29162989e0SOle Trøanfrom vpp_lo_interface import VppLoInterface
30192b13f9SNeale Rannsfrom vpp_bvi_interface import VppBviInterface
31162989e0SOle Trøanfrom vpp_papi_provider import VppPapiProvider
321043fd38SPaul Vinciguerraimport vpp_papi
33162989e0SOle Trøanfrom vpp_papi.vpp_stats import VPPStats
34499ea648SPaul Vinciguerrafrom vpp_papi.vpp_transport_shmem import VppTransportShmemIOError
3505742265SKlement Sekerafrom log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \
36dfb5f2afSjuraj.linkes    get_logger, colorize
3772f0004aSPaul Vinciguerrafrom vpp_object import VppObjectRegistry
38162989e0SOle Trøanfrom util import ppp, is_core_present
39162989e0SOle Trøanfrom scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror
40162989e0SOle Trøanfrom scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest
41162989e0SOle Trøanfrom scapy.layers.inet6 import ICMPv6EchoReply
426919b0deSPaul Vinciguerra
43acb9b8e8SKlement Sekeraif os.name == 'posix' and sys.version_info[0] < 3:
44acb9b8e8SKlement Sekera    # using subprocess32 is recommended by python official documentation
45acb9b8e8SKlement Sekera    # @ https://docs.python.org/2/library/subprocess.html
46acb9b8e8SKlement Sekera    import subprocess32 as subprocess
47acb9b8e8SKlement Sekeraelse:
48acb9b8e8SKlement Sekera    import subprocess
49f62ae128SKlement Sekera
50852f5ef9SPaul Vinciguerra#  Python2/3 compatible
51852f5ef9SPaul Vinciguerratry:
52852f5ef9SPaul Vinciguerra    input = raw_input
53852f5ef9SPaul Vinciguerraexcept NameError:
54852f5ef9SPaul Vinciguerra    pass
55852f5ef9SPaul Vinciguerra
569beabd8aSPaul Vinciguerralogger = logging.getLogger(__name__)
579beabd8aSPaul Vinciguerra
589beabd8aSPaul Vinciguerra# Set up an empty logger for the testcase that can be overridden as necessary
599beabd8aSPaul Vinciguerranull_logger = logging.getLogger('VppTestCase')
609beabd8aSPaul Vinciguerranull_logger.addHandler(logging.NullHandler())
619beabd8aSPaul Vinciguerra
62cae64f89Sjuraj.linkesPASS = 0
63cae64f89Sjuraj.linkesFAIL = 1
64cae64f89Sjuraj.linkesERROR = 2
65cae64f89Sjuraj.linkesSKIP = 3
66cae64f89Sjuraj.linkesTEST_RUN = 4
67cae64f89Sjuraj.linkes
680cbc71d7SPaul Vinciguerra
690cbc71d7SPaul Vinciguerraclass BoolEnvironmentVariable(object):
700cbc71d7SPaul Vinciguerra
710cbc71d7SPaul Vinciguerra    def __init__(self, env_var_name, default='n', true_values=None):
720cbc71d7SPaul Vinciguerra        self.name = env_var_name
730cbc71d7SPaul Vinciguerra        self.default = default
740cbc71d7SPaul Vinciguerra        self.true_values = true_values if true_values is not None else \
750cbc71d7SPaul Vinciguerra            ("y", "yes", "1")
760cbc71d7SPaul Vinciguerra
770cbc71d7SPaul Vinciguerra    def __bool__(self):
780cbc71d7SPaul Vinciguerra        return os.getenv(self.name, self.default).lower() in self.true_values
790cbc71d7SPaul Vinciguerra
800cbc71d7SPaul Vinciguerra    if sys.version_info[0] == 2:
810cbc71d7SPaul Vinciguerra        __nonzero__ = __bool__
820cbc71d7SPaul Vinciguerra
830cbc71d7SPaul Vinciguerra    def __repr__(self):
840cbc71d7SPaul Vinciguerra        return 'BoolEnvironmentVariable(%r, default=%r, true_values=%r)' % \
850cbc71d7SPaul Vinciguerra               (self.name, self.default, self.true_values)
860cbc71d7SPaul Vinciguerra
870cbc71d7SPaul Vinciguerra
880cbc71d7SPaul Vinciguerradebug_framework = BoolEnvironmentVariable('TEST_DEBUG')
890cbc71d7SPaul Vinciguerraif debug_framework:
90ebbaf55eSKlement Sekera    import debug_internal
91ebbaf55eSKlement Sekera
92f62ae128SKlement Sekera"""
93f62ae128SKlement Sekera  Test framework module.
94f62ae128SKlement Sekera
95f62ae128SKlement Sekera  The module provides a set of tools for constructing and running tests and
96f62ae128SKlement Sekera  representing the results.
97f62ae128SKlement Sekera"""
98f62ae128SKlement Sekera
99f56b77a0SDamjan Marion
100496b0deeSPaul Vinciguerraclass VppDiedError(Exception):
101496b0deeSPaul Vinciguerra    """ exception for reporting that the subprocess has died."""
102496b0deeSPaul Vinciguerra
103496b0deeSPaul Vinciguerra    signals_by_value = {v: k for k, v in signal.__dict__.items() if
104496b0deeSPaul Vinciguerra                        k.startswith('SIG') and not k.startswith('SIG_')}
105496b0deeSPaul Vinciguerra
1065dd6a7b2SPaul Vinciguerra    def __init__(self, rv=None, testcase=None, method_name=None):
107496b0deeSPaul Vinciguerra        self.rv = rv
108496b0deeSPaul Vinciguerra        self.signal_name = None
1095dd6a7b2SPaul Vinciguerra        self.testcase = testcase
1105dd6a7b2SPaul Vinciguerra        self.method_name = method_name
1115dd6a7b2SPaul Vinciguerra
112496b0deeSPaul Vinciguerra        try:
113496b0deeSPaul Vinciguerra            self.signal_name = VppDiedError.signals_by_value[-rv]
114fea82607SPaul Vinciguerra        except (KeyError, TypeError):
115496b0deeSPaul Vinciguerra            pass
116496b0deeSPaul Vinciguerra
1175dd6a7b2SPaul Vinciguerra        if testcase is None and method_name is None:
1185dd6a7b2SPaul Vinciguerra            in_msg = ''
1195dd6a7b2SPaul Vinciguerra        else:
1205dd6a7b2SPaul Vinciguerra            in_msg = 'running %s.%s ' % (testcase, method_name)
1215dd6a7b2SPaul Vinciguerra
1225dd6a7b2SPaul Vinciguerra        msg = "VPP subprocess died %sunexpectedly with return code: %d%s." % (
1235dd6a7b2SPaul Vinciguerra            in_msg,
124496b0deeSPaul Vinciguerra            self.rv,
125f7457521SPaul Vinciguerra            ' [%s]' % (self.signal_name if
126f7457521SPaul Vinciguerra                       self.signal_name is not None else ''))
127496b0deeSPaul Vinciguerra        super(VppDiedError, self).__init__(msg)
128496b0deeSPaul Vinciguerra
129496b0deeSPaul Vinciguerra
130f56b77a0SDamjan Marionclass _PacketInfo(object):
131f62ae128SKlement Sekera    """Private class to create packet info object.
132f62ae128SKlement Sekera
133f62ae128SKlement Sekera    Help process information about the next packet.
134f62ae128SKlement Sekera    Set variables to default values.
135f62ae128SKlement Sekera    """
13686d87c40SMatej Klotton    #: Store the index of the packet.
137f56b77a0SDamjan Marion    index = -1
13886d87c40SMatej Klotton    #: Store the index of the source packet generator interface of the packet.
139f56b77a0SDamjan Marion    src = -1
14086d87c40SMatej Klotton    #: Store the index of the destination packet generator interface
14186d87c40SMatej Klotton    #: of the packet.
142f56b77a0SDamjan Marion    dst = -1
14359dda065SPavel Kotucek    #: Store expected ip version
14459dda065SPavel Kotucek    ip = -1
14559dda065SPavel Kotucek    #: Store expected upper protocol
14659dda065SPavel Kotucek    proto = -1
14786d87c40SMatej Klotton    #: Store the copy of the former packet.
148f56b77a0SDamjan Marion    data = None
149f62ae128SKlement Sekera
15016a14cdbSMatej Klotton    def __eq__(self, other):
15116a14cdbSMatej Klotton        index = self.index == other.index
15216a14cdbSMatej Klotton        src = self.src == other.src
15316a14cdbSMatej Klotton        dst = self.dst == other.dst
15416a14cdbSMatej Klotton        data = self.data == other.data
15516a14cdbSMatej Klotton        return index and src and dst and data
15616a14cdbSMatej Klotton
157f62ae128SKlement Sekera
158acb9b8e8SKlement Sekeradef pump_output(testclass):
159acb9b8e8SKlement Sekera    """ pump output from vpp stdout/stderr to proper queues """
1606a6f4f7fSKlement Sekera    stdout_fragment = ""
1616a6f4f7fSKlement Sekera    stderr_fragment = ""
16216782368SNeale Ranns    while not testclass.pump_thread_stop_flag.is_set():
163acb9b8e8SKlement Sekera        readable = select.select([testclass.vpp.stdout.fileno(),
164acb9b8e8SKlement Sekera                                  testclass.vpp.stderr.fileno(),
165acb9b8e8SKlement Sekera                                  testclass.pump_thread_wakeup_pipe[0]],
166acb9b8e8SKlement Sekera                                 [], [])[0]
167acb9b8e8SKlement Sekera        if testclass.vpp.stdout.fileno() in readable:
1686a6f4f7fSKlement Sekera            read = os.read(testclass.vpp.stdout.fileno(), 102400)
1696a6f4f7fSKlement Sekera            if len(read) > 0:
17045ec5708SOle Troan                split = read.decode('ascii',
17145ec5708SOle Troan                                    errors='backslashreplace').splitlines(True)
1726a6f4f7fSKlement Sekera                if len(stdout_fragment) > 0:
1736a6f4f7fSKlement Sekera                    split[0] = "%s%s" % (stdout_fragment, split[0])
1746a6f4f7fSKlement Sekera                if len(split) > 0 and split[-1].endswith("\n"):
1756a6f4f7fSKlement Sekera                    limit = None
1766a6f4f7fSKlement Sekera                else:
1776a6f4f7fSKlement Sekera                    limit = -1
1786a6f4f7fSKlement Sekera                    stdout_fragment = split[-1]
1796a6f4f7fSKlement Sekera                testclass.vpp_stdout_deque.extend(split[:limit])
1806a6f4f7fSKlement Sekera                if not testclass.cache_vpp_output:
1816a6f4f7fSKlement Sekera                    for line in split[:limit]:
1821a7ed5e3SBenoît Ganne                        testclass.logger.info(
1836a6f4f7fSKlement Sekera                            "VPP STDOUT: %s" % line.rstrip("\n"))
184acb9b8e8SKlement Sekera        if testclass.vpp.stderr.fileno() in readable:
1856a6f4f7fSKlement Sekera            read = os.read(testclass.vpp.stderr.fileno(), 102400)
1866a6f4f7fSKlement Sekera            if len(read) > 0:
1876ed154f7SOle Troan                split = read.decode('ascii',
1886ed154f7SOle Troan                                    errors='backslashreplace').splitlines(True)
1896a6f4f7fSKlement Sekera                if len(stderr_fragment) > 0:
1906a6f4f7fSKlement Sekera                    split[0] = "%s%s" % (stderr_fragment, split[0])
1916ed154f7SOle Troan                if len(split) > 0 and split[-1].endswith("\n"):
1926a6f4f7fSKlement Sekera                    limit = None
1936a6f4f7fSKlement Sekera                else:
1946a6f4f7fSKlement Sekera                    limit = -1
1956a6f4f7fSKlement Sekera                    stderr_fragment = split[-1]
1966ed154f7SOle Troan
1976a6f4f7fSKlement Sekera                testclass.vpp_stderr_deque.extend(split[:limit])
1986a6f4f7fSKlement Sekera                if not testclass.cache_vpp_output:
1996a6f4f7fSKlement Sekera                    for line in split[:limit]:
2001a7ed5e3SBenoît Ganne                        testclass.logger.error(
2016a6f4f7fSKlement Sekera                            "VPP STDERR: %s" % line.rstrip("\n"))
2026919b0deSPaul Vinciguerra                        # ignoring the dummy pipe here intentionally - the
2036919b0deSPaul Vinciguerra                        # flag will take care of properly terminating the loop
204277b89c9SKlement Sekera
205277b89c9SKlement Sekera
206defde0f8SPaul Vinciguerradef _is_skip_aarch64_set():
2070cbc71d7SPaul Vinciguerra    return BoolEnvironmentVariable('SKIP_AARCH64')
20868ebc83eSjuraj.linkes
2096aa58b73SKlement Sekera
210defde0f8SPaul Vinciguerrais_skip_aarch64_set = _is_skip_aarch64_set()
21168ebc83eSjuraj.linkes
212defde0f8SPaul Vinciguerra
213defde0f8SPaul Vinciguerradef _is_platform_aarch64():
21468ebc83eSjuraj.linkes    return platform.machine() == 'aarch64'
21568ebc83eSjuraj.linkes
2166aa58b73SKlement Sekera
217defde0f8SPaul Vinciguerrais_platform_aarch64 = _is_platform_aarch64()
218defde0f8SPaul Vinciguerra
21968ebc83eSjuraj.linkes
220defde0f8SPaul Vinciguerradef _running_extended_tests():
2210cbc71d7SPaul Vinciguerra    return BoolEnvironmentVariable("EXTENDED_TESTS")
22287134937SKlement Sekera
2236aa58b73SKlement Sekera
224defde0f8SPaul Vinciguerrarunning_extended_tests = _running_extended_tests()
22587134937SKlement Sekera
226defde0f8SPaul Vinciguerra
227d498c9ebSDave Barachdef _running_gcov_tests():
228d498c9ebSDave Barach    return BoolEnvironmentVariable("GCOV_TESTS")
229d498c9ebSDave Barach
230d498c9ebSDave Barach
231d498c9ebSDave Barachrunning_gcov_tests = _running_gcov_tests()
232d498c9ebSDave Barach
233d498c9ebSDave Barach
234defde0f8SPaul Vinciguerradef _running_on_centos():
23513a83ef4SKlement Sekera    os_id = os.getenv("OS_ID", "")
23613a83ef4SKlement Sekera    return True if "centos" in os_id.lower() else False
237d3e671e0SKlement Sekera
2386aa58b73SKlement Sekera
2393a350702SKlement Sekerarunning_on_centos = _running_on_centos()
240defde0f8SPaul Vinciguerra
241d3e671e0SKlement Sekera
242909a6a1eSKlement Sekeraclass KeepAliveReporter(object):
243909a6a1eSKlement Sekera    """
244909a6a1eSKlement Sekera    Singleton object which reports test start to parent process
245909a6a1eSKlement Sekera    """
246909a6a1eSKlement Sekera    _shared_state = {}
247909a6a1eSKlement Sekera
248909a6a1eSKlement Sekera    def __init__(self):
249909a6a1eSKlement Sekera        self.__dict__ = self._shared_state
250c7b03fe8SPaul Vinciguerra        self._pipe = None
251909a6a1eSKlement Sekera
252909a6a1eSKlement Sekera    @property
253909a6a1eSKlement Sekera    def pipe(self):
254909a6a1eSKlement Sekera        return self._pipe
255909a6a1eSKlement Sekera
256909a6a1eSKlement Sekera    @pipe.setter
257909a6a1eSKlement Sekera    def pipe(self, pipe):
258c7b03fe8SPaul Vinciguerra        if self._pipe is not None:
259909a6a1eSKlement Sekera            raise Exception("Internal error - pipe should only be set once.")
260909a6a1eSKlement Sekera        self._pipe = pipe
261909a6a1eSKlement Sekera
26240dd73bcSjuraj.linkes    def send_keep_alive(self, test, desc=None):
263909a6a1eSKlement Sekera        """
264909a6a1eSKlement Sekera        Write current test tmpdir & desc to keep-alive pipe to signal liveness
265909a6a1eSKlement Sekera        """
2663f6ff19aSKlement Sekera        if self.pipe is None:
2673f6ff19aSKlement Sekera            # if not running forked..
2683f6ff19aSKlement Sekera            return
2693f6ff19aSKlement Sekera
270909a6a1eSKlement Sekera        if isclass(test):
27140dd73bcSjuraj.linkes            desc = '%s (%s)' % (desc, unittest.util.strclass(test))
272909a6a1eSKlement Sekera        else:
27340dd73bcSjuraj.linkes            desc = test.id()
274909a6a1eSKlement Sekera
275e2efd12bSDave Wallace        self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid))
276909a6a1eSKlement Sekera
277909a6a1eSKlement Sekera
278f56b77a0SDamjan Marionclass VppTestCase(unittest.TestCase):
27986d87c40SMatej Klotton    """This subclass is a base class for VPP test cases that are implemented as
28086d87c40SMatej Klotton    classes. It provides methods to create and run test case.
281f62ae128SKlement Sekera    """
282f62ae128SKlement Sekera
283a45dc07cSOle Troan    extra_vpp_punt_config = []
284a45dc07cSOle Troan    extra_vpp_plugin_config = []
2859beabd8aSPaul Vinciguerra    logger = null_logger
286bfd7d294SPaul Vinciguerra    vapi_response_timeout = 5
287e88865d7SPavel Kotucek
288f62ae128SKlement Sekera    @property
289f62ae128SKlement Sekera    def packet_infos(self):
290f62ae128SKlement Sekera        """List of packet infos"""
291f62ae128SKlement Sekera        return self._packet_infos
292f62ae128SKlement Sekera
293dab231a1SKlement Sekera    @classmethod
294dab231a1SKlement Sekera    def get_packet_count_for_if_idx(cls, dst_if_index):
295dab231a1SKlement Sekera        """Get the number of packet info for specified destination if index"""
296dab231a1SKlement Sekera        if dst_if_index in cls._packet_count_for_dst_if_idx:
297dab231a1SKlement Sekera            return cls._packet_count_for_dst_if_idx[dst_if_index]
298dab231a1SKlement Sekera        else:
299dab231a1SKlement Sekera            return 0
300f62ae128SKlement Sekera
301f62ae128SKlement Sekera    @classmethod
302f62ae128SKlement Sekera    def instance(cls):
303f62ae128SKlement Sekera        """Return the instance of this testcase"""
304f62ae128SKlement Sekera        return cls.test_instance
305f56b77a0SDamjan Marion
306277b89c9SKlement Sekera    @classmethod
307277b89c9SKlement Sekera    def set_debug_flags(cls, d):
3082456433dSDave Wallace        cls.gdbserver_port = 7777
309277b89c9SKlement Sekera        cls.debug_core = False
310277b89c9SKlement Sekera        cls.debug_gdb = False
311277b89c9SKlement Sekera        cls.debug_gdbserver = False
3122456433dSDave Wallace        cls.debug_all = False
313277b89c9SKlement Sekera        if d is None:
314277b89c9SKlement Sekera            return
315277b89c9SKlement Sekera        dl = d.lower()
316277b89c9SKlement Sekera        if dl == "core":
317277b89c9SKlement Sekera            cls.debug_core = True
3182456433dSDave Wallace        elif dl == "gdb" or dl == "gdb-all":
319277b89c9SKlement Sekera            cls.debug_gdb = True
3202456433dSDave Wallace        elif dl == "gdbserver" or dl == "gdbserver-all":
321277b89c9SKlement Sekera            cls.debug_gdbserver = True
322277b89c9SKlement Sekera        else:
323277b89c9SKlement Sekera            raise Exception("Unrecognized DEBUG option: '%s'" % d)
3242456433dSDave Wallace        if dl == "gdb-all" or dl == "gdbserver-all":
3252456433dSDave Wallace            cls.debug_all = True
326277b89c9SKlement Sekera
32786ebba6bSPaul Vinciguerra    @staticmethod
32886ebba6bSPaul Vinciguerra    def get_least_used_cpu():
329184870acSjuraj.linkes        cpu_usage_list = [set(range(psutil.cpu_count()))]
330184870acSjuraj.linkes        vpp_processes = [p for p in psutil.process_iter(attrs=['pid', 'name'])
331184870acSjuraj.linkes                         if 'vpp_main' == p.info['name']]
332184870acSjuraj.linkes        for vpp_process in vpp_processes:
333184870acSjuraj.linkes            for cpu_usage_set in cpu_usage_list:
334184870acSjuraj.linkes                try:
335184870acSjuraj.linkes                    cpu_num = vpp_process.cpu_num()
336184870acSjuraj.linkes                    if cpu_num in cpu_usage_set:
337184870acSjuraj.linkes                        cpu_usage_set_index = cpu_usage_list.index(
338184870acSjuraj.linkes                            cpu_usage_set)
339184870acSjuraj.linkes                        if cpu_usage_set_index == len(cpu_usage_list) - 1:
340184870acSjuraj.linkes                            cpu_usage_list.append({cpu_num})
341184870acSjuraj.linkes                        else:
342184870acSjuraj.linkes                            cpu_usage_list[cpu_usage_set_index + 1].add(
343184870acSjuraj.linkes                                cpu_num)
344184870acSjuraj.linkes                        cpu_usage_set.remove(cpu_num)
345184870acSjuraj.linkes                        break
346184870acSjuraj.linkes                except psutil.NoSuchProcess:
347184870acSjuraj.linkes                    pass
348184870acSjuraj.linkes
349184870acSjuraj.linkes        for cpu_usage_set in cpu_usage_list:
350184870acSjuraj.linkes            if len(cpu_usage_set) > 0:
351184870acSjuraj.linkes                min_usage_set = cpu_usage_set
352184870acSjuraj.linkes                break
353184870acSjuraj.linkes
354184870acSjuraj.linkes        return random.choice(tuple(min_usage_set))
355184870acSjuraj.linkes
356f56b77a0SDamjan Marion    @classmethod
357f56b77a0SDamjan Marion    def setUpConstants(cls):
358f62ae128SKlement Sekera        """ Set-up the test case class based on environment variables """
3590cbc71d7SPaul Vinciguerra        cls.step = BoolEnvironmentVariable('STEP')
36013a83ef4SKlement Sekera        d = os.getenv("DEBUG", None)
3610cbc71d7SPaul Vinciguerra        # inverted case to handle '' == True
36213a83ef4SKlement Sekera        c = os.getenv("CACHE_OUTPUT", "1")
36313a83ef4SKlement Sekera        cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True
364277b89c9SKlement Sekera        cls.set_debug_flags(d)
365b8c72a4aSKlement Sekera        cls.vpp_bin = os.getenv('VPP_BIN', "vpp")
366b8c72a4aSKlement Sekera        cls.plugin_path = os.getenv('VPP_PLUGIN_PATH')
3677d31ab2aSDave Barach        cls.test_plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH')
36847e275bbSKlement Sekera        cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS')
36947e275bbSKlement Sekera        plugin_path = None
37047e275bbSKlement Sekera        if cls.plugin_path is not None:
37147e275bbSKlement Sekera            if cls.extern_plugin_path is not None:
37247e275bbSKlement Sekera                plugin_path = "%s:%s" % (
37347e275bbSKlement Sekera                    cls.plugin_path, cls.extern_plugin_path)
3746abbc288SKlement Sekera            else:
3756abbc288SKlement Sekera                plugin_path = cls.plugin_path
37647e275bbSKlement Sekera        elif cls.extern_plugin_path is not None:
37747e275bbSKlement Sekera            plugin_path = cls.extern_plugin_path
378a45dc07cSOle Troan        debug_cli = ""
37901bbbe91SKlement Sekera        if cls.step or cls.debug_gdb or cls.debug_gdbserver:
380a45dc07cSOle Troan            debug_cli = "cli-listen localhost:5002"
38180a7f0a8SKlement Sekera        coredump_size = None
38213a83ef4SKlement Sekera        size = os.getenv("COREDUMP_SIZE")
383a45dc07cSOle Troan        if size is not None:
384a45dc07cSOle Troan            coredump_size = "coredump-size %s" % size
385a45dc07cSOle Troan        if coredump_size is None:
386a45dc07cSOle Troan            coredump_size = "coredump-size unlimited"
387a45dc07cSOle Troan
388a45dc07cSOle Troan        cpu_core_number = cls.get_least_used_cpu()
389630ab584SKlement Sekera        if not hasattr(cls, "worker_config"):
390630ab584SKlement Sekera            cls.worker_config = ""
391a45dc07cSOle Troan
3924830e4f7SRay Kinsella        default_variant = os.getenv("VARIANT")
3934830e4f7SRay Kinsella        if default_variant is not None:
3944830e4f7SRay Kinsella            default_variant = "defaults { %s 100 }" % default_variant
3954830e4f7SRay Kinsella        else:
3964830e4f7SRay Kinsella            default_variant = ""
3974830e4f7SRay Kinsella
3987784140fSDave Barach        api_fuzzing = os.getenv("API_FUZZ")
3997784140fSDave Barach        if api_fuzzing is None:
4007784140fSDave Barach            api_fuzzing = 'off'
4017784140fSDave Barach
402a45dc07cSOle Troan        cls.vpp_cmdline = [cls.vpp_bin, "unix",
403a45dc07cSOle Troan                           "{", "nodaemon", debug_cli, "full-coredump",
404a45dc07cSOle Troan                           coredump_size, "runtime-dir", cls.tempdir, "}",
405a45dc07cSOle Troan                           "api-trace", "{", "on", "}", "api-segment", "{",
406a45dc07cSOle Troan                           "prefix", cls.shm_prefix, "}", "cpu", "{",
407630ab584SKlement Sekera                           "main-core", str(cpu_core_number),
408630ab584SKlement Sekera                           cls.worker_config, "}",
4094ed25985SDave Barach                           "physmem", "{", "max-size", "32m", "}",
4104ff09ae3SOle Troan                           "statseg", "{", "socket-name", cls.stats_sock, "}",
4114ff09ae3SOle Troan                           "socksvr", "{", "socket-name", cls.api_sock, "}",
4124830e4f7SRay Kinsella                           "node { ", default_variant, "}",
4137784140fSDave Barach                           "api-fuzz {", api_fuzzing, "}",
4144ff09ae3SOle Troan                           "plugins",
415a45dc07cSOle Troan                           "{", "plugin", "dpdk_plugin.so", "{", "disable",
4162e1c8967SOle Troan                           "}", "plugin", "rdma_plugin.so", "{", "disable",
417a45dc07cSOle Troan                           "}", "plugin", "unittest_plugin.so", "{", "enable",
418a45dc07cSOle Troan                           "}"] + cls.extra_vpp_plugin_config + ["}", ]
4194830e4f7SRay Kinsella
420a45dc07cSOle Troan        if cls.extra_vpp_punt_config is not None:
421a45dc07cSOle Troan            cls.vpp_cmdline.extend(cls.extra_vpp_punt_config)
42247e275bbSKlement Sekera        if plugin_path is not None:
423a45dc07cSOle Troan            cls.vpp_cmdline.extend(["plugin_path", plugin_path])
4247d31ab2aSDave Barach        if cls.test_plugin_path is not None:
4257d31ab2aSDave Barach            cls.vpp_cmdline.extend(["test_plugin_path", cls.test_plugin_path])
4267d31ab2aSDave Barach
427f37c3ba9SKlement Sekera        cls.logger.info("vpp_cmdline args: %s" % cls.vpp_cmdline)
428f37c3ba9SKlement Sekera        cls.logger.info("vpp_cmdline: %s" % " ".join(cls.vpp_cmdline))
429277b89c9SKlement Sekera
430277b89c9SKlement Sekera    @classmethod
431277b89c9SKlement Sekera    def wait_for_enter(cls):
432277b89c9SKlement Sekera        if cls.debug_gdbserver:
433277b89c9SKlement Sekera            print(double_line_delim)
434277b89c9SKlement Sekera            print("Spawned GDB server with PID: %d" % cls.vpp.pid)
435277b89c9SKlement Sekera        elif cls.debug_gdb:
436277b89c9SKlement Sekera            print(double_line_delim)
437277b89c9SKlement Sekera            print("Spawned VPP with PID: %d" % cls.vpp.pid)
438277b89c9SKlement Sekera        else:
439277b89c9SKlement Sekera            cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid)
440277b89c9SKlement Sekera            return
441277b89c9SKlement Sekera        print(single_line_delim)
4422456433dSDave Wallace        print("You can debug VPP using:")
443277b89c9SKlement Sekera        if cls.debug_gdbserver:
4443a9f11e6SPaul Vinciguerra            print("sudo gdb " + cls.vpp_bin +
4452456433dSDave Wallace                  " -ex 'target remote localhost:{port}'"
4462456433dSDave Wallace                  .format(port=cls.gdbserver_port))
4472456433dSDave Wallace            print("Now is the time to attach gdb by running the above "
4482456433dSDave Wallace                  "command, set up breakpoints etc., then resume VPP from "
449277b89c9SKlement Sekera                  "within gdb by issuing the 'continue' command")
4502456433dSDave Wallace            cls.gdbserver_port += 1
451277b89c9SKlement Sekera        elif cls.debug_gdb:
4523a9f11e6SPaul Vinciguerra            print("sudo gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid)
4532456433dSDave Wallace            print("Now is the time to attach gdb by running the above "
4542456433dSDave Wallace                  "command and set up breakpoints etc., then resume VPP from"
4552456433dSDave Wallace                  " within gdb by issuing the 'continue' command")
456277b89c9SKlement Sekera        print(single_line_delim)
457852f5ef9SPaul Vinciguerra        input("Press ENTER to continue running the testcase...")
458277b89c9SKlement Sekera
459277b89c9SKlement Sekera    @classmethod
460277b89c9SKlement Sekera    def run_vpp(cls):
461277b89c9SKlement Sekera        cmdline = cls.vpp_cmdline
462277b89c9SKlement Sekera
463277b89c9SKlement Sekera        if cls.debug_gdbserver:
464931be3acSKlement Sekera            gdbserver = '/usr/bin/gdbserver'
465931be3acSKlement Sekera            if not os.path.isfile(gdbserver) or \
466931be3acSKlement Sekera                    not os.access(gdbserver, os.X_OK):
467931be3acSKlement Sekera                raise Exception("gdbserver binary '%s' does not exist or is "
468931be3acSKlement Sekera                                "not executable" % gdbserver)
469931be3acSKlement Sekera
4702456433dSDave Wallace            cmdline = [gdbserver, 'localhost:{port}'
4712456433dSDave Wallace                       .format(port=cls.gdbserver_port)] + cls.vpp_cmdline
472277b89c9SKlement Sekera            cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline))
473277b89c9SKlement Sekera
474931be3acSKlement Sekera        try:
475931be3acSKlement Sekera            cls.vpp = subprocess.Popen(cmdline,
476931be3acSKlement Sekera                                       stdout=subprocess.PIPE,
4776e6ad64aSOle Troan                                       stderr=subprocess.PIPE)
47861e63bf4SPaul Vinciguerra        except subprocess.CalledProcessError as e:
47938a4ec73SPaul Vinciguerra            cls.logger.critical("Subprocess returned with non-0 return code: ("
48038a4ec73SPaul Vinciguerra                                "%s)", e.returncode)
48138a4ec73SPaul Vinciguerra            raise
48238a4ec73SPaul Vinciguerra        except OSError as e:
48338a4ec73SPaul Vinciguerra            cls.logger.critical("Subprocess returned with OS error: "
48438a4ec73SPaul Vinciguerra                                "(%s) %s", e.errno, e.strerror)
48538a4ec73SPaul Vinciguerra            raise
48638a4ec73SPaul Vinciguerra        except Exception as e:
48738a4ec73SPaul Vinciguerra            cls.logger.exception("Subprocess returned unexpected from "
48838a4ec73SPaul Vinciguerra                                 "%s:", cmdline)
489931be3acSKlement Sekera            raise
490931be3acSKlement Sekera
491277b89c9SKlement Sekera        cls.wait_for_enter()
492cd8e318aSPierre Pfister
4933e9b7a21SDave Wallace    @classmethod
494b31d3935SAndrew Yourtchenko    def wait_for_coredump(cls):
495b31d3935SAndrew Yourtchenko        corefile = cls.tempdir + "/core"
4963e9b7a21SDave Wallace        if os.path.isfile(corefile):
497b31d3935SAndrew Yourtchenko            cls.logger.error("Waiting for coredump to complete: %s", corefile)
4983e9b7a21SDave Wallace            curr_size = os.path.getsize(corefile)
499b31d3935SAndrew Yourtchenko            deadline = time.time() + 60
5003e9b7a21SDave Wallace            ok = False
5013e9b7a21SDave Wallace            while time.time() < deadline:
5023e9b7a21SDave Wallace                cls.sleep(1)
5033e9b7a21SDave Wallace                size = curr_size
5043e9b7a21SDave Wallace                curr_size = os.path.getsize(corefile)
5053e9b7a21SDave Wallace                if size == curr_size:
5063e9b7a21SDave Wallace                    ok = True
5073e9b7a21SDave Wallace                    break
5083e9b7a21SDave Wallace            if not ok:
5093e9b7a21SDave Wallace                cls.logger.error("Timed out waiting for coredump to complete:"
5103e9b7a21SDave Wallace                                 " %s", corefile)
5113e9b7a21SDave Wallace            else:
5123e9b7a21SDave Wallace                cls.logger.error("Coredump complete: %s, size %d",
5133e9b7a21SDave Wallace                                 corefile, curr_size)
5143e9b7a21SDave Wallace
515f56b77a0SDamjan Marion    @classmethod
516f56b77a0SDamjan Marion    def setUpClass(cls):
517f62ae128SKlement Sekera        """
518f62ae128SKlement Sekera        Perform class setup before running the testcase
519f62ae128SKlement Sekera        Remove shared memory files, start vpp and connect the vpp-api
520f62ae128SKlement Sekera        """
5218d991d92SPaul Vinciguerra        super(VppTestCase, cls).setUpClass()
522acb9b8e8SKlement Sekera        gc.collect()  # run garbage collection first
523dfb5f2afSjuraj.linkes        cls.logger = get_logger(cls.__name__)
52445a95dd7SKlement Sekera        seed = os.environ["RND_SEED"]
52545a95dd7SKlement Sekera        random.seed(seed)
526dfb5f2afSjuraj.linkes        if hasattr(cls, 'parallel_handler'):
527dfb5f2afSjuraj.linkes            cls.logger.addHandler(cls.parallel_handler)
5283d9b92a0Sjuraj.linkes            cls.logger.propagate = False
52990cf21b5SPaul Vinciguerra
530f62ae128SKlement Sekera        cls.tempdir = tempfile.mkdtemp(
531f413bef1SKlement Sekera            prefix='vpp-unittest-%s-' % cls.__name__)
532611864f4SKlement Sekera        cls.stats_sock = "%s/stats.sock" % cls.tempdir
5334ff09ae3SOle Troan        cls.api_sock = "%s/api.sock" % cls.tempdir
534027dbd52SKlement Sekera        cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir)
535027dbd52SKlement Sekera        cls.file_handler.setFormatter(
536acb9b8e8SKlement Sekera            Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s',
537acb9b8e8SKlement Sekera                      datefmt="%H:%M:%S"))
538027dbd52SKlement Sekera        cls.file_handler.setLevel(DEBUG)
539027dbd52SKlement Sekera        cls.logger.addHandler(cls.file_handler)
54090cf21b5SPaul Vinciguerra        cls.logger.debug("--- setUpClass() for %s called ---" %
54190cf21b5SPaul Vinciguerra                         cls.__name__)
542184870acSjuraj.linkes        cls.shm_prefix = os.path.basename(cls.tempdir)
543f62ae128SKlement Sekera        os.chdir(cls.tempdir)
544277b89c9SKlement Sekera        cls.logger.info("Temporary dir is %s, shm prefix is %s",
545277b89c9SKlement Sekera                        cls.tempdir, cls.shm_prefix)
54645a95dd7SKlement Sekera        cls.logger.debug("Random seed is %s" % seed)
547f56b77a0SDamjan Marion        cls.setUpConstants()
548dab231a1SKlement Sekera        cls.reset_packet_infos()
5499225dee9SKlement Sekera        cls._captures = []
550f62ae128SKlement Sekera        cls.verbose = 0
551085f5c00SKlement Sekera        cls.vpp_dead = False
55210db26f7SKlement Sekera        cls.registry = VppObjectRegistry()
5533747c75aSKlement Sekera        cls.vpp_startup_failed = False
554909a6a1eSKlement Sekera        cls.reporter = KeepAliveReporter()
555f62ae128SKlement Sekera        # need to catch exceptions here because if we raise, then the cleanup
556f62ae128SKlement Sekera        # doesn't get called and we might end with a zombie vpp
557f62ae128SKlement Sekera        try:
558277b89c9SKlement Sekera            cls.run_vpp()
55940dd73bcSjuraj.linkes            cls.reporter.send_keep_alive(cls, 'setUpClass')
56040dd73bcSjuraj.linkes            VppTestResult.current_test_case_info = TestCaseInfo(
56140dd73bcSjuraj.linkes                cls.logger, cls.tempdir, cls.vpp.pid, cls.vpp_bin)
562e4504c63SKlement Sekera            cls.vpp_stdout_deque = deque()
563e4504c63SKlement Sekera            cls.vpp_stderr_deque = deque()
564acb9b8e8SKlement Sekera            cls.pump_thread_stop_flag = Event()
565acb9b8e8SKlement Sekera            cls.pump_thread_wakeup_pipe = os.pipe()
566acb9b8e8SKlement Sekera            cls.pump_thread = Thread(target=pump_output, args=(cls,))
567aeeac3bfSKlement Sekera            cls.pump_thread.daemon = True
568acb9b8e8SKlement Sekera            cls.pump_thread.start()
569611864f4SKlement Sekera            if cls.debug_gdb or cls.debug_gdbserver:
570bfd7d294SPaul Vinciguerra                cls.vapi_response_timeout = 0
571611864f4SKlement Sekera            cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls,
572bfd7d294SPaul Vinciguerra                                       cls.vapi_response_timeout)
573277b89c9SKlement Sekera            if cls.step:
574496b0deeSPaul Vinciguerra                hook = hookmodule.StepHook(cls)
575277b89c9SKlement Sekera            else:
576496b0deeSPaul Vinciguerra                hook = hookmodule.PollHook(cls)
577085f5c00SKlement Sekera            cls.vapi.register_hook(hook)
578611864f4SKlement Sekera            cls.statistics = VPPStats(socketname=cls.stats_sock)
5793747c75aSKlement Sekera            try:
5803747c75aSKlement Sekera                hook.poll_vpp()
58113a83ef4SKlement Sekera            except VppDiedError:
5823747c75aSKlement Sekera                cls.vpp_startup_failed = True
5833747c75aSKlement Sekera                cls.logger.critical(
5843747c75aSKlement Sekera                    "VPP died shortly after startup, check the"
5853747c75aSKlement Sekera                    " output to standard error for possible cause")
5863747c75aSKlement Sekera                raise
587277b89c9SKlement Sekera            try:
588277b89c9SKlement Sekera                cls.vapi.connect()
5891043fd38SPaul Vinciguerra            except vpp_papi.VPPIOError as e:
5901043fd38SPaul Vinciguerra                cls.logger.debug("Exception connecting to vapi: %s" % e)
5911043fd38SPaul Vinciguerra                cls.vapi.disconnect()
5921043fd38SPaul Vinciguerra
593277b89c9SKlement Sekera                if cls.debug_gdbserver:
594277b89c9SKlement Sekera                    print(colorize("You're running VPP inside gdbserver but "
595277b89c9SKlement Sekera                                   "VPP-API connection failed, did you forget "
596277b89c9SKlement Sekera                                   "to 'continue' VPP from within gdb?", RED))
597277b89c9SKlement Sekera                raise
598e64e5fffSPaul Vinciguerra        except vpp_papi.VPPRuntimeError as e:
599e64e5fffSPaul Vinciguerra            cls.logger.debug("%s" % e)
600e64e5fffSPaul Vinciguerra            cls.quit()
601e64e5fffSPaul Vinciguerra            raise
6024f05a8e4SAndrew Yourtchenko        except Exception as e:
6034f05a8e4SAndrew Yourtchenko            cls.logger.debug("Exception connecting to VPP: %s" % e)
604496b0deeSPaul Vinciguerra            cls.quit()
60513a83ef4SKlement Sekera            raise
606f62ae128SKlement Sekera
607c701e571SPaul Vinciguerra    @classmethod
608c701e571SPaul Vinciguerra    def _debug_quit(cls):
609c701e571SPaul Vinciguerra        if (cls.debug_gdbserver or cls.debug_gdb):
610c701e571SPaul Vinciguerra            try:
611c701e571SPaul Vinciguerra                cls.vpp.poll()
612c701e571SPaul Vinciguerra
613c701e571SPaul Vinciguerra                if cls.vpp.returncode is None:
614c701e571SPaul Vinciguerra                    print()
615c701e571SPaul Vinciguerra                    print(double_line_delim)
616c701e571SPaul Vinciguerra                    print("VPP or GDB server is still running")
617c701e571SPaul Vinciguerra                    print(single_line_delim)
618c701e571SPaul Vinciguerra                    input("When done debugging, press ENTER to kill the "
619c701e571SPaul Vinciguerra                          "process and finish running the testcase...")
620c701e571SPaul Vinciguerra            except AttributeError:
621c701e571SPaul Vinciguerra                pass
622c701e571SPaul Vinciguerra
623f56b77a0SDamjan Marion    @classmethod
624f56b77a0SDamjan Marion    def quit(cls):
625f62ae128SKlement Sekera        """
626f62ae128SKlement Sekera        Disconnect vpp-api, kill vpp and cleanup shared memory files
627f62ae128SKlement Sekera        """
628c701e571SPaul Vinciguerra        cls._debug_quit()
629277b89c9SKlement Sekera
630184870acSjuraj.linkes        # first signal that we want to stop the pump thread, then wake it up
631184870acSjuraj.linkes        if hasattr(cls, 'pump_thread_stop_flag'):
632184870acSjuraj.linkes            cls.pump_thread_stop_flag.set()
633184870acSjuraj.linkes        if hasattr(cls, 'pump_thread_wakeup_pipe'):
6347f99183aSOle Troan            os.write(cls.pump_thread_wakeup_pipe[1], b'ding dong wake up')
635acb9b8e8SKlement Sekera        if hasattr(cls, 'pump_thread'):
636acb9b8e8SKlement Sekera            cls.logger.debug("Waiting for pump thread to stop")
637acb9b8e8SKlement Sekera            cls.pump_thread.join()
638acb9b8e8SKlement Sekera        if hasattr(cls, 'vpp_stderr_reader_thread'):
639c701e571SPaul Vinciguerra            cls.logger.debug("Waiting for stderr pump to stop")
640acb9b8e8SKlement Sekera            cls.vpp_stderr_reader_thread.join()
641acb9b8e8SKlement Sekera
642f62ae128SKlement Sekera        if hasattr(cls, 'vpp'):
6430529a743SKlement Sekera            if hasattr(cls, 'vapi'):
644fd574087SOle Troan                cls.logger.debug(cls.vapi.vpp.get_stats())
645a7427ec6SPaul Vinciguerra                cls.logger.debug("Disconnecting class vapi client on %s",
646a7427ec6SPaul Vinciguerra                                 cls.__name__)
6470529a743SKlement Sekera                cls.vapi.disconnect()
648a7427ec6SPaul Vinciguerra                cls.logger.debug("Deleting class vapi attribute on %s",
649a7427ec6SPaul Vinciguerra                                 cls.__name__)
650acb9b8e8SKlement Sekera                del cls.vapi
651f62ae128SKlement Sekera            cls.vpp.poll()
652f62ae128SKlement Sekera            if cls.vpp.returncode is None:
653b31d3935SAndrew Yourtchenko                cls.wait_for_coredump()
654acb9b8e8SKlement Sekera                cls.logger.debug("Sending TERM to vpp")
655ad64687eSDave Barach                cls.vpp.terminate()
656acb9b8e8SKlement Sekera                cls.logger.debug("Waiting for vpp to die")
657acb9b8e8SKlement Sekera                cls.vpp.communicate()
658a7427ec6SPaul Vinciguerra            cls.logger.debug("Deleting class vpp attribute on %s",
659a7427ec6SPaul Vinciguerra                             cls.__name__)
660f62ae128SKlement Sekera            del cls.vpp
661f62ae128SKlement Sekera
6623747c75aSKlement Sekera        if cls.vpp_startup_failed:
6633747c75aSKlement Sekera            stdout_log = cls.logger.info
664