The daemon life cycle spans over the whole test case life time, which
significantly speeds up test cases that rely on fmt_pkt for packet byte
representations.

The speed-up comes from the fact that we no longer start a python
environment with all scapy modules imported on any fmt_pkt invocation;
but only on the first call to fmt_pkt.

The daemon is not started for test cases that don't trigger fmt_pkt.
(The server is started lazily as part of fmt_pkt call.)

For example, without the daemon, all tests that use fmt_pkt took the
following on my vagrant box:

real    15m27.117s
user    23m55.023s
sys     4m35.469s

With the daemon, the same set of tests run in:

real    4m34.092s
user    3m20.231s
sys     1m10.886s

We may want to make the daemon global, so that it's invoked once per
test suite run. But I haven't figured out, yet, how to run a trap to
clean up the deamon and its socket and pid files on suite exit (and not
on test case exit.)

Signed-off-by: Ihar Hrachyshka <ihrac...@redhat.com>
---
 tests/automake.mk     |   1 +
 tests/ovn-macros.at   |  27 ++++++++++--
 tests/scapy_server.py | 100 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 124 insertions(+), 4 deletions(-)
 create mode 100755 tests/scapy_server.py

diff --git a/tests/automake.mk b/tests/automake.mk
index eea0d00f4..0f7f1bd9a 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -309,6 +309,7 @@ tests_ovstest_LDADD = $(OVS_LIBDIR)/daemon.lo \
 CHECK_PYFILES = \
        tests/test-l7.py \
        tests/uuidfilt.py \
+       tests/scapy_server.py \
        tests/test-tcp-rst.py \
        tests/check_acl_log.py
 
diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at
index 13d5dc3d4..8b12f8e55 100644
--- a/tests/ovn-macros.at
+++ b/tests/ovn-macros.at
@@ -816,6 +816,15 @@ ovn_trace_client() {
     ovs-appctl -t $target trace "$@" | tee trace | sed '/^# /d'
 }
 
+wait_unix_socket() {
+    local socketfile=$1
+    for i in `seq 100`; do
+        nc -zU "$socketfile" && return 0
+        sleep 0.1
+    done
+    return 1
+}
+
 # Receives a string with scapy python code that represents a packet.
 # Returns a hex-string that contains bytes that reflect the packet symbolic
 # description.
@@ -833,10 +842,20 @@ ovn_trace_client() {
 # ovs-appctl netdev-dummy/receive $vif $packet
 #
 fmt_pkt() {
-    echo "from scapy.all import *; \
-          import binascii; \
-          out = binascii.hexlify(raw($1)); \
-          print(out.decode())" | $PYTHON3
+    sockfile=$ovs_base/scapy.sock
+    if [[ ! -e $sockfile ]]; then
+        start_scapy_server > /dev/null
+        wait_unix_socket $sockfile
+    fi
+    echo "$(echo $1 | nc -U $sockfile)"
+}
+
+start_scapy_server() {
+    pidfile=$ovs_base/scapy.pid
+    sockfile=$ovs_base/scapy.sock
+    "$top_srcdir"/tests/scapy_server.py --pidfile=$pidfile --sockfile=$sockfile
+    on_exit "test -e \"$pidfile\" && kill \`cat \"$pidfile\"\`"
+    on_exit "test -e \"$sockfile\" && rm \"$sockfile\""
 }
 
 sleep_sb() {
diff --git a/tests/scapy_server.py b/tests/scapy_server.py
new file mode 100755
index 000000000..a417d42c9
--- /dev/null
+++ b/tests/scapy_server.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+
+import argparse
+import atexit
+import os
+import socket
+import sys
+import threading
+
+import binascii
+from scapy.all import *  # noqa: F401,F403
+from scapy.all import raw
+
+
+_MAX_PATTERN_LEN = 1024 * 32
+
+
+def write_pidfile(pidfile):
+    pid = str(os.getpid())
+    with open(pidfile, 'w') as f:
+        f.write(pid)
+
+
+def remove_pidfile(pidfile):
+    os.remove(pidfile)
+
+
+def check_pidfile(pidfile):
+    if os.path.exists(pidfile):
+        with open(pidfile, 'r') as f:
+            pid = f.read().strip()
+            try:
+                pid = int(pid)
+                if os.path.exists(f"/proc/{pid}"):
+                    print("Scapy server is already running:", pid)
+                    sys.exit(1)
+            except ValueError:
+                sys.exit(1)
+
+
+def fork():
+    try:
+        pid = os.fork()
+        if pid > 0:
+            sys.exit(0)
+    except OSError as e:
+        print("Fork failed:", e)
+        sys.exit(1)
+
+
+def daemonize(pidfile):
+    fork()
+    check_pidfile(pidfile)
+    write_pidfile(pidfile)
+    atexit.register(remove_pidfile, pidfile)
+
+
+def process(data):
+    try:
+        return binascii.hexlify(raw(eval(data)))
+    except Exception:
+        return ""
+
+
+def handle_client(client_socket):
+    data = client_socket.recv(_MAX_PATTERN_LEN)
+    data = data.strip()
+    if data:
+        client_socket.send(process(data))
+    client_socket.close()
+
+
+def main(pidfile, socket_path):
+    daemonize(pidfile)
+
+    if os.path.exists(socket_path):
+        os.remove(socket_path)
+
+    server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+    server_socket.bind(socket_path)
+    server_socket.listen()
+
+    while True:
+        client_socket, _ = server_socket.accept()
+        handler = threading.Thread(target=handle_client, args=(client_socket,))
+        handler.start()
+
+
+def parse_args():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--pidfile", help="Path to PID file",
+                        default="/tmp/scapy.pid")
+    parser.add_argument("--sockfile", help="Path to Unix socket file",
+                        default="/tmp/scapy.sock")
+    return parser.parse_args()
+
+
+if __name__ == "__main__":
+    args = parse_args()
+    main(args.pidfile, args.sockfile)
-- 
2.38.1

_______________________________________________
dev mailing list
d...@openvswitch.org
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to