2021-05-11 10:31:18 -07:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
# Utility functions for QEMU tests ##
|
|
|
|
|
|
|
|
import subprocess
|
2022-10-04 14:22:05 -07:00
|
|
|
import sys
|
2023-07-28 16:33:30 -07:00
|
|
|
import os
|
2024-10-31 18:55:27 +00:00
|
|
|
import time
|
|
|
|
import random
|
|
|
|
import string
|
|
|
|
from multiprocessing import Lock, Process
|
|
|
|
|
|
|
|
lock = Lock()
|
2021-05-11 10:31:18 -07:00
|
|
|
|
|
|
|
|
2023-10-24 12:53:10 +02:00
|
|
|
def can_create_namespaces(namespace="vpp_chk_4212"):
|
2023-06-20 14:52:08 +00:00
|
|
|
"""Check if the environment allows creating the namespaces"""
|
2024-10-31 18:55:27 +00:00
|
|
|
with lock:
|
|
|
|
try:
|
|
|
|
result = subprocess.run(
|
|
|
|
["ip", "netns", "add", namespace], capture_output=True
|
|
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
|
|
return False
|
|
|
|
subprocess.run(["ip", "netns", "del", namespace], capture_output=True)
|
|
|
|
return True
|
|
|
|
except Exception:
|
2023-06-20 14:52:08 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
|
2024-10-31 18:55:27 +00:00
|
|
|
def create_namespace(history_file, ns=None):
|
|
|
|
"""Create one or more namespaces."""
|
|
|
|
|
|
|
|
with lock:
|
|
|
|
namespaces = []
|
|
|
|
retries = 5
|
|
|
|
|
|
|
|
if ns is None:
|
|
|
|
result = None
|
|
|
|
|
|
|
|
for retry in range(retries):
|
|
|
|
suffix = "".join(
|
|
|
|
random.choices(string.ascii_lowercase + string.digits, k=8)
|
|
|
|
)
|
|
|
|
new_namespace_name = f"vpp_ns{suffix}"
|
|
|
|
# Check if the namespace already exists
|
|
|
|
result = subprocess.run(
|
|
|
|
["ip", "netns", "add", new_namespace_name],
|
|
|
|
capture_output=True,
|
|
|
|
text=True,
|
|
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
|
|
with open(history_file, "a") as ns_file:
|
|
|
|
ns_file.write(f"{new_namespace_name}\n")
|
|
|
|
return new_namespace_name
|
|
|
|
error_message = result.stderr if result else "Unknown error"
|
|
|
|
raise Exception(
|
|
|
|
f"Failed to generate a unique namespace name after {retries} attempts."
|
|
|
|
f"Error from last attempt: {error_message}"
|
|
|
|
)
|
|
|
|
elif isinstance(ns, str):
|
|
|
|
namespaces = [ns]
|
|
|
|
else:
|
|
|
|
namespaces = ns
|
2022-10-04 14:22:05 -07:00
|
|
|
|
|
|
|
for namespace in namespaces:
|
2024-10-31 18:55:27 +00:00
|
|
|
for attempt in range(retries):
|
|
|
|
result = subprocess.run(
|
|
|
|
["ip", "netns", "add", namespace],
|
|
|
|
capture_output=True,
|
|
|
|
text=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
with open(history_file, "a") as ns_file:
|
|
|
|
ns_file.write(f"{namespace}\n")
|
|
|
|
break
|
|
|
|
if attempt >= retries - 1:
|
|
|
|
raise Exception(
|
|
|
|
f"Failed to create namespace {namespace} after {retries} attempts. Error: {result.stderr.decode()}"
|
|
|
|
)
|
|
|
|
return ns
|
2021-05-11 10:31:18 -07:00
|
|
|
|
|
|
|
|
2022-10-04 14:22:05 -07:00
|
|
|
def add_namespace_route(ns, prefix, gw_ip):
|
|
|
|
"""Add a route to a namespace.
|
|
|
|
arguments:
|
|
|
|
ns -- namespace string value
|
|
|
|
prefix -- NETWORK/MASK or "default"
|
|
|
|
gw_ip -- Gateway IP
|
|
|
|
"""
|
2024-10-31 18:55:27 +00:00
|
|
|
with lock:
|
|
|
|
try:
|
|
|
|
subprocess.run(
|
|
|
|
["ip", "netns", "exec", ns, "ip", "route", "add", prefix, "via", gw_ip],
|
|
|
|
capture_output=True,
|
|
|
|
)
|
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
raise Exception("Error adding route to namespace:", e.output)
|
2022-10-04 14:22:05 -07:00
|
|
|
|
|
|
|
|
2024-10-31 18:55:27 +00:00
|
|
|
def delete_all_host_interfaces(history_file):
|
|
|
|
"""Delete all host interfaces whose names have been added to the history file."""
|
|
|
|
|
|
|
|
with lock:
|
|
|
|
if os.path.exists(history_file):
|
|
|
|
with open(history_file, "r") as if_file:
|
|
|
|
for line in if_file:
|
|
|
|
if_name = line.strip()
|
|
|
|
if if_name:
|
|
|
|
_delete_host_interfaces(if_name)
|
|
|
|
os.remove(history_file)
|
2022-10-04 14:22:05 -07:00
|
|
|
|
2024-10-31 18:55:27 +00:00
|
|
|
|
|
|
|
def _delete_host_interfaces(*host_interface_names):
|
|
|
|
"""Delete host interfaces.
|
2022-10-04 14:22:05 -07:00
|
|
|
arguments:
|
|
|
|
host_interface_names - sequence of host interface names to be deleted
|
|
|
|
"""
|
|
|
|
for host_interface_name in host_interface_names:
|
2024-10-31 18:55:27 +00:00
|
|
|
retries = 3
|
|
|
|
for attempt in range(retries):
|
|
|
|
check_result = subprocess.run(
|
|
|
|
["ip", "link", "show", host_interface_name],
|
|
|
|
capture_output=True,
|
|
|
|
text=True,
|
2022-10-04 14:22:05 -07:00
|
|
|
)
|
2024-10-31 18:55:27 +00:00
|
|
|
if check_result.returncode != 0:
|
|
|
|
break
|
|
|
|
|
|
|
|
result = subprocess.run(
|
|
|
|
["ip", "link", "del", host_interface_name],
|
|
|
|
capture_output=True,
|
|
|
|
text=True,
|
|
|
|
)
|
|
|
|
|
|
|
|
if result.returncode == 0:
|
|
|
|
break
|
|
|
|
if attempt < retries - 1:
|
|
|
|
time.sleep(1)
|
|
|
|
else:
|
|
|
|
raise Exception(
|
|
|
|
f"Failed to delete host interface {host_interface_name} after {retries} attempts"
|
|
|
|
)
|
2022-10-04 14:22:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
def create_host_interface(
|
2024-10-31 18:55:27 +00:00
|
|
|
history_file, host_namespace, *host_ip_prefixes, vpp_if_name=None, host_if_name=None
|
2022-10-04 14:22:05 -07:00
|
|
|
):
|
|
|
|
"""Create a host interface of type veth.
|
|
|
|
arguments:
|
|
|
|
host_namespace -- host namespace into which the host_interface needs to be set
|
|
|
|
host_ip_prefixes -- a sequence of ip/prefix-lengths to be set
|
|
|
|
on the host_interface
|
2024-10-31 18:55:27 +00:00
|
|
|
vpp_if_name -- name of the veth interface on the VPP side
|
|
|
|
host_if_name -- name of the veth interface on the host side
|
2022-10-04 14:22:05 -07:00
|
|
|
"""
|
2024-10-31 18:55:27 +00:00
|
|
|
with lock:
|
|
|
|
retries = 5
|
|
|
|
|
|
|
|
for attempt in range(retries):
|
|
|
|
if_name = (
|
|
|
|
host_if_name
|
|
|
|
or f"hostif{''.join(random.choices(string.ascii_lowercase + string.digits, k=8))}"
|
|
|
|
)
|
|
|
|
new_vpp_if_name = (
|
|
|
|
vpp_if_name
|
|
|
|
or f"vppout{''.join(random.choices(string.ascii_lowercase + string.digits, k=8))}"
|
|
|
|
)
|
|
|
|
|
|
|
|
result = subprocess.run(
|
|
|
|
[
|
|
|
|
"ip",
|
|
|
|
"link",
|
|
|
|
"add",
|
|
|
|
"name",
|
|
|
|
new_vpp_if_name,
|
|
|
|
"type",
|
|
|
|
"veth",
|
|
|
|
"peer",
|
|
|
|
"name",
|
|
|
|
if_name,
|
|
|
|
],
|
|
|
|
capture_output=True,
|
|
|
|
)
|
|
|
|
if result.returncode == 0:
|
|
|
|
host_if_name = if_name
|
|
|
|
vpp_if_name = new_vpp_if_name
|
|
|
|
with open(history_file, "a") as if_file:
|
|
|
|
if_file.write(f"{host_if_name}\n{vpp_if_name}\n")
|
|
|
|
break
|
|
|
|
if attempt >= retries - 1:
|
|
|
|
raise Exception(
|
|
|
|
f"Failed to create host interface {if_name} and vpp {new_vpp_if_name} after {retries} attempts. Error: {result.stderr.decode()}"
|
|
|
|
)
|
2022-10-04 14:22:05 -07:00
|
|
|
|
2024-10-31 18:55:27 +00:00
|
|
|
result = subprocess.run(
|
|
|
|
["ip", "link", "set", host_if_name, "netns", host_namespace],
|
2022-10-04 14:22:05 -07:00
|
|
|
capture_output=True,
|
|
|
|
)
|
2024-10-31 18:55:27 +00:00
|
|
|
if result.returncode != 0:
|
|
|
|
raise Exception(
|
|
|
|
f"Error setting host interface namespace: {result.stderr.decode()}"
|
|
|
|
)
|
2022-10-04 14:22:05 -07:00
|
|
|
|
2024-10-31 18:55:27 +00:00
|
|
|
result = subprocess.run(
|
|
|
|
["ip", "link", "set", "dev", vpp_if_name, "up"], capture_output=True
|
2022-10-04 14:22:05 -07:00
|
|
|
)
|
2024-10-31 18:55:27 +00:00
|
|
|
if result.returncode != 0:
|
|
|
|
raise Exception(
|
|
|
|
f"Error bringing up the host interface: {result.stderr.decode()}"
|
|
|
|
)
|
2022-10-04 14:22:05 -07:00
|
|
|
|
2024-10-31 18:55:27 +00:00
|
|
|
result = subprocess.run(
|
2022-10-04 14:22:05 -07:00
|
|
|
[
|
|
|
|
"ip",
|
|
|
|
"netns",
|
|
|
|
"exec",
|
|
|
|
host_namespace,
|
|
|
|
"ip",
|
|
|
|
"link",
|
|
|
|
"set",
|
|
|
|
"dev",
|
2024-10-31 18:55:27 +00:00
|
|
|
host_if_name,
|
2022-10-04 14:22:05 -07:00
|
|
|
"up",
|
|
|
|
],
|
|
|
|
capture_output=True,
|
|
|
|
)
|
2024-10-31 18:55:27 +00:00
|
|
|
if result.returncode != 0:
|
|
|
|
raise Exception(
|
|
|
|
f"Error bringing up the host interface in namespace: {result.stderr.decode()}"
|
2022-10-04 14:22:05 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
for host_ip_prefix in host_ip_prefixes:
|
2024-10-31 18:55:27 +00:00
|
|
|
result = subprocess.run(
|
2022-10-04 14:22:05 -07:00
|
|
|
[
|
|
|
|
"ip",
|
|
|
|
"netns",
|
|
|
|
"exec",
|
|
|
|
host_namespace,
|
|
|
|
"ip",
|
|
|
|
"addr",
|
|
|
|
"add",
|
|
|
|
host_ip_prefix,
|
|
|
|
"dev",
|
2024-10-31 18:55:27 +00:00
|
|
|
host_if_name,
|
2022-10-04 14:22:05 -07:00
|
|
|
],
|
|
|
|
capture_output=True,
|
|
|
|
)
|
2024-10-31 18:55:27 +00:00
|
|
|
if result.returncode != 0:
|
|
|
|
raise Exception(
|
|
|
|
f"Error setting ip prefix on the host interface: {result.stderr.decode()}"
|
2022-10-04 14:22:05 -07:00
|
|
|
)
|
2024-10-31 18:55:27 +00:00
|
|
|
|
|
|
|
return host_if_name, vpp_if_name
|
2022-10-04 14:22:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
def set_interface_mtu(namespace, interface, mtu, logger):
|
2024-10-31 18:55:27 +00:00
|
|
|
"""Set an MTU number on a linux device interface."""
|
2022-10-04 14:22:05 -07:00
|
|
|
args = ["ip", "link", "set", "mtu", str(mtu), "dev", interface]
|
|
|
|
if namespace:
|
|
|
|
args = ["ip", "netns", "exec", namespace] + args
|
2024-10-31 18:55:27 +00:00
|
|
|
with lock:
|
|
|
|
retries = 3
|
|
|
|
for attempt in range(retries):
|
|
|
|
result = subprocess.run(args, capture_output=True)
|
|
|
|
if result.returncode == 0:
|
|
|
|
break
|
|
|
|
if attempt < retries - 1:
|
|
|
|
time.sleep(1)
|
|
|
|
else:
|
|
|
|
raise Exception(
|
|
|
|
f"Failed to set MTU on interface {interface} in namespace {namespace} after {retries} attempts"
|
|
|
|
)
|
2022-10-04 14:22:05 -07:00
|
|
|
|
|
|
|
|
|
|
|
def enable_interface_gso(namespace, interface):
|
2024-10-31 18:55:27 +00:00
|
|
|
"""Enable GSO offload on a linux device interface."""
|
2022-10-04 14:22:05 -07:00
|
|
|
args = ["ethtool", "-K", interface, "rx", "on", "tx", "on"]
|
|
|
|
if namespace:
|
|
|
|
args = ["ip", "netns", "exec", namespace] + args
|
2024-10-31 18:55:27 +00:00
|
|
|
with lock:
|
|
|
|
result = subprocess.run(args, capture_output=True)
|
|
|
|
if result.returncode != 0:
|
|
|
|
raise Exception(
|
|
|
|
f"Error enabling GSO offload on interface {interface} in namespace {namespace}: {result.stderr.decode()}"
|
2022-10-04 14:22:05 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def disable_interface_gso(namespace, interface):
|
2024-10-31 18:55:27 +00:00
|
|
|
"""Disable GSO offload on a linux device interface."""
|
2022-10-04 14:22:05 -07:00
|
|
|
args = ["ethtool", "-K", interface, "rx", "off", "tx", "off"]
|
|
|
|
if namespace:
|
|
|
|
args = ["ip", "netns", "exec", namespace] + args
|
2024-10-31 18:55:27 +00:00
|
|
|
with lock:
|
|
|
|
result = subprocess.run(args, capture_output=True)
|
|
|
|
if result.returncode != 0:
|
|
|
|
raise Exception(
|
|
|
|
f"Error disabling GSO offload on interface {interface} in namespace {namespace}: {result.stderr.decode()}"
|
2022-10-04 14:22:05 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
2024-10-31 18:55:27 +00:00
|
|
|
def delete_all_namespaces(history_file):
|
|
|
|
"""Delete all namespaces whose names have been added to the history file."""
|
|
|
|
with lock:
|
|
|
|
if os.path.exists(history_file):
|
|
|
|
with open(history_file, "r") as ns_file:
|
|
|
|
for line in ns_file:
|
|
|
|
ns_name = line.strip()
|
|
|
|
if ns_name:
|
|
|
|
_delete_namespace(ns_name)
|
|
|
|
os.remove(history_file)
|
|
|
|
|
|
|
|
|
|
|
|
def _delete_namespace(ns):
|
|
|
|
"""Delete one or more namespaces.
|
2022-10-04 14:22:05 -07:00
|
|
|
|
|
|
|
arguments:
|
2024-10-31 18:55:27 +00:00
|
|
|
ns -- a list of namespace names or namespace
|
2022-10-04 14:22:05 -07:00
|
|
|
"""
|
2023-11-08 15:17:14 +01:00
|
|
|
if isinstance(ns, str):
|
|
|
|
namespaces = [ns]
|
|
|
|
else:
|
|
|
|
namespaces = ns
|
2024-10-31 18:55:27 +00:00
|
|
|
|
|
|
|
existing_namespaces = subprocess.run(
|
|
|
|
["ip", "netns", "list"], capture_output=True, text=True
|
|
|
|
).stdout.splitlines()
|
|
|
|
existing_namespaces = {line.split()[0] for line in existing_namespaces}
|
|
|
|
|
|
|
|
for namespace in namespaces:
|
|
|
|
if namespace not in existing_namespaces:
|
|
|
|
continue
|
|
|
|
|
|
|
|
retries = 3
|
|
|
|
for attempt in range(retries):
|
2023-06-20 14:52:08 +00:00
|
|
|
result = subprocess.run(
|
|
|
|
["ip", "netns", "del", namespace], capture_output=True
|
|
|
|
)
|
2024-10-31 18:55:27 +00:00
|
|
|
if result.returncode == 0:
|
|
|
|
break
|
|
|
|
if attempt < retries - 1:
|
|
|
|
time.sleep(1)
|
|
|
|
else:
|
|
|
|
raise Exception(
|
|
|
|
f"Failed to delete namespace {namespace} after {retries} attempts"
|
|
|
|
)
|
2022-10-04 14:22:05 -07:00
|
|
|
|
|
|
|
|
2021-05-11 10:31:18 -07:00
|
|
|
def list_namespace(ns):
|
2024-10-31 18:55:27 +00:00
|
|
|
"""List the IP address of a namespace."""
|
|
|
|
with lock:
|
|
|
|
result = subprocess.run(
|
|
|
|
["ip", "netns", "exec", ns, "ip", "addr"], capture_output=True
|
|
|
|
)
|
|
|
|
if result.returncode != 0:
|
|
|
|
raise Exception(
|
|
|
|
f"Error listing IP addresses in namespace {ns}: {result.stderr.decode()}"
|
|
|
|
)
|
2023-07-28 16:33:30 -07:00
|
|
|
|
|
|
|
|
|
|
|
def libmemif_test_app(memif_sock_path, logger):
|
|
|
|
"""Build & run the libmemif test_app for memif interface testing."""
|
|
|
|
test_dir = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
ws_root = os.path.dirname(test_dir)
|
|
|
|
libmemif_app = os.path.join(
|
|
|
|
ws_root, "extras", "libmemif", "build", "examples", "test_app"
|
|
|
|
)
|
|
|
|
|
|
|
|
def build_libmemif_app():
|
|
|
|
if not os.path.exists(libmemif_app):
|
2024-10-31 18:55:27 +00:00
|
|
|
logger.info(f"Building app:{libmemif_app} for memif interface testing")
|
2023-07-28 16:33:30 -07:00
|
|
|
libmemif_app_dir = os.path.join(ws_root, "extras", "libmemif", "build")
|
2024-10-31 18:55:27 +00:00
|
|
|
os.makedirs(libmemif_app_dir, exist_ok=True)
|
2023-07-28 16:33:30 -07:00
|
|
|
os.chdir(libmemif_app_dir)
|
2024-10-31 18:55:27 +00:00
|
|
|
subprocess.run(["cmake", ".."], check=True)
|
|
|
|
subprocess.run(["make"], check=True)
|
2023-07-28 16:33:30 -07:00
|
|
|
|
|
|
|
def start_libmemif_app():
|
|
|
|
"""Restart once if the initial run fails."""
|
|
|
|
max_tries = 2
|
|
|
|
run = 0
|
|
|
|
while run < max_tries:
|
2024-10-31 18:55:27 +00:00
|
|
|
result = subprocess.run(
|
|
|
|
[libmemif_app, "-b", "9216", "-s", memif_sock_path], capture_output=True
|
|
|
|
)
|
|
|
|
if result.returncode == 0:
|
2023-07-28 16:33:30 -07:00
|
|
|
break
|
2024-10-31 18:55:27 +00:00
|
|
|
logger.error(
|
|
|
|
f"Restarting libmemif app due to error: {result.stderr.decode()}"
|
|
|
|
)
|
|
|
|
run += 1
|
|
|
|
time.sleep(1)
|
2023-07-28 16:33:30 -07:00
|
|
|
|
|
|
|
build_libmemif_app()
|
2024-10-31 18:55:27 +00:00
|
|
|
process = Process(target=start_libmemif_app)
|
2023-07-28 16:33:30 -07:00
|
|
|
process.start()
|
|
|
|
return process
|