2023-09-28 16:48:28 +08:00
# SPDX-License-Identifier: LGPL-2.1-or-later
2018-10-17 10:25:42 +03:00
import logging
2022-03-02 12:44:41 +01:00
import os
2019-11-12 21:15:43 +01:00
import subprocess
2024-04-01 09:42:21 +02:00
import tempfile
from pathlib import Path
2018-10-17 10:25:42 +03:00
import pytest
2019-11-19 17:39:01 +01:00
import libnmstate
2024-05-30 08:25:28 +08:00
from libnmstate . schema import Description
2020-03-19 00:01:37 +08:00
from libnmstate . schema import DNS
from libnmstate . schema import Route
from libnmstate . schema import RouteRule
2019-11-19 17:39:01 +01:00
2019-07-28 20:09:10 +03:00
from . testlib import ifacelib
2023-09-28 16:48:28 +08:00
from . testlib . veth import create_veth_pair
from . testlib . veth import remove_veth_pair
2018-10-24 06:42:06 +03:00
2018-10-17 10:25:42 +03:00
2019-11-12 21:15:43 +01:00
REPORT_HEADER = """ RPMs: {rpms}
OS : { osname }
2020-02-27 20:35:23 +08:00
nmstate : { nmstate_version }
2019-11-12 21:15:43 +01:00
"""
2023-09-28 16:48:28 +08:00
ISOLATE_NAMESPACE = " nmstate_test_ep "
2024-04-01 09:42:21 +02:00
LIBNMSTATE_APPLY = libnmstate . apply
LIBNMSTATE_SHOW = libnmstate . show
DUMP_STATES_DIR = os . path . join (
os . path . dirname ( os . path . realpath ( __file__ ) ) , " .states "
)
2024-05-30 08:25:28 +08:00
# Dump YAMLs for AI training
OPT_DUMP_AI_TRAIN_YAML = " --dump-ai-train-yaml "
DUMP_AI_TRAIN_YAML = False
2023-09-28 16:48:28 +08:00
2019-11-12 21:15:43 +01:00
2020-06-23 11:45:45 +08:00
def pytest_configure ( config ) :
2021-05-19 07:41:17 +08:00
config . addinivalue_line ( " markers " , " slow: mark time consuming test " )
config . addinivalue_line ( " markers " , " tier2 " )
config . addinivalue_line ( " markers " , " tier1 " )
2020-06-23 11:45:45 +08:00
2020-02-13 15:09:11 +01:00
def pytest_addoption ( parser ) :
parser . addoption (
" --runslow " , action = " store_true " , default = False , help = " run slow tests "
)
2024-04-01 09:42:21 +02:00
parser . addoption (
" --dump-states " ,
action = " store_true " ,
default = False ,
help = " dump applied and showed network states " ,
)
2024-05-30 08:25:28 +08:00
parser . addoption (
OPT_DUMP_AI_TRAIN_YAML ,
action = " store_true " ,
default = False ,
help = " dump applied network states with top description only " ,
)
2020-02-13 15:09:11 +01:00
def pytest_collection_modifyitems ( config , items ) :
2020-03-17 23:32:36 +01:00
if not config . getoption ( " --runslow " ) :
# --runslow is not in cli: skip slow tests
_mark_skip_slow_tests ( items )
2024-04-01 09:42:21 +02:00
2024-05-30 08:25:28 +08:00
if config . getoption ( OPT_DUMP_AI_TRAIN_YAML ) :
global DUMP_AI_TRAIN_YAML
DUMP_AI_TRAIN_YAML = True
if config . getoption ( " --dump-states " ) or config . getoption (
OPT_DUMP_AI_TRAIN_YAML
) :
2024-04-01 09:42:21 +02:00
libnmstate . apply = _custom_apply_with_dump_state
libnmstate . show = _custom_show_with_dump_state
2020-03-17 23:32:36 +01:00
_mark_tier2_tests ( items )
def _mark_skip_slow_tests ( items ) :
2020-02-13 15:09:11 +01:00
skip_slow = pytest . mark . skip ( reason = " need --runslow option to run " )
for item in items :
if " slow " in item . keywords :
item . add_marker ( skip_slow )
2020-03-17 23:32:36 +01:00
def _mark_tier2_tests ( items ) :
for item in items :
if " tier1 " not in item . keywords :
item . add_marker ( pytest . mark . tier2 )
2019-12-12 09:00:05 +01:00
@pytest.fixture ( scope = " session " , autouse = True )
2023-10-20 16:31:47 +08:00
def test_env_setup ( ) :
2020-03-19 00:01:37 +08:00
_logging_setup ( )
old_state = libnmstate . show ( )
2022-03-02 12:44:41 +01:00
old_state = _remove_interfaces_from_env ( old_state )
2021-06-03 18:59:07 +08:00
_remove_dns_route_route_rule ( )
2023-09-28 16:48:28 +08:00
for nic_name in [ " eth1 " , " eth2 " ] :
remove_veth_pair ( nic_name , ISOLATE_NAMESPACE )
for nic_name in [ " eth1 " , " eth2 " ] :
create_veth_pair ( nic_name , f " { nic_name } .ep " , ISOLATE_NAMESPACE )
2020-03-19 00:01:37 +08:00
_ethx_init ( )
yield
2023-09-28 16:48:28 +08:00
for nic_name in [ " eth1 " , " eth2 " ] :
remove_veth_pair ( nic_name , ISOLATE_NAMESPACE )
2022-09-08 11:47:58 +08:00
restore_old_state ( old_state )
2020-03-19 00:01:37 +08:00
2021-06-03 18:59:07 +08:00
def _remove_dns_route_route_rule ( ) :
2020-03-19 00:01:37 +08:00
"""
Remove existing DNS , routes , route rules in case it interference tests .
"""
2021-06-03 18:59:07 +08:00
libnmstate . apply (
{
DNS . KEY : { DNS . CONFIG : { } } ,
Route . KEY : {
Route . CONFIG : [ { Route . STATE : Route . STATE_ABSENT } ] ,
} ,
RouteRule . KEY : { RouteRule . CONFIG : [ ] } ,
2022-07-29 21:42:40 +08:00
} ,
verify_change = False ,
2021-06-03 18:59:07 +08:00
)
2020-03-19 00:01:37 +08:00
def _logging_setup ( ) :
2018-10-17 10:25:42 +03:00
logging . basicConfig (
2019-12-12 09:00:05 +01:00
format = " %(asctime)s %(name)-12s %(levelname)-8s %(message)s " ,
2019-06-16 10:16:02 +03:00
level = logging . DEBUG ,
)
2018-10-24 06:42:06 +03:00
2020-03-19 00:01:37 +08:00
def _ethx_init ( ) :
2021-06-07 12:32:36 +08:00
""" Remove any existing definitions on the ethX interfaces. """
2019-12-12 09:00:05 +01:00
ifacelib . ifaces_init ( " eth1 " , " eth2 " )
2018-11-19 09:54:12 +02:00
2022-03-02 12:44:41 +01:00
def _remove_interfaces_from_env ( state ) :
"""
Remove references from interfaces passed to environment variable
NMSTATE_TEST_IGNORE_IFACE .
"""
ignore_iface = os . getenv ( " NMSTATE_TEST_IGNORE_IFACE " )
if ignore_iface is None :
return state
state [ " interfaces " ] = [
i for i in state [ " interfaces " ] if ignore_iface not in i [ " name " ]
]
state [ " routes " ] [ " config " ] = [
r
for r in state [ " routes " ] [ " config " ]
if ignore_iface not in r [ " next-hop-interface " ]
]
return state
2019-12-12 09:00:05 +01:00
@pytest.fixture ( scope = " function " )
2023-09-28 16:48:28 +08:00
def eth1_up ( test_env_setup ) :
2019-12-12 09:00:05 +01:00
with ifacelib . iface_up ( " eth1 " ) as ifstate :
2019-07-28 20:09:10 +03:00
yield ifstate
2018-11-19 09:54:12 +02:00
2019-12-12 09:00:05 +01:00
@pytest.fixture ( scope = " function " )
2023-09-28 16:48:28 +08:00
def eth2_up ( test_env_setup ) :
2019-12-12 09:00:05 +01:00
with ifacelib . iface_up ( " eth2 " ) as ifstate :
2019-07-28 20:09:10 +03:00
yield ifstate
2018-11-19 09:54:12 +02:00
2019-06-21 11:07:00 +03:00
port0_up = eth1_up
port1_up = eth2_up
2019-11-12 21:15:43 +01:00
def pytest_report_header ( config ) :
2024-06-12 09:45:48 +08:00
nm_ver = _get_package_nvr ( " NetworkManager " )
2024-07-31 10:30:34 +02:00
try :
nm_libreswan_ver = _get_package_nvr ( " NetworkManager-libreswan " )
except subprocess . CalledProcessError :
nm_libreswan_ver = " NetworkManager-libreswan (not installed) "
2019-11-12 21:15:43 +01:00
return REPORT_HEADER . format (
2024-06-12 09:45:48 +08:00
rpms = f " { nm_ver } { nm_libreswan_ver } " ,
2020-02-27 20:35:23 +08:00
osname = _get_osname ( ) ,
nmstate_version = _get_nmstate_version ( ) ,
2019-11-12 21:15:43 +01:00
)
2020-02-27 20:35:23 +08:00
def _get_nmstate_version ( ) :
"""
Prefer RPM version of nmstate , if not found , use libnmstate module version
"""
try :
return _get_package_nvr ( " nmstate " )
except subprocess . CalledProcessError :
return libnmstate . __version__
2019-11-12 21:15:43 +01:00
def _get_package_nvr ( package ) :
2024-06-12 09:45:48 +08:00
return subprocess . check_output (
[ " rpm " , " -q " , " --qf " , " % {name} - % {VERSION} - % {RELEASE} " , package ]
) . decode ( " utf-8 " )
2019-11-12 21:15:43 +01:00
def _get_osname ( ) :
2019-12-12 09:00:05 +01:00
with open ( " /etc/os-release " ) as os_release :
2019-11-12 21:15:43 +01:00
for line in os_release . readlines ( ) :
2019-12-12 09:00:05 +01:00
if line . startswith ( " PRETTY_NAME= " ) :
return line . split ( " = " , maxsplit = 1 ) [ 1 ] . strip ( ) . strip ( ' " ' )
return " "
2022-09-08 11:47:58 +08:00
2024-04-01 09:42:21 +02:00
def _dump_state (
state ,
) :
path = Path ( DUMP_STATES_DIR )
path . mkdir ( exist_ok = True )
test_name = (
os . environ . get ( " PYTEST_CURRENT_TEST " )
. split ( " : " ) [ - 1 ]
. split ( " " ) [ 0 ]
. lower ( )
)
state_file = tempfile . NamedTemporaryFile (
dir = path , prefix = test_name + " - " , suffix = " .yml " , delete = False
)
with open ( state_file . name , " a " ) as outfile :
2024-05-30 08:25:28 +08:00
outfile . write ( libnmstate . PrettyState ( state ) . yaml )
2024-04-01 09:42:21 +02:00
def _custom_apply_with_dump_state (
desired_state ,
* args ,
* * kwargs ,
) :
2024-05-30 08:25:28 +08:00
if DUMP_AI_TRAIN_YAML :
cur_state = libnmstate . show ( )
2024-04-01 09:42:21 +02:00
result = LIBNMSTATE_APPLY (
desired_state ,
* args ,
* * kwargs ,
)
2024-05-30 08:25:28 +08:00
if DUMP_AI_TRAIN_YAML :
if Description . KEY in desired_state :
diff_state = libnmstate . generate_differences (
desired_state , cur_state
)
_dump_state ( diff_state )
else :
_dump_state ( desired_state )
2024-04-01 09:42:21 +02:00
return result
def _custom_show_with_dump_state (
* args ,
* * kwargs ,
) :
current_state = LIBNMSTATE_SHOW (
* args ,
* * kwargs ,
)
2024-05-30 08:25:28 +08:00
if not DUMP_AI_TRAIN_YAML :
_dump_state ( current_state )
2024-04-01 09:42:21 +02:00
return current_state
2022-09-08 11:47:58 +08:00
# Only restore the interface with IPv4/IPv6 gateway with IP/DNS config only
# For test machine, it is expected to lose configurations
def restore_old_state ( old_state ) :
gw_routes = [
rt
for rt in old_state [ " routes " ] . get ( " config " , [ ] )
if rt [ " destination " ] in ( " 0.0.0.0/0 " , " ::/0 " )
]
gw_ifaces = [ rt [ " next-hop-interface " ] for rt in gw_routes ]
desire_state = {
" interfaces " : [ ] ,
" routes " : { " config " : gw_routes } ,
" dns-resolver " : old_state . get ( " dns-resolver " , { } ) ,
}
for iface_name in gw_ifaces :
for iface in old_state [ " interfaces " ] :
if iface [ " name " ] in gw_ifaces :
if iface [ " state " ] == " up " :
desire_state [ " interfaces " ] . append (
{
" name " : iface [ " name " ] ,
" type " : iface [ " type " ] ,
" ipv4 " : iface [ " ipv4 " ] ,
" ipv6 " : iface [ " ipv6 " ] ,
}
)
if len ( desire_state [ " interfaces " ] ) :
libnmstate . apply ( desire_state , verify_change = False )