diff options
Diffstat (limited to 'tests')
25 files changed, 1142 insertions, 0 deletions
diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..0e7ab4f --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,17 @@ +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Byte-compiled +__pycache__/ + +# pytest +.pytest_cache + +# gcovr reports +report/ diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..d062ed4 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,103 @@ +# Requirements + +These tests are done for Ubuntu and Debian distros. Please use latest stable Debian or Ubuntu to run the tests. + +## Preparations + +Install pytest + +Using apt: `sudo apt install python3-pytest python3-pytest-dependency` or using pip: `sudo pip3 install pytest pytest-dependency`. + +pytest-dependency version must be >= 0.5 (with 'scope' support) + +--- +Note: tests will be run under sudo. If you prefer install python modules using pip, then do it under sudo as described above. + +--- + +Install additional tools required for tests: +```bash +sudo apt install iproute2 ppp pppoe isc-dhcp-client +``` + +Then build accel-ppp in 'build' directory (as usual) + +Install accel-pppd (make install or use distro package). Do not run accel-pppd using systemd or other supervisors +```bash +mkdir build && cd build +cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr -DKDIR=/usr/src/linux-headers-`uname -r` -DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE -DCPACK_TYPE=Ubuntu20 .. +make +sudo make install # or +# cpack -G DEB && dpkg -i accel-ppp.deb +``` + +If you prefer make install, then it is required to insert kernel modules: +```bash +# form root dir +sudo insmod build/drivers/vlan_mon/driver/vlan_mon.ko +sudo insmod build/drivers/ipoe/driver/ipoe.ko +``` + + +## Run tests (without coverage) + +```bash +# from this dir (tests) +sudo python3 -m pytest -Wall -v +``` + +To skip tests related to ipoe and vlan_mon kernel modules: +```bash +# from this dir (tests) +sudo python3 -m pytest -Wall -v -m "not ipoe_driver and not vlan_mon_driver" +``` + +## Preparations (for coverage report) + +Perform preparation steps for running tests without coverage + +Install gcovr + +Using apt: +```bash +sudo apt install gcovr +``` + +Using pip +```bash +sudo pip3 install gcovr +``` + +```bash +# from root dir +rm -rf build && mkdir build && cd build +cmake -DBUILD_IPOE_DRIVER=TRUE -DBUILD_VLAN_MON_DRIVER=TRUE -DCMAKE_INSTALL_PREFIX=/usr -DKDIR=/usr/src/linux-headers-`uname -r` -DLUA=TRUE -DSHAPER=TRUE -DRADIUS=TRUE -DCPACK_TYPE=Ubuntu20 -DCMAKE_C_FLAGS="--coverage -O0" .. +make +sudo make install # or +# cpack -G DEB && dpkg -i accel-ppp.deb +``` + +Then insert kernel modules (ipoe.ko and vlan-mon.ko) + +## Run tests and generate coverage report + +```bash +# from root dir (parent for this dir) +sudo python3 -m pytest -Wall tests -v # execute tests to collect coverage data +mkdir tests/report +gcovr --config=tests/gcovr.conf # default report +gcovr --config=tests/gcovr.conf --csv # csv report +gcovr --config=tests/gcovr.conf --html --html-details --output=tests/report/accel-ppp.html # html reports (most useful) +``` + +(If `gcovr` command does not exist, use `python3 -m gcovr` instead) + +## Remove coverage data + +If you want to re-run tests 'from scratch', you may want to remove coverage data. To do this: + +```bash +# from root dir (parent for this dir) +sudo gcovr -d # build report and delete +sudo gcovr -d # check that data is deleted (any coverage = 0%) +```
\ No newline at end of file diff --git a/tests/accel-cmd/test_cmd_basic.py b/tests/accel-cmd/test_cmd_basic.py new file mode 100644 index 0000000..c6cdc7a --- /dev/null +++ b/tests/accel-cmd/test_cmd_basic.py @@ -0,0 +1,26 @@ +import pytest +from common import process + + +def test_accel_cmd_version(accel_cmd): + (exit, out, err) = process.run([accel_cmd, "--version"]) + + # test that accel-cmd --version exits with code 0, prints + # nothing to stdout and prints to stdout + assert exit == 0 and err == "" and "accel-cmd " in out and len(out.split(" ")) == 2 + + +def test_accel_cmd_non_existent_host(accel_cmd): + (exit, out, err) = process.run([accel_cmd, "-Hnon-existent-host", "--verbose"]) + + # test that accel-cmd (tried to connecto to non-existent host) exits with code != 0, + # prints nothing to stdout and prints an error to stderr + assert exit != 0 and out == "" and err != "" + + +def test_accel_cmd_mcast_host(accel_cmd): + (exit, out, err) = process.run([accel_cmd, "-H225.0.0.1"]) + + # test that accel-cmd (tried to connecto to mcast host) exits with code != 0, + # prints nothing to stdout and prints an error to stderr + assert exit != 0 and out == "" and err != "" diff --git a/tests/accel-cmd/test_real_commands.py b/tests/accel-cmd/test_real_commands.py new file mode 100644 index 0000000..1758b4e --- /dev/null +++ b/tests/accel-cmd/test_real_commands.py @@ -0,0 +1,53 @@ +import pytest +from common import process + + +@pytest.fixture() +def accel_pppd_config(): + return """ + [modules] + + [log] + log-debug=/dev/stdout + level=5 + + [cli] + tcp=127.0.0.1:2001 + """ + + +# test accel-cmd command with started accel-pppd +def test_accel_cmd_commands(accel_pppd_instance, accel_cmd): + + # test that accel-pppd started successfully + assert accel_pppd_instance + + (exit_sh_stat, out_sh_stat, err_sh_stat) = process.run([accel_cmd, "show stat"]) + + # test that 'show stat' has no errors and contains 'uptime' + assert ( + exit_sh_stat == 0 + and len(out_sh_stat) > 0 + and err_sh_stat == "" + and "uptime" in out_sh_stat + ) + + (exit_sh_ses, out_sh_ses, err_sh_ses) = process.run( + [accel_cmd, "show sessions sid,uptime"] + ) + # test that 'show sessions' has no errors and contains 'sid' + assert ( + exit_sh_ses == 0 + and len(out_sh_ses) > 0 + and err_sh_ses == "" + and "sid" in out_sh_ses + ) + + (exit_help, out_help, err_help) = process.run([accel_cmd, "help"]) + # test that 'help' has no errors and contains 'show stat' + assert ( + exit_help == 0 + and len(out_help) > 0 + and err_help == "" + and "show stat" in out_help + ) diff --git a/tests/accel-pppd/ipoe/conftest.py b/tests/accel-pppd/ipoe/conftest.py new file mode 100644 index 0000000..3db8dd6 --- /dev/null +++ b/tests/accel-pppd/ipoe/conftest.py @@ -0,0 +1,46 @@ +import pytest +from common import dhclient_process +import tempfile, os + +# dhclient executable file name +@pytest.fixture() +def dhclient(pytestconfig): + return pytestconfig.getoption("dhclient") + + +# pppd configuration as command line args (might be redefined by specific test) +# "-d" (do not daemonize) must be a part of the args +@pytest.fixture() +def dhclient_args(): + # test setup: + #lease_file = tempfile.NamedTemporaryFile(delete=True) + #lease_file_name = lease_file.name + #lease_file.close() # just create, close and delete + + # test execution: + yield ["-d", "-4", "--no-pid", "-lf", "/dev/null"] + + # test teardown: + #os.unlink(lease_file_name) + + +# setup and teardown for tests that required running dhclient (after accel-pppd) +@pytest.fixture() +def dhclient_instance(accel_pppd_instance, veth_pair_netns, dhclient, dhclient_args): + # test setup: + print("dhclient_instance: accel_pppd_instance = " + str(accel_pppd_instance)) + is_started, dhclient_thread, dhclient_control = dhclient_process.start( + veth_pair_netns["netns"], + dhclient, + dhclient_args, + ) + + # test execution: + yield { + "is_started": is_started, + "dhclient_thread": dhclient_thread, + "dhclient_control": dhclient_control, + } + + # test teardown: + dhclient_process.end(dhclient_thread, dhclient_control) diff --git a/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_wo_auth.py b/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_wo_auth.py new file mode 100644 index 0000000..104e4e9 --- /dev/null +++ b/tests/accel-pppd/ipoe/dhcpv4/test_ipoe_shared_session_wo_auth.py @@ -0,0 +1,70 @@ +import pytest +from common import process +import time + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns): + print("accel_pppd_config veth_pair_netns: " + str(veth_pair_netns)) + return ( + """ + [modules] + pppoe + ipoe + ippool + + [ip-pool] + gw-ip-address=192.0.2.1 + 192.0.2.2-255 + + [cli] + tcp=127.0.0.1:2001 + + [log] + log-debug=/dev/stdout + level=5 + + [ipoe] + noauth=1 + shared=1 + gw-ip-address=192.0.2.1/24 + interface=""" + + veth_pair_netns["veth_a"] + ) + + +# test dhcpv4 shared session without auth check +@pytest.mark.dependency(depends=["ipoe_driver_loaded"], scope = 'session') +@pytest.mark.ipoe_driver +def test_ipoe_shared_session_wo_auth(dhclient_instance, accel_cmd, veth_pair_netns): + + # test that dhclient (with accel-pppd) started successfully + assert dhclient_instance["is_started"] + + # wait until session is started + max_wait_time = 10.0 + sleep_time = 0.0 + is_started = False # is session started + while sleep_time < max_wait_time: + (exit, out, err) = process.run( + [ + accel_cmd, + "show sessions called-sid,ip,state", + ] + ) + assert exit == 0 # accel-cmd fails + # print(out) + if veth_pair_netns["veth_a"] in out and "192.0.2." in out and "active" in out: + # session is found + print( + "test_pppoe_session_wo_auth: session found in (sec): " + str(sleep_time) + ) + is_started = True + break + time.sleep(0.1) + sleep_time += 0.1 + + print("test_ipoe_shared_session_wo_auth: last accel-cmd out: " + out) + + # test that session is started + assert is_started == True diff --git a/tests/accel-pppd/ipoe/test_ipoe_driver.py b/tests/accel-pppd/ipoe/test_ipoe_driver.py new file mode 100644 index 0000000..d21e9ba --- /dev/null +++ b/tests/accel-pppd/ipoe/test_ipoe_driver.py @@ -0,0 +1,8 @@ +import pytest +import os + +# test that ipoe kernel module is loaded +@pytest.mark.dependency(name = 'ipoe_driver_loaded', scope = 'session') +@pytest.mark.ipoe_driver +def test_ipoe_kernel_module_loaded(): + assert os.path.isdir("/sys/module/ipoe")
\ No newline at end of file diff --git a/tests/accel-pppd/pppoe/conftest.py b/tests/accel-pppd/pppoe/conftest.py new file mode 100644 index 0000000..8ebaaed --- /dev/null +++ b/tests/accel-pppd/pppoe/conftest.py @@ -0,0 +1,42 @@ +import pytest +from common import pppd_process + +# pppd executable file name +@pytest.fixture() +def pppd(pytestconfig): + return pytestconfig.getoption("pppd") + + +# pppd configuration as string (should be redefined by specific test) +# all configs should contain "nodetach" option +@pytest.fixture() +def pppd_config(): + return "" + + +# pppd configuration as command line args +@pytest.fixture() +def pppd_args(pppd_config): + return pppd_config.split() + + +# setup and teardown for tests that required running pppd (after accel-pppd) +@pytest.fixture() +def pppd_instance(accel_pppd_instance, veth_pair_netns, pppd, pppd_args): + # test setup: + print("pppd_instance: accel_pppd_instance = " + str(accel_pppd_instance)) + is_started, pppd_thread, pppd_control = pppd_process.start( + veth_pair_netns["netns"], + pppd, + pppd_args, + ) + + # test execution: + yield { + "is_started": is_started, + "pppd_thread": pppd_thread, + "pppd_control": pppd_control, + } + + # test teardown: + pppd_process.end(pppd_thread, pppd_control) diff --git a/tests/accel-pppd/pppoe/test_pppoe_disc.py b/tests/accel-pppd/pppoe/test_pppoe_disc.py new file mode 100644 index 0000000..eb069c4 --- /dev/null +++ b/tests/accel-pppd/pppoe/test_pppoe_disc.py @@ -0,0 +1,42 @@ +import pytest +from common import netns + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns): + print(veth_pair_netns) + return ( + """ + [modules] + pppoe + + [log] + log-debug=/dev/stdout + level=5 + + [cli] + tcp=127.0.0.1:2001 + + [pppoe] + ac-name=test-accel + interface=""" + + veth_pair_netns["veth_a"] + ) + + +# test pppoe discovery +def test_pppoe_discovery(accel_pppd_instance, veth_pair_netns): + + # test that accel-pppd started successfully + assert accel_pppd_instance + + (exit_sh_stat, out_sh_stat, err_sh_stat) = netns.exec( + veth_pair_netns["netns"], ["pppoe-discovery", "-I", veth_pair_netns["veth_b"]] + ) + + # test that ac-name=test-accel is in pppoe-discovery reply (PADO) + assert ( + exit_sh_stat == 0 + and err_sh_stat == "" + and "test-accel" in out_sh_stat + ) diff --git a/tests/accel-pppd/pppoe/test_pppoe_pado_delay.py b/tests/accel-pppd/pppoe/test_pppoe_pado_delay.py new file mode 100644 index 0000000..96c73bf --- /dev/null +++ b/tests/accel-pppd/pppoe/test_pppoe_pado_delay.py @@ -0,0 +1,83 @@ +import pytest +from common import netns, process +import time + +# This test module requires pppoe-discovery with -a and -t options +# Ubuntu 20.04 has not this option, Ubuntu 22.04 is ok + +# Check that pppoe-discover supports -a and -t (to disable some tests required these features) +def support_pppoe_discovery_a_t(): + try: + (_, out, err) = process.run(["pppoe-discovery", "-h"]) + except: # can't run pppoe-discovery + return False + + if "-t " in out + err and "-a " in out + err: # found -t and -a options + return True + else: + return False + + +# skip tests in this module if pppoe-discovery doesn't support '-a' and '-t' options +pytestmark = pytest.mark.skipif( + not support_pppoe_discovery_a_t(), reason="bad pppoe-discovery" +) + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns): + print(veth_pair_netns) + return ( + """ + [modules] + pppoe + + [log] + log-debug=/dev/stdout + level=5 + + [cli] + tcp=127.0.0.1:2001 + + [pppoe] + ac-name=test-accel + pado-delay=1500 + interface=""" + + veth_pair_netns["veth_a"] + ) + + +# test pado delay. accel-pppd is configured for 1.5s delay +# first step: test that pppoe-discovery fails if wait timeout=1<1.5 +# second step: test that pppoe-discovery gets pado if wait timeout=2>1.5 +def test_pppoe_pado_delay(accel_pppd_instance, veth_pair_netns): + + # test that accel-pppd started successfully + assert accel_pppd_instance + + # send two times with wait timeout = 1 + (exit_sh_stat, out_sh_stat, err_sh_stat) = netns.exec( + veth_pair_netns["netns"], + ["pppoe-discovery", "-a1", "-t1", "-I", veth_pair_netns["veth_b"]], + ) + time.sleep(1) # sleep for one second (because accel-pppd replies in this timeslot) + (exit_sh_stat2, out_sh_stat2, err_sh_stat2) = netns.exec( + veth_pair_netns["netns"], + ["pppoe-discovery", "-a1", "-t1", "-I", veth_pair_netns["veth_b"]], + ) + time.sleep(1) # sleep for one second (because accel-pppd replies in this timeslot) + + # print(out_sh_stat + err_sh_stat) + # print(out_sh_stat2 + err_sh_stat2) + + # test that pppoe-discovery (wait timeout 1s) fails (as expected) (two times) + assert exit_sh_stat != 0 and "test-accel" not in out_sh_stat + assert exit_sh_stat2 != 0 and "test-accel" not in out_sh_stat2 + + (exit_sh_stat3, out_sh_stat3, err_sh_stat3) = netns.exec( + veth_pair_netns["netns"], + ["pppoe-discovery", "-a1", "-t2", "-I", veth_pair_netns["veth_b"]], + ) + + # test that pppoe-discovery (wait timeout 2s) gets pado + assert exit_sh_stat3 == 0 and "test-accel" in out_sh_stat3 diff --git a/tests/accel-pppd/pppoe/test_pppoe_session_wo_auth.py b/tests/accel-pppd/pppoe/test_pppoe_session_wo_auth.py new file mode 100644 index 0000000..0c8aa2c --- /dev/null +++ b/tests/accel-pppd/pppoe/test_pppoe_session_wo_auth.py @@ -0,0 +1,90 @@ +import pytest +from common import process +import time + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns): + print("accel_pppd_config veth_pair_netns: " + str(veth_pair_netns)) + return ( + """ + [modules] + pppoe + auth_pap + ippool + + [log] + log-debug=/dev/stdout + level=5 + + [auth] + any-login=1 + + [ip-pool] + gw-ip-address=192.0.2.1 + 192.0.2.2-255 + + [cli] + tcp=127.0.0.1:2001 + + [pppoe] + interface=""" + + veth_pair_netns["veth_a"] + ) + + +@pytest.fixture() +def pppd_config(veth_pair_netns): + print("pppd_config veth_pair_netns: " + str(veth_pair_netns)) + return ( + """ + nodetach + noipdefault + defaultroute + connect /bin/true + noauth + persist + mtu 1492 + noaccomp + default-asyncmap + plugin rp-pppoe.so + user loginAB + password pass123 + nic-""" + + veth_pair_netns["veth_b"] + ) + + +# test pppoe session without auth check +def test_pppoe_session_wo_auth(pppd_instance, accel_cmd): + + # test that pppd (with accel-pppd) started successfully + assert pppd_instance["is_started"] + + # wait until session is started + max_wait_time = 10.0 + sleep_time = 0.0 + is_started = False # is session started + while sleep_time < max_wait_time: + (exit, out, err) = process.run( + [ + accel_cmd, + "show sessions match username loginAB username,ip,state", + ] + ) + assert exit == 0 # accel-cmd fails + # print(out) + if "loginAB" in out and "192.0.2." in out and "active" in out: + # session is found + print( + "test_pppoe_session_wo_auth: session found in (sec): " + str(sleep_time) + ) + is_started = True + break + time.sleep(0.1) + sleep_time += 0.1 + + print("test_pppoe_session_wo_auth: last accel-cmd out: " + out) + + # test that session is started + assert is_started == True diff --git a/tests/accel-pppd/pppoe/test_pppoe_vlan_mon.py b/tests/accel-pppd/pppoe/test_pppoe_vlan_mon.py new file mode 100644 index 0000000..670abc3 --- /dev/null +++ b/tests/accel-pppd/pppoe/test_pppoe_vlan_mon.py @@ -0,0 +1,49 @@ +import pytest +from common import netns + + +# create vlan 15 only in netns (invisble to accel-pppd) +@pytest.fixture() +def veth_pair_vlans_config(): + return {"vlans_a": [], "vlans_b": [15]} + + +@pytest.fixture() +def accel_pppd_config(veth_pair_netns): + print(veth_pair_netns) + return """ + [modules] + pppoe + + [log] + log-debug=/dev/stdout + level=5 + + [cli] + tcp=127.0.0.1:2001 + + [pppoe] + ac-name=test-accel + vlan-mon=%s,10-20 + interface=re:%s.\\d+ + """ % ( + veth_pair_netns["veth_a"], + veth_pair_netns["veth_a"], + ) + + +# test pppoe discovery in vlan created by vlan_mon +@pytest.mark.dependency(depends=["vlan_mon_driver_loaded"], scope="session") +@pytest.mark.vlan_mon_driver +def test_pppoe_vlan_mon(accel_pppd_instance, veth_pair_netns): + + # test that accel-pppd started successfully + assert accel_pppd_instance + + (exit_sh_stat, out_sh_stat, err_sh_stat) = netns.exec( + veth_pair_netns["netns"], + ["pppoe-discovery", "-I", veth_pair_netns["veth_b"] + ".15"], + ) + + # test that ac-name=test-accel is in pppoe-discovery reply (PADO) + assert exit_sh_stat == 0 and err_sh_stat == "" and "test-accel" in out_sh_stat diff --git a/tests/accel-pppd/test_basic.py b/tests/accel-pppd/test_basic.py new file mode 100644 index 0000000..2b2c6f7 --- /dev/null +++ b/tests/accel-pppd/test_basic.py @@ -0,0 +1,83 @@ +import pytest +from common import process + + +def test_accel_pppd_version(accel_pppd): + (exit, out, err) = process.run([accel_pppd, "--version"]) + + # test that accel-pppd --version exits with code 0, prints + # nothing to stdout and prints to stdout + assert exit == 0 and err == "" and "accel-ppp " in out and len(out.split(" ")) == 2 + + +@pytest.fixture() +def accel_pppd_config(): + return """ + [modules] + log_file + log_syslog + log_tcp + #log_pgsql + + pptp + l2tp + sstp + pppoe + ipoe + + auth_mschap_v2 + auth_mschap_v1 + auth_chap_md5 + auth_pap + + radius + chap-secrets + + ippool + + pppd_compat + shaper + #net-snmp + logwtmp + connlimit + + ipv6_nd + ipv6_dhcp + ipv6pool + + [core] + log-error=/dev/stderr + + [log] + log-debug=/dev/stdout + log-file=/dev/stdout + log-emerg=/dev/stderr + level=5 + + [cli] + tcp=127.0.0.1:2001 + + [pppoe] + + [client-ip-range] + 10.0.0.0/8 + + [radius] + """ + + +# load all modules and check that accel-pppd replies to 'show stat' command +def test_load_all_modules(accel_pppd_instance, accel_cmd): + + # test that accel-pppd started successfully + assert accel_pppd_instance + + (exit_sh_stat, out_sh_stat, err_sh_stat) = process.run([accel_cmd, "show stat"]) + + # test that 'show stat' has no errors and contains 'uptime' + assert ( + exit_sh_stat == 0 + and len(out_sh_stat) > 1 + and err_sh_stat == "" + and "uptime" in out_sh_stat + ) diff --git a/tests/accel-pppd/test_vlan_mon_driver.py b/tests/accel-pppd/test_vlan_mon_driver.py new file mode 100644 index 0000000..3e937f8 --- /dev/null +++ b/tests/accel-pppd/test_vlan_mon_driver.py @@ -0,0 +1,7 @@ +import pytest +import os + +# test that vlan_mon kernel module is loaded +@pytest.mark.dependency(name = 'vlan_mon_driver_loaded', scope = 'session') +def test_vlan_mon_kernel_module_loaded(): + assert os.path.isdir("/sys/module/vlan_mon")
\ No newline at end of file diff --git a/tests/common/__init__.py b/tests/common/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/common/__init__.py diff --git a/tests/common/accel_pppd_process.py b/tests/common/accel_pppd_process.py new file mode 100644 index 0000000..c2ee451 --- /dev/null +++ b/tests/common/accel_pppd_process.py @@ -0,0 +1,86 @@ +from subprocess import Popen, PIPE +from common import process +from threading import Thread +import time + + +def accel_pppd_thread_func(accel_pppd_control): + process = accel_pppd_control["process"] + print("accel_pppd_thread_func: before communicate") + (out, err) = process.communicate() + print( + "accel_pppd_thread_func: after communicate out=" + str(out) + " err=" + str(err) + ) + process.wait() + print("accel_pppd_thread_func: after wait") + + +def start(accel_pppd, args, accel_cmd, max_wait_time): + print("accel_pppd_start: begin") + accel_pppd_process = Popen([accel_pppd] + args, stdout=PIPE, stderr=PIPE) + accel_pppd_control = {"process": accel_pppd_process} + accel_pppd_thread = Thread( + target=accel_pppd_thread_func, + args=[accel_pppd_control], + ) + accel_pppd_thread.start() + + # wait until accel-pppd replies to 'show version' + # accel-pppd needs some time to be accessible + sleep_time = 0.0 + is_started = False + while sleep_time < max_wait_time: + if accel_pppd_process.poll() is not None: # process is terminated + print( + "accel_pppd_start: terminated during 'show version' polling in (sec): " + + str(sleep_time) + ) + is_started = False + break + (exit, out, err) = process.run([accel_cmd, "show version"]) + if exit != 0: # does not reply + time.sleep(0.1) + sleep_time += 0.1 + else: # replied + print("accel_pppd_start: 'show version' replied") + is_started = True + break + + return (is_started, accel_pppd_thread, accel_pppd_control) + + +def end(accel_pppd_thread, accel_pppd_control, accel_cmd, max_wait_time): + print("accel_pppd_end: begin") + if accel_pppd_control["process"].poll() is not None: # terminated + print("accel_pppd_end: already terminated. nothing to do") + accel_pppd_thread.join() + return + + process.run( + [accel_cmd, "shutdown hard"] + ) # send shutdown hard command (in coverage mode it helps saving coverage data) + print("accel_pppd_end: after shutdown hard") + + # wait until accel-pppd is finished + sleep_time = 0.0 + is_finished = False + while sleep_time < max_wait_time: + if accel_pppd_control["process"].poll() is None: # not terminated yet + time.sleep(0.01) + sleep_time += 0.01 + # print("accel_pppd_end: sleep 0.01") + else: + is_finished = True + print( + "accel_pppd_end: finished via shutdown hard in (sec): " + + str(sleep_time) + ) + break + + # accel-pppd is still alive. kill it + if not is_finished: + print("accel_pppd_end: kill process: " + str(accel_pppd_control["process"])) + accel_pppd_control["process"].kill() # kill -9 if 'shutdown hard' didn't help + + accel_pppd_thread.join() # wait until thread is finished + print("accel_pppd_end: end") diff --git a/tests/common/config.py b/tests/common/config.py new file mode 100644 index 0000000..94ddcd8 --- /dev/null +++ b/tests/common/config.py @@ -0,0 +1,15 @@ +import tempfile +import os + + +def make_tmp(content): + f = tempfile.NamedTemporaryFile(delete=False) + print("make_tmp filename: " + f.name) + f.write(bytes(content, "utf-8")) + f.close() + return f.name + + +def delete_tmp(filename): + print("delete_tmp filename: " + filename) + os.unlink(filename) diff --git a/tests/common/dhclient_process.py b/tests/common/dhclient_process.py new file mode 100644 index 0000000..aaea486 --- /dev/null +++ b/tests/common/dhclient_process.py @@ -0,0 +1,45 @@ +from subprocess import Popen, PIPE +from threading import Thread + + +def dhclient_thread_func(dhclient_control): + process = dhclient_control["process"] + print("dhclient_thread_func: before communicate") + (out, err) = process.communicate() + print( + "dhclient_thread_func: after communicate out=" + str(out) + " err=" + str(err) + ) + process.wait() + print("dhclient_thread_func: after wait") + + +def start(netns, dhclient, args): + print("dhclient_start: begin") + print("dhclient_start: args=" + str(args)) + dhclient_process = Popen( + ["ip", "netns", "exec", netns] + [dhclient] + args, stdout=PIPE, stderr=PIPE + ) + print("dhclient_start: dhclient_process=" + str(dhclient_process)) + dhclient_control = {"process": dhclient_process} + dhclient_thread = Thread( + target=dhclient_thread_func, + args=[dhclient_control], + ) + dhclient_thread.start() + + is_started = True if dhclient_process.poll() is None else False + + return (is_started, dhclient_thread, dhclient_control) + + +def end(dhclient_thread, dhclient_control): + print("dhclient_end: begin") + if dhclient_control["process"].poll() is not None: # already terminated + print("dhclient_end: already terminated. nothing to do") + dhclient_thread.join() + return + + print("dhclient_end: kill process: " + str(dhclient_control["process"])) + dhclient_control["process"].kill() # kill -9 + dhclient_thread.join() # wait until thread is finished + print("dhclient_end: end") diff --git a/tests/common/netns.py b/tests/common/netns.py new file mode 100644 index 0000000..9b0d729 --- /dev/null +++ b/tests/common/netns.py @@ -0,0 +1,29 @@ +from common import process + +# creates netns and returns netns name. if ok return 0 +def create(netns_name): + netns, out, err = process.run(["ip", "netns", "add", netns_name]) + print("netns.create: exit=%d out=%s err=%s" % (netns, out, err)) + + return netns + + +# deletes netns. if ok return 0 +def delete(netns_name): + netns, out, err = process.run(["ip", "netns", "delete", netns_name]) + print("netns.delete: exit=%d out=%s err=%s" % (netns, out, err)) + + return netns + + +# execute command in netns using process.run +# if netns_name is None, then execute in global rt +def exec(netns_name, command): + if netns_name is None: + exit, out, err = process.run(command) + else: + exit, out, err = process.run(["ip", "netns", "exec", netns_name] + command) + + print("netns.exec: netns=%s command=%s :: exit=%d out=%s err=%s" % (netns_name, str(command), exit, out, err)) + + return (exit, out, err) diff --git a/tests/common/pppd_process.py b/tests/common/pppd_process.py new file mode 100644 index 0000000..4584440 --- /dev/null +++ b/tests/common/pppd_process.py @@ -0,0 +1,43 @@ +from subprocess import Popen, PIPE +from threading import Thread + + +def pppd_thread_func(pppd_control): + process = pppd_control["process"] + print("pppd_thread_func: before communicate") + (out, err) = process.communicate() + print("pppd_thread_func: after communicate out=" + str(out) + " err=" + str(err)) + process.wait() + print("pppd_thread_func: after wait") + + +def start(netns, pppd, args): + print("pppd_start: begin") + print("pppd_start: args=" + str(args)) + pppd_process = Popen( + ["ip", "netns", "exec", netns] + [pppd] + args, stdout=PIPE, stderr=PIPE + ) + print("pppd_start: pppd_process=" + str(pppd_process)) + pppd_control = {"process": pppd_process} + pppd_thread = Thread( + target=pppd_thread_func, + args=[pppd_control], + ) + pppd_thread.start() + + is_started = True if pppd_process.poll() is None else False + + return (is_started, pppd_thread, pppd_control) + + +def end(pppd_thread, pppd_control): + print("pppd_end: begin") + if pppd_control["process"].poll() is not None: # already terminated + print("pppd_end: already terminated. nothing to do") + pppd_thread.join() + return + + print("pppd_end: kill process: " + str(pppd_control["process"])) + pppd_control["process"].kill() # kill -9 + pppd_thread.join() # wait until thread is finished + print("pppd_end: end") diff --git a/tests/common/process.py b/tests/common/process.py new file mode 100644 index 0000000..e0c6136 --- /dev/null +++ b/tests/common/process.py @@ -0,0 +1,7 @@ +from subprocess import Popen, PIPE + +def run(command): + process = Popen(command, stdout=PIPE, stderr=PIPE) + (out, err) = process.communicate() + exit_code = process.wait() + return (exit_code, out.decode("utf-8"), err.decode("utf-8"))
\ No newline at end of file diff --git a/tests/common/veth.py b/tests/common/veth.py new file mode 100644 index 0000000..a31d245 --- /dev/null +++ b/tests/common/veth.py @@ -0,0 +1,84 @@ +from common import process, netns, vlan +import time +import math + +# creates veth pair. if ok returns 0 +def create_pair(name_a, name_b): + veth, out, err = process.run( + ["ip", "link", "add", name_a, "type", "veth", "peer", "name", name_b] + ) + print("veth.create: exit=%d out=%s err=%s" % (veth, out, err)) + + return veth + + +# deletes veth pair. if ok returns 0 +def delete_veth(name_a): + veth, out, err = process.run(["ip", "link", "delete", name_a]) + print("veth.delete: exit=%d out=%s err=%s" % (veth, out, err)) + + return veth + + +# put veth to netns. if ok returns 0 +def assign_netns(veth, netns): + veth, out, err = process.run(["ip", "link", "set", veth, "netns", netns]) + print("veth.assign_netns: exit=%d out=%s err=%s" % (veth, out, err)) + + return veth + + +# up interface. if netns is None, then up in global rt. if ok returns 0 +def up_interface(iface, netns_name): + command = ["ip", "link", "set", iface, "up"] + exit, out, err = netns.exec(netns_name, command) + print( + "veth.up_interface: iface=%s netns=%s exit=%d out=%s err=%s" + % (iface, str(netns_name), exit, out, err) + ) + + return exit + + +# creates netns, creates veth pair and place second link to netns +# creates vlans over veth interfaces according to veth_pair_vlans_config +# return dict with 'netns', 'veth_a', 'veth_b' +def create_veth_pair_netns(veth_pair_vlans_config): + + name = str(math.floor(time.time() * 1000) % 1000000) + netns_name = "N" + name + netns_status = netns.create(netns_name) + print("create_veth_pair_netns: netns_status=%d" % netns_status) + + veth_a = "A" + name + veth_b = "B" + name + pair_status = create_pair(veth_a, veth_b) + print("create_veth_pair_netns: pair_status=%d" % pair_status) + + up_interface(veth_a, None) + + assign_status = assign_netns(veth_b, netns_name) + print("create_veth_pair_netns: assign_status=%d" % assign_status) + + up_interface(veth_b, netns_name) + + vlans_a = veth_pair_vlans_config["vlans_a"] + for vlan_num in vlans_a: + vlan.create(veth_a, vlan_num, None) + up_interface(veth_a + "." + str(vlan_num), None) + + vlans_b = veth_pair_vlans_config["vlans_b"] + for vlan_num in vlans_b: + vlan.create(veth_b, vlan_num, netns_name) + up_interface(veth_b + "." + str(vlan_num), netns_name) + + return {"netns": netns_name, "veth_a": veth_a, "veth_b": veth_b} + + +# deletes veth pair and netns created by create_veth_pair_netns +def delete_veth_pair_netns(veth_pair_netns): + veth_status = delete_veth(veth_pair_netns["veth_a"]) + print("delete_veth_pair_netns: veth_status=%d" % veth_status) + + netns_status = netns.delete(veth_pair_netns["netns"]) + print("delete_veth_pair_netns: netns_status=%d" % netns_status) diff --git a/tests/common/vlan.py b/tests/common/vlan.py new file mode 100644 index 0000000..459efb1 --- /dev/null +++ b/tests/common/vlan.py @@ -0,0 +1,13 @@ +from common import netns + +# up interface. if netns is None, then up in global rt. if ok returns 0 +def create(parent_if, vlan, netns_name): + command = ( + "ip link add link %s name %s.%d type vlan id %d" + % (parent_if, parent_if, vlan, vlan) + ).split() + + vlan, out, err = netns.exec(netns_name, command) + print("vlan.create: exit=%d out=%s err=%s" % (vlan, out, err)) + + return vlan diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..d373340 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,99 @@ +import pytest +from common import accel_pppd_process, config, veth + + +def pytest_addoption(parser): + parser.addoption("--accel_cmd", action="store", default="accel-cmd") + parser.addoption("--accel_pppd", action="store", default="accel-pppd") + parser.addoption("--pppd", action="store", default="pppd") # pppd client + parser.addoption( + "--dhclient", action="store", default="dhclient" + ) # isc-dhcp-client + parser.addoption( + "--accel_pppd_max_wait_time", action="store", default=5.0 + ) # start timeout + parser.addoption( + "--accel_pppd_max_finish_time", action="store", default=10.0 + ) # fininsh timeout (before kill) + + +def pytest_configure(config): + config.addinivalue_line( + "markers", + "ipoe_driver: marks tests as related to ipoe kernel module (deselect with '-m \"not ipoe_driver\"')", + ) + config.addinivalue_line( + "markers", + "vlan_mon_driver: marks tests as related to ipoe kernel module (deselect with '-m \"not vlan_mon_driver\"')", + ) + + +# accel-pppd executable file name +@pytest.fixture() +def accel_pppd(pytestconfig): + return pytestconfig.getoption("accel_pppd") + + +# accel-cmd executable file name +@pytest.fixture() +def accel_cmd(pytestconfig): + return pytestconfig.getoption("accel_cmd") + + +# accel-pppd configuration as string (should be redefined by specific test) +@pytest.fixture() +def accel_pppd_config(): + return "" + + +# accel-pppd configuration file name +@pytest.fixture() +def accel_pppd_config_file(accel_pppd_config): + # test setup: + filename = config.make_tmp(accel_pppd_config) + + # test execution + yield filename + + # test teardown: + config.delete_tmp(filename) + + +# setup and teardown for tests that required running accel-pppd +@pytest.fixture() +def accel_pppd_instance(accel_pppd, accel_pppd_config_file, accel_cmd, pytestconfig): + # test setup: + is_started, accel_pppd_thread, accel_pppd_control = accel_pppd_process.start( + accel_pppd, + ["-c" + accel_pppd_config_file], + accel_cmd, + pytestconfig.getoption("accel_pppd_max_wait_time"), + ) + + # test execution: + yield is_started + + # test teardown: + accel_pppd_process.end( + accel_pppd_thread, + accel_pppd_control, + accel_cmd, + pytestconfig.getoption("accel_pppd_max_finish_time"), + ) + +# defines vlans that will be created over veth pair (might be redefined by specific test) +@pytest.fixture() +def veth_pair_vlans_config(): + return {"vlans_a": [], "vlans_b": []} + +# setup and teardown for netns and veth pair +@pytest.fixture() +def veth_pair_netns(veth_pair_vlans_config): + # test setup: + veth_pair_netns_instance = veth.create_veth_pair_netns(veth_pair_vlans_config) + + # test execution: + yield veth_pair_netns_instance + + # test teardown: + veth.delete_veth_pair_netns(veth_pair_netns_instance) diff --git a/tests/gcovr.conf b/tests/gcovr.conf new file mode 100644 index 0000000..144b318 --- /dev/null +++ b/tests/gcovr.conf @@ -0,0 +1,2 @@ +#exclude = * +exclude = ../build/CMakeFiles |