memif: Memif Test Case

Change-Id: Ic0d5fc6ccbd496afcc870b908ef799af7c804c30
Signed-off-by: Jakub Grajciar <jgrajcia@cisco.com>
diff --git a/test/remote_test.py b/test/remote_test.py
new file mode 100644
index 0000000..1659500
--- /dev/null
+++ b/test/remote_test.py
@@ -0,0 +1,380 @@
+#!/usr/bin/env python
+
+import os
+import unittest
+import inspect
+from multiprocessing import Process, Pipe
+from pickle import dumps, PicklingError
+from framework import VppTestCase
+
+
+class SerializableClassCopy(object):
+    """
+    Empty class used as a basis for a serializable copy of another class.
+    """
+    pass
+
+
+class RemoteClassAttr(object):
+    """
+    Wrapper around attribute of a remotely executed class.
+    """
+
+    def __init__(self, remote, attr):
+        self._path = [attr] if attr else []
+        self._remote = remote
+
+    def path_to_str(self):
+        return '.'.join(self._path)
+
+    def get_remote_value(self):
+        return self._remote._remote_exec(RemoteClass.GET, self.path_to_str())
+
+    def __repr__(self):
+        return self._remote._remote_exec(RemoteClass.REPR, self.path_to_str())
+
+    def __str__(self):
+        return self._remote._remote_exec(RemoteClass.STR, self.path_to_str())
+
+    def __getattr__(self, attr):
+        if attr[0] == '_':
+            raise AttributeError
+        self._path.append(attr)
+        return self
+
+    def __setattr__(self, attr, val):
+        if attr[0] == '_':
+            super(RemoteClassAttr, self).__setattr__(attr, val)
+            return
+        self._path.append(attr)
+        self._remote._remote_exec(RemoteClass.SETATTR, self.path_to_str(),
+                                  True, value=val)
+
+    def __call__(self, *args, **kwargs):
+        return self._remote._remote_exec(RemoteClass.CALL, self.path_to_str(),
+                                         True, *args, **kwargs)
+
+
+class RemoteClass(Process):
+    """
+    This class can wrap around and adapt the interface of another class,
+    and then delegate its execution to a newly forked child process.
+    Usage:
+        # Create a remotely executed instance of MyClass
+        object = RemoteClass(MyClass, arg1='foo', arg2='bar')
+        object.start_remote()
+        # Access the object normally as if it was an instance of your class.
+        object.my_attribute = 20
+        print object.my_attribute
+        print object.my_method(object.my_attribute)
+        object.my_attribute.nested_attribute = 'test'
+        # If you need the value of a remote attribute, use .get_remote_value
+        method. This method is automatically called when needed in the context
+        of a remotely executed class. E.g.:
+        if (object.my_attribute.get_remote_value() > 20):
+            object.my_attribute2 = object.my_attribute
+        # Destroy the instance
+        object.quit_remote()
+        object.terminate()
+    """
+
+    GET = 0       # Get attribute remotely
+    CALL = 1      # Call method remotely
+    SETATTR = 2   # Set attribute remotely
+    REPR = 3      # Get representation of a remote object
+    STR = 4       # Get string representation of a remote object
+    QUIT = 5      # Quit remote execution
+
+    PIPE_PARENT = 0  # Parent end of the pipe
+    PIPE_CHILD = 1  # Child end of the pipe
+
+    DEFAULT_TIMEOUT = 2  # default timeout for an operation to execute
+
+    def __init__(self, cls, *args, **kwargs):
+        super(RemoteClass, self).__init__()
+        self._cls = cls
+        self._args = args
+        self._kwargs = kwargs
+        self._timeout = RemoteClass.DEFAULT_TIMEOUT
+        self._pipe = Pipe()  # pipe for input/output arguments
+
+    def __repr__(self):
+        return repr(RemoteClassAttr(self, None))
+
+    def __str__(self):
+        return str(RemoteClassAttr(self, None))
+
+    def __call__(self, *args, **kwargs):
+        return self.RemoteClassAttr(self, None)()
+
+    def __getattr__(self, attr):
+        if attr[0] == '_' or not self.is_alive():
+            if hasattr(super(RemoteClass, self), '__getattr__'):
+                return super(RemoteClass, self).__getattr__(attr)
+            raise AttributeError
+        return RemoteClassAttr(self, attr)
+
+    def __setattr__(self, attr, val):
+        if attr[0] == '_' or not self.is_alive():
+            super(RemoteClass, self).__setattr__(attr, val)
+            return
+        setattr(RemoteClassAttr(self, None), attr, val)
+
+    def _remote_exec(self, op, path=None, ret=True, *args, **kwargs):
+        """
+        Execute given operation on a given, possibly nested, member remotely.
+        """
+        # automatically resolve remote objects in the arguments
+        mutable_args = list(args)
+        for i, val in enumerate(mutable_args):
+            if isinstance(val, RemoteClass) or \
+               isinstance(val, RemoteClassAttr):
+                mutable_args[i] = val.get_remote_value()
+        args = tuple(mutable_args)
+        for key, val in kwargs.iteritems():
+            if isinstance(val, RemoteClass) or \
+               isinstance(val, RemoteClassAttr):
+                kwargs[key] = val.get_remote_value()
+        # send request
+        args = self._make_serializable(args)
+        kwargs = self._make_serializable(kwargs)
+        self._pipe[RemoteClass.PIPE_PARENT].send((op, path, args, kwargs))
+        if not ret:
+            # no return value expected
+            return None
+        timeout = self._timeout
+        # adjust timeout specifically for the .sleep method
+        if path.split('.')[-1] == 'sleep':
+            if args and isinstance(args[0], (long, int)):
+                timeout += args[0]
+            elif 'timeout' in kwargs:
+                timeout += kwargs['timeout']
+        if not self._pipe[RemoteClass.PIPE_PARENT].poll(timeout):
+            return None
+        try:
+            rv = self._pipe[RemoteClass.PIPE_PARENT].recv()
+            rv = self._deserialize(rv)
+            return rv
+        except EOFError:
+            return None
+
+    def _get_local_object(self, path):
+        """
+        Follow the path to obtain a reference on the addressed nested attribute
+        """
+        obj = self._instance
+        for attr in path:
+            obj = getattr(obj, attr)
+        return obj
+
+    def _get_local_value(self, path):
+        try:
+            return self._get_local_object(path)
+        except AttributeError:
+            return None
+
+    def _call_local_method(self, path, *args, **kwargs):
+        try:
+            method = self._get_local_object(path)
+            return method(*args, **kwargs)
+        except AttributeError:
+            return None
+
+    def _set_local_attr(self, path, value):
+        try:
+            obj = self._get_local_object(path[:-1])
+            setattr(obj, path[-1], value)
+        except AttributeError:
+            pass
+        return None
+
+    def _get_local_repr(self, path):
+        try:
+            obj = self._get_local_object(path)
+            return repr(obj)
+        except AttributeError:
+            return None
+
+    def _get_local_str(self, path):
+        try:
+            obj = self._get_local_object(path)
+            return str(obj)
+        except AttributeError:
+            return None
+
+    def _serializable(self, obj):
+        """ Test if the given object is serializable """
+        try:
+            dumps(obj)
+            return True
+        except:
+            return False
+
+    def _make_obj_serializable(self, obj):
+        """
+        Make a serializable copy of an object.
+        Members which are difficult/impossible to serialize are stripped.
+        """
+        if self._serializable(obj):
+            return obj  # already serializable
+        copy = SerializableClassCopy()
+        # copy at least serializable attributes and properties
+        for name, member in inspect.getmembers(obj):
+            if name[0] == '_':  # skip private members
+                continue
+            if callable(member) and not isinstance(member, property):
+                continue
+            if not self._serializable(member):
+                continue
+            setattr(copy, name, member)
+        return copy
+
+    def _make_serializable(self, obj):
+        """
+        Make a serializable copy of an object or a list/tuple of objects.
+        Members which are difficult/impossible to serialize are stripped.
+        """
+        if (type(obj) is list) or (type(obj) is tuple):
+            rv = []
+            for item in obj:
+                rv.append(self._make_serializable(item))
+            if type(obj) is tuple:
+                rv = tuple(rv)
+            return rv
+        else:
+            return self._make_obj_serializable(obj)
+
+    def _deserialize_obj(self, obj):
+        return obj
+
+    def _deserialize(self, obj):
+        if (type(obj) is list) or (type(obj) is tuple):
+            rv = []
+            for item in obj:
+                rv.append(self._deserialize(item))
+            if type(obj) is tuple:
+                rv = tuple(rv)
+            return rv
+        else:
+            return self._deserialize_obj(obj)
+
+    def start_remote(self):
+        """ Start remote execution """
+        self.start()
+
+    def quit_remote(self):
+        """ Quit remote execution """
+        self._remote_exec(RemoteClass.QUIT, None, False)
+
+    def get_remote_value(self):
+        """ Get value of a remotely held object """
+        return RemoteClassAttr(self, None).get_remote_value()
+
+    def set_request_timeout(self, timeout):
+        """ Change request timeout """
+        self._timeout = timeout
+
+    def run(self):
+        """
+        Create instance of the wrapped class and execute operations
+        on it as requested by the parent process.
+        """
+        self._instance = self._cls(*self._args, **self._kwargs)
+        while True:
+            try:
+                rv = None
+                # get request from the parent process
+                (op, path, args,
+                 kwargs) = self._pipe[RemoteClass.PIPE_CHILD].recv()
+                args = self._deserialize(args)
+                kwargs = self._deserialize(kwargs)
+                path = path.split('.') if path else []
+                if op == RemoteClass.GET:
+                    rv = self._get_local_value(path)
+                elif op == RemoteClass.CALL:
+                    rv = self._call_local_method(path, *args, **kwargs)
+                elif op == RemoteClass.SETATTR and 'value' in kwargs:
+                    self._set_local_attr(path, kwargs['value'])
+                elif op == RemoteClass.REPR:
+                    rv = self._get_local_repr(path)
+                elif op == RemoteClass.STR:
+                    rv = self._get_local_str(path)
+                elif op == RemoteClass.QUIT:
+                    break
+                else:
+                    continue
+                # send return value
+                if not self._serializable(rv):
+                    rv = self._make_serializable(rv)
+                self._pipe[RemoteClass.PIPE_CHILD].send(rv)
+            except EOFError:
+                break
+        self._instance = None  # destroy the instance
+
+
+@unittest.skip("Remote Vpp Test Case Class")
+class RemoteVppTestCase(VppTestCase):
+    """ Re-use VppTestCase to create remote VPP segment
+
+        In your test case:
+
+        @classmethod
+        def setUpClass(cls):
+            # fork new process before clinet connects to VPP
+            cls.remote_test = RemoteClass(RemoteVppTestCase)
+
+            # start remote process
+            cls.remote_test.start_remote()
+
+            # set up your test case
+            super(MyTestCase, cls).setUpClass()
+
+            # set up remote test
+            cls.remote_test.setUpClass(cls.tempdir)
+
+        @classmethod
+        def tearDownClass(cls):
+            # tear down remote test
+            cls.remote_test.tearDownClass()
+
+            # stop remote process
+            cls.remote_test.quit_remote()
+
+            # tear down your test case
+            super(MyTestCase, cls).tearDownClass()
+    """
+
+    def __init__(self):
+        super(RemoteVppTestCase, self).__init__("emptyTest")
+
+    def __del__(self):
+        if hasattr(self, "vpp"):
+            cls.vpp.poll()
+            if cls.vpp.returncode is None:
+                cls.vpp.terminate()
+                cls.vpp.communicate()
+
+    @classmethod
+    def setUpClass(cls, tempdir):
+        # disable features unsupported in remote VPP
+        orig_env = dict(os.environ)
+        if 'STEP' in os.environ:
+            del os.environ['STEP']
+        if 'DEBUG' in os.environ:
+            del os.environ['DEBUG']
+        cls.tempdir_prefix = os.path.basename(tempdir) + "/"
+        super(RemoteVppTestCase, cls).setUpClass()
+        os.environ = orig_env
+
+    @unittest.skip("Empty test")
+    def emptyTest(self):
+        """ Do nothing """
+        pass
+
+    def setTestFunctionInfo(self, name, doc):
+        """
+        Store the name and documentation string of currently executed test
+        in the main VPP for logging purposes.
+        """
+        self._testMethodName = name
+        self._testMethodDoc = doc
diff --git a/test/test_memif.py b/test/test_memif.py
new file mode 100644
index 0000000..8fe2299
--- /dev/null
+++ b/test/test_memif.py
@@ -0,0 +1,259 @@
+import unittest
+
+from scapy.layers.l2 import Ether
+from scapy.layers.inet import IP, ICMP
+
+from framework import VppTestCase, VppTestRunner, running_extended_tests
+from remote_test import RemoteClass, RemoteVppTestCase
+from vpp_memif import *
+
+
+class TestMemif(VppTestCase):
+    """ Memif Test Case """
+
+    @classmethod
+    def setUpClass(cls):
+        # fork new process before client connects to VPP
+        cls.remote_test = RemoteClass(RemoteVppTestCase)
+        cls.remote_test.start_remote()
+        cls.remote_test.set_request_timeout(10)
+        super(TestMemif, cls).setUpClass()
+        cls.remote_test.setUpClass(cls.tempdir)
+        cls.create_pg_interfaces(range(1))
+        for pg in cls.pg_interfaces:
+            pg.config_ip4()
+            pg.admin_up()
+            pg.resolve_arp()
+
+    @classmethod
+    def tearDownClass(cls):
+        cls.remote_test.tearDownClass()
+        cls.remote_test.quit_remote()
+        for pg in cls.pg_interfaces:
+            pg.unconfig_ip4()
+            pg.set_table_ip4(0)
+            pg.admin_down()
+        super(TestMemif, cls).tearDownClass()
+
+    def tearDown(self):
+        remove_all_memif_vpp_config(self)
+        remove_all_memif_vpp_config(self.remote_test)
+        super(TestMemif, self).tearDown()
+
+    def _check_socket_filename(self, dump, socket_id, filename):
+        for d in dump:
+            if (d.socket_id == socket_id) and (
+                    d.socket_filename.rstrip("\0") == filename):
+                return True
+        return False
+
+    def test_memif_socket_filename_add_del(self):
+        """ Memif socket filenale add/del """
+
+        # dump default socket filename
+        dump = self.vapi.memif_socket_filename_dump()
+        self.assertTrue(
+            self._check_socket_filename(
+                dump, 0, "/run/vpp/memif.sock"))
+
+        memif_sockets = []
+        # existing path
+        memif_sockets.append(
+            VppSocketFilename(
+                self, 1, "/run/vpp/memif1.sock"))
+        # default path ("/run/vpp")
+        memif_sockets.append(
+            VppSocketFilename(
+                self,
+                2,
+                "memif2.sock",
+                add_default_folder=True))
+        # create new folder in default folder
+        memif_sockets.append(
+            VppSocketFilename(
+                self,
+                3,
+                "sock/memif3.sock",
+                add_default_folder=True))
+
+        for sock in memif_sockets:
+            sock.add_vpp_config()
+            dump = sock.query_vpp_config()
+            self.assertTrue(
+                self._check_socket_filename(
+                    dump,
+                    sock.socket_id,
+                    sock.socket_filename))
+
+        for sock in memif_sockets:
+            sock.remove_vpp_config()
+
+        dump = self.vapi.memif_socket_filename_dump()
+        self.assertTrue(
+            self._check_socket_filename(
+                dump, 0, "/run/vpp/memif.sock"))
+
+    def _create_delete_test_one_interface(self, memif):
+        memif.add_vpp_config()
+
+        dump = memif.query_vpp_config()
+
+        self.assertTrue(dump)
+        self.assertEqual(dump.sw_if_index, memif.sw_if_index)
+        self.assertEqual(dump.role, memif.role)
+        self.assertEqual(dump.mode, memif.mode)
+        if (memif.socket_id is not None):
+            self.assertEqual(dump.socket_id, memif.socket_id)
+
+        memif.remove_vpp_config()
+
+        dump = memif.query_vpp_config()
+
+        self.assertFalse(dump)
+
+    def _connect_test_one_interface(self, memif):
+        self.assertTrue(memif.wait_for_link_up(5))
+        dump = memif.query_vpp_config()
+
+        if memif.role == MEMIF_ROLE.SLAVE:
+            self.assertEqual(dump.ring_size, memif.ring_size)
+            self.assertEqual(dump.buffer_size, memif.buffer_size)
+        else:
+            self.assertEqual(dump.ring_size, 1)
+            self.assertEqual(dump.buffer_size, 0)
+
+    def _connect_test_interface_pair(self, memif0, memif1):
+        memif0.add_vpp_config()
+        memif1.add_vpp_config()
+
+        memif0.admin_up()
+        memif1.admin_up()
+
+        self._connect_test_one_interface(memif0)
+        self._connect_test_one_interface(memif1)
+
+        memif0.remove_vpp_config()
+        memif1.remove_vpp_config()
+
+    def test_memif_create_delete(self):
+        """ Memif create/delete interface """
+
+        memif = VppMemif(self, MEMIF_ROLE.SLAVE, MEMIF_MODE.ETHERNET)
+        self._create_delete_test_one_interface(memif)
+        memif.role = MEMIF_ROLE.MASTER
+        self._create_delete_test_one_interface(memif)
+
+    def test_memif_create_custom_socket(self):
+        """ Memif create with non-default socket filname """
+
+        memif_sockets = []
+        # existing path
+        memif_sockets.append(
+            VppSocketFilename(
+                self, 1, "/run/vpp/memif1.sock"))
+        # default path ("/run/vpp")
+        memif_sockets.append(
+            VppSocketFilename(
+                self,
+                2,
+                "memif2.sock",
+                add_default_folder=True))
+        # create new folder in default folder
+        memif_sockets.append(
+            VppSocketFilename(
+                self,
+                3,
+                "sock/memif3.sock",
+                add_default_folder=True))
+
+        memif = VppMemif(self, MEMIF_ROLE.SLAVE, MEMIF_MODE.ETHERNET)
+
+        for sock in memif_sockets:
+            sock.add_vpp_config()
+            memif.socket_id = sock.socket_id
+            memif.role = MEMIF_ROLE.SLAVE
+            self._create_delete_test_one_interface(memif)
+            memif.role = MEMIF_ROLE.MASTER
+            self._create_delete_test_one_interface(memif)
+
+    def test_memif_connect(self):
+        """ Memif connect """
+        memif = VppMemif(
+            self,
+            MEMIF_ROLE.SLAVE,
+            MEMIF_MODE.ETHERNET,
+            ring_size=1024,
+            buffer_size=2048)
+        remote_memif = VppMemif(
+            self.remote_test,
+            MEMIF_ROLE.MASTER,
+            MEMIF_MODE.ETHERNET,
+            ring_size=1024,
+            buffer_size=2048)
+
+        self._connect_test_interface_pair(memif, remote_memif)
+
+        memif.role = MEMIF_ROLE.MASTER
+        remote_memif.role = MEMIF_ROLE.SLAVE
+
+        self._connect_test_interface_pair(memif, remote_memif)
+
+    def _create_icmp(self, pg, memif, num):
+        pkts = []
+        for i in range(num):
+            pkt = (Ether(dst=pg.local_mac, src=pg.remote_mac) /
+                   IP(src=pg.remote_ip4, dst=memif.ip4_addr) /
+                   ICMP(id=memif.if_id, type='echo-request', seq=i))
+            pkts.append(pkt)
+        return pkts
+
+    def _verify_icmp(self, pg, memif, rx, seq):
+        ip = rx[IP]
+        self.assertEqual(ip.src, memif.ip4_addr)
+        self.assertEqual(ip.dst, pg.remote_ip4)
+        self.assertEqual(ip.proto, 1)
+        icmp = rx[ICMP]
+        self.assertEqual(icmp.type, 0)  # echo-reply
+        self.assertEqual(icmp.id, memif.if_id)
+        self.assertEqual(icmp.seq, seq)
+
+    def test_memif_ping(self):
+        """ Memif ping """
+        memif = VppMemif(self, MEMIF_ROLE.MASTER, MEMIF_MODE.ETHERNET)
+        remote_memif = VppMemif(self.remote_test, MEMIF_ROLE.SLAVE,
+                                MEMIF_MODE.ETHERNET)
+
+        memif.add_vpp_config()
+        memif.config_ip4()
+        memif.admin_up()
+
+        remote_memif.add_vpp_config()
+        remote_memif.config_ip4()
+        remote_memif.admin_up()
+
+        self.assertTrue(memif.wait_for_link_up(5))
+        self.assertTrue(remote_memif.wait_for_link_up(5))
+
+        # add routing to remote vpp
+        dst_addr = socket.inet_pton(socket.AF_INET, self.pg0._local_ip4_subnet)
+        dst_addr_len = 24
+        next_hop_addr = socket.inet_pton(socket.AF_INET, memif.ip4_addr)
+        self.remote_test.vapi.ip_add_del_route(
+            dst_addr, dst_addr_len, next_hop_addr)
+
+        # create ICMP echo-request from local pg to remote memif
+        packet_num = 10
+        pkts = self._create_icmp(self.pg0, remote_memif, packet_num)
+
+        self.pg0.add_stream(pkts)
+        self.pg_enable_capture(self.pg_interfaces)
+        self.pg_start()
+        capture = self.pg0.get_capture(packet_num, timeout=2)
+        seq = 0
+        for c in capture:
+            self._verify_icmp(self.pg0, remote_memif, c, seq)
+            seq += 1
+
+
+if __name__ == '__main__':
+    unittest.main(testRunner=VppTestRunner)
diff --git a/test/vpp_memif.py b/test/vpp_memif.py
new file mode 100644
index 0000000..2095480
--- /dev/null
+++ b/test/vpp_memif.py
@@ -0,0 +1,140 @@
+import socket
+
+from vpp_object import VppObject
+
+
+class MEMIF_ROLE:
+    MASTER = 0
+    SLAVE = 1
+
+
+class MEMIF_MODE:
+    ETHERNET = 0
+    IP = 1
+    PUNT_INJECT = 2
+
+
+def get_if_dump(dump, sw_if_index):
+    for d in dump:
+        if (d.sw_if_index == sw_if_index):
+            return d
+
+
+def query_all_memif_vpp_config(_test):
+    return _test.vapi.memif_dump()
+
+
+def remove_all_memif_vpp_config(_test):
+    dump = _test.vapi.memif_dump()
+    for d in dump:
+        _test.vapi.memif_delete(d.sw_if_index)
+    dump = _test.vapi.memif_socket_filename_dump()
+    for d in dump:
+        if d.socket_id != 0:
+            _test.vapi.memif_socket_filename_add_del(
+                0, d.socket_id, d.socket_filename)
+
+
+class VppSocketFilename(VppObject):
+    def __init__(self, test, socket_id, socket_filename,
+                 add_default_folder=False):
+        self._test = test
+        self.socket_id = socket_id
+        self.socket_filename = socket_filename
+
+        # if True insert default socket folder before socket filename,
+        # after adding vpp config
+        self.add_default_folder = add_default_folder
+
+    def add_vpp_config(self):
+        rv = self._test.vapi.memif_socket_filename_add_del(
+            1, self.socket_id, self.socket_filename)
+        if self.add_default_folder:
+            self.socket_filename = "/run/vpp/" + self.socket_filename
+        return rv
+
+    def remove_vpp_config(self):
+        return self._test.vapi.memif_socket_filename_add_del(
+            0, self.socket_id, self.socket_filename)
+
+    def query_vpp_config(self):
+        return self._test.vapi.memif_socket_filename_dump()
+
+    def __str__(self):
+        return self.object_id()
+
+    def object_id(self):
+        return "%d" % (self.socket_id)
+
+
+class VppMemif(VppObject):
+    def __init__(self, test, role, mode, rx_queues=0, tx_queues=0, if_id=0,
+                 socket_id=0, secret="", ring_size=0, buffer_size=0,
+                 hw_addr=""):
+        self._test = test
+        self.role = role
+        self.mode = mode
+        self.rx_queues = rx_queues
+        self.tx_queues = tx_queues
+        self.if_id = if_id
+        self.socket_id = socket_id
+        self.secret = secret
+        self.ring_size = ring_size
+        self.buffer_size = buffer_size
+        self.hw_addr = hw_addr
+        self.sw_if_index = None
+        self.ip4_addr = "192.168.%d.%d" % (self.if_id + 1, self.role + 1)
+        self.ip4_addr_len = 24
+
+    def add_vpp_config(self):
+        rv = self._test.vapi.memif_create(self.role, self.mode, self.rx_queues,
+                                          self.tx_queues, self.if_id,
+                                          self.socket_id, self.secret,
+                                          self.ring_size, self.buffer_size,
+                                          self.hw_addr)
+        self.sw_if_index = rv.sw_if_index
+        return self.sw_if_index
+
+    def admin_up(self):
+        if self.sw_if_index:
+            return self._test.vapi.sw_interface_set_flags(self.sw_if_index, 1)
+
+    def admin_down(self):
+        if self.sw_if_index:
+            return self._test.vapi.sw_interface_set_flags(self.sw_if_index, 0)
+
+    def wait_for_link_up(self, timeout, step=1):
+        if not self.sw_if_index:
+            return False
+        while True:
+            dump = self.query_vpp_config()
+            if dump.link_up_down == 1:
+                return True
+            self._test.sleep(step)
+            timeout -= step
+            if timeout <= 0:
+                return False
+
+    def config_ip4(self):
+        return self._test.vapi.sw_interface_add_del_address(
+            self.sw_if_index, socket.inet_pton(
+                socket.AF_INET, self.ip4_addr), self.ip4_addr_len)
+
+    def remove_vpp_config(self):
+        self._test.vapi.memif_delete(self.sw_if_index)
+        self.sw_if_index = None
+
+    def query_vpp_config(self):
+        if not self.sw_if_index:
+            return None
+        dump = self._test.vapi.memif_dump()
+        return get_if_dump(dump, self.sw_if_index)
+
+    def __str__(self):
+        return self.object_id()
+
+    def object_id(self):
+        if self.sw_if_index:
+            return "%d:%d:%d" % (self.role, self.if_id, self.sw_if_index)
+        else:
+            return "%d:%d:None" % (self.role, self.if_id)
diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py
index b814da2..f63ca6a 100644
--- a/test/vpp_papi_provider.py
+++ b/test/vpp_papi_provider.py
@@ -3824,3 +3824,44 @@
 
     def pipe_dump(self):
         return self.api(self.papi.pipe_dump, {})
+
+    def memif_create(
+            self,
+            role,
+            mode,
+            rx_queues=None,
+            tx_queues=None,
+            _id=None,
+            socket_id=None,
+            secret=None,
+            ring_size=None,
+            buffer_size=None,
+            hw_addr=None):
+        return self.api(self.papi.memif_create,
+                        {'role': role,
+                         'mode': mode,
+                         'rx_queues': rx_queues,
+                         'tx_queues': tx_queues,
+                         'id': _id,
+                         'socket_id': socket_id,
+                         'secret': secret,
+                         'ring_size': ring_size,
+                         'buffer_size': buffer_size,
+                         'hw_addr': hw_addr})
+
+    def memif_delete(self, sw_if_index):
+        return self.api(self.papi.memif_delete, {'sw_if_index': sw_if_index})
+
+    def memif_dump(self):
+        return self.api(self.papi.memif_dump, {})
+
+    def memif_socket_filename_add_del(
+            self, is_add, socket_id, socket_filename):
+        return self.api(
+            self.papi.memif_socket_filename_add_del,
+            {'is_add': is_add,
+             'socket_id': socket_id,
+             'socket_filename': socket_filename})
+
+    def memif_socket_filename_dump(self):
+        return self.api(self.papi.memif_socket_filename_dump, {})