summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/accel-pppd/ipoe/test_ipoe_link_ops.py82
-rw-r--r--tests/accel-pppd/ipoe/test_ipoe_stale_cleanup.py97
-rw-r--r--tests/common/ipoe_iface.py84
3 files changed, 263 insertions, 0 deletions
diff --git a/tests/accel-pppd/ipoe/test_ipoe_link_ops.py b/tests/accel-pppd/ipoe/test_ipoe_link_ops.py
new file mode 100644
index 00000000..00d06af4
--- /dev/null
+++ b/tests/accel-pppd/ipoe/test_ipoe_link_ops.py
@@ -0,0 +1,82 @@
+import pytest
+from common import process, ipoe_iface
+
+
+@pytest.fixture()
+def accel_pppd_config(veth_pair_netns):
+ return (
+ """
+ [modules]
+ connlimit
+ radius
+ ipoe
+ ippool
+
+ [ip-pool]
+ gw-ip-address=192.0.2.1
+ 192.0.2.2-255
+
+ [cli]
+ tcp=127.0.0.1:2001
+
+ [core]
+ log-error=/dev/stderr
+
+ [log]
+ log-debug=/dev/stdout
+ log-file=/dev/stdout
+ log-emerg=/dev/stderr
+ level=5
+
+ [radius]
+
+ [ipoe]
+ noauth=1
+ shared=1
+ gw-ip-address=192.0.2.1/24
+ interface=re:."""
+ + veth_pair_netns["veth_a"][1:]
+ )
+
+
+# sessions carry private state set up over generic netlink, a device made by
+# rtnetlink would have none of it
+@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope="session")
+@pytest.mark.ipoe_driver
+def test_ipoe_interface_cannot_be_created_by_iproute():
+ (exit_code, out, err) = process.run(
+ ["ip", "link", "add", "ipoetest0", "type", "ipoe"]
+ )
+ print("ip link add: exit=%d out=%s err=%s" % (exit_code, out, err))
+
+ # remove it again in case it got created anyway, so that the rest of the
+ # suite is not affected
+ process.run(["ip", "link", "del", "ipoetest0"])
+
+ assert exit_code != 0
+
+
+# stale interfaces have to be removable without restarting accel-pppd, which
+# would drop every remaining session
+@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope="session")
+@pytest.mark.ipoe_driver
+def test_ipoe_interface_can_be_deleted_by_iproute(
+ accel_pppd_instance, dhclient_instance, accel_cmd, veth_pair_netns
+):
+ assert accel_pppd_instance, "accel-pppd did not start"
+ assert dhclient_instance["is_started"]
+ assert ipoe_iface.wait_for_session(accel_cmd, veth_pair_netns["veth_a"])
+
+ before = ipoe_iface.list_ipoe()
+ print("ipoe interfaces: " + str(before))
+ assert len(before) > 0
+
+ (ifindex, name) = before[0]
+
+ (exit_code, out, err) = process.run(["ip", "link", "del", name])
+ print("ip link del %s: exit=%d out=%s err=%s" % (name, exit_code, out, err))
+ assert exit_code == 0
+
+ after = ipoe_iface.list_ipoe()
+ print("ipoe interfaces after delete: " + str(after))
+ assert (ifindex, name) not in after
diff --git a/tests/accel-pppd/ipoe/test_ipoe_stale_cleanup.py b/tests/accel-pppd/ipoe/test_ipoe_stale_cleanup.py
new file mode 100644
index 00000000..b7cfd533
--- /dev/null
+++ b/tests/accel-pppd/ipoe/test_ipoe_stale_cleanup.py
@@ -0,0 +1,97 @@
+import pytest
+from common import accel_pppd_process, ipoe_iface
+
+
+@pytest.fixture()
+def accel_pppd_config(veth_pair_netns):
+ return (
+ """
+ [modules]
+ connlimit
+ radius
+ ipoe
+ ippool
+
+ [ip-pool]
+ gw-ip-address=192.0.2.1
+ 192.0.2.2-255
+
+ [cli]
+ tcp=127.0.0.1:2001
+
+ [core]
+ log-error=/dev/stderr
+
+ [log]
+ log-debug=/dev/stdout
+ log-file=/dev/stdout
+ log-emerg=/dev/stderr
+ level=5
+
+ [radius]
+
+ [ipoe]
+ noauth=1
+ shared=1
+ gw-ip-address=192.0.2.1/24
+ interface=re:."""
+ + veth_pair_netns["veth_a"][1:]
+ )
+
+
+# accel-pppd killed with SIGKILL has no chance to remove the session
+# interfaces it created, so they are left in the kernel. The next instance is
+# supposed to drop them while starting up.
+@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope="session")
+@pytest.mark.ipoe_driver
+def test_ipoe_stale_interfaces_removed_after_sigkill(
+ accel_pppd_instance,
+ dhclient_instance,
+ accel_cmd,
+ accel_pppd,
+ accel_pppd_config_file,
+ veth_pair_netns,
+ pytestconfig,
+):
+ assert accel_pppd_instance, "accel-pppd did not start"
+ assert dhclient_instance["is_started"]
+ assert ipoe_iface.wait_for_session(accel_cmd, veth_pair_netns["veth_a"])
+
+ # the session must have created an interface, otherwise there is nothing
+ # for this test to check
+ before = ipoe_iface.list_ipoe()
+ print("ipoe interfaces before the crash: " + str(before))
+ assert len(before) > 0
+
+ assert ipoe_iface.kill_accel_pppd() > 0
+
+ # nothing removes them while no accel-pppd is running (not asserted, the
+ # kernel module is free to start doing it on its own one day)
+ print("ipoe interfaces after the crash: " + str(ipoe_iface.list_ipoe()))
+
+ # start again, it replies to 'show version' only once the startup flush is
+ # done, so there is no need to wait for anything else
+ (is_started, thread, control) = accel_pppd_process.start(
+ accel_pppd,
+ ["-c" + accel_pppd_config_file],
+ accel_cmd,
+ pytestconfig.getoption("accel_pppd_max_wait_time"),
+ )
+
+ try:
+ assert is_started
+
+ after = ipoe_iface.list_ipoe()
+ print("ipoe interfaces after the restart: " + str(after))
+
+ # compared by ifindex: dhclient may get a new session in the meantime,
+ # and the fresh interface would reuse the ipoe0 name
+ stale = set(before) & set(after)
+ assert not stale, "interfaces left over from the killed instance: " + str(stale)
+ finally:
+ accel_pppd_process.end(
+ thread,
+ control,
+ accel_cmd,
+ pytestconfig.getoption("accel_pppd_max_finish_time"),
+ )
diff --git a/tests/common/ipoe_iface.py b/tests/common/ipoe_iface.py
new file mode 100644
index 00000000..62b26355
--- /dev/null
+++ b/tests/common/ipoe_iface.py
@@ -0,0 +1,84 @@
+from common import process
+import os
+import signal
+import time
+
+
+# (ifindex, name) of every ipoe session interface currently in the kernel.
+# Matched by name rather than by 'ip link show type ipoe' so that the helper
+# keeps working with a module that does not register rtnl_link_ops.
+def list_ipoe():
+ (exit_code, out, err) = process.run(["ip", "-o", "link", "show"])
+ assert exit_code == 0, "ip link show failed: " + err
+
+ ifaces = []
+ for line in out.splitlines():
+ fields = line.split(":")
+ if len(fields) < 2:
+ continue
+ try:
+ ifindex = int(fields[0].strip())
+ except ValueError:
+ continue
+ name = fields[1].strip().split("@")[0]
+ if name.startswith("ipoe"):
+ ifaces.append((ifindex, name))
+
+ return ifaces
+
+
+# pids of the running accel-pppd processes
+def accel_pppd_pids():
+ pids = []
+ for entry in os.listdir("/proc"):
+ if not entry.isdigit():
+ continue
+ try:
+ with open("/proc/" + entry + "/comm") as comm:
+ if comm.read().strip() == "accel-pppd":
+ pids.append(int(entry))
+ except OSError: # process is gone, or not ours to look at
+ pass
+
+ return pids
+
+
+# SIGKILL every accel-pppd and wait until they are really gone.
+# Returns the number of processes that were killed.
+def kill_accel_pppd(max_wait_time=10.0):
+ pids = accel_pppd_pids()
+ for pid in pids:
+ print("kill_accel_pppd: SIGKILL to pid " + str(pid))
+ try:
+ os.kill(pid, signal.SIGKILL)
+ except OSError:
+ pass
+
+ sleep_time = 0.0
+ while sleep_time < max_wait_time:
+ if not accel_pppd_pids():
+ print("kill_accel_pppd: gone in (sec): " + str(sleep_time))
+ break
+ time.sleep(0.1)
+ sleep_time += 0.1
+
+ return len(pids)
+
+
+# wait until accel-pppd reports an active ipoe session on the given interface
+def wait_for_session(accel_cmd, called_sid, max_wait_time=10.0):
+ sleep_time = 0.0
+ out = ""
+ while sleep_time < max_wait_time:
+ (exit_code, out, err) = process.run(
+ [accel_cmd, "show sessions called-sid,ip,state"]
+ )
+ assert exit_code == 0, "accel-cmd failed: " + err
+ if called_sid in out and "192.0.2." in out and "active" in out:
+ print("wait_for_session: session found in (sec): " + str(sleep_time))
+ return True
+ time.sleep(0.1)
+ sleep_time += 0.1
+
+ print("wait_for_session: last accel-cmd out: " + out)
+ return False