diff --git a/t/fixtures/ngcpcfg_network_interfaces.cfg b/t/fixtures/ngcpcfg_network_interfaces.cfg new file mode 100644 index 00000000..58da0e5e --- /dev/null +++ b/t/fixtures/ngcpcfg_network_interfaces.cfg @@ -0,0 +1,37 @@ +# directory name where ngcpcfg is managed through git +[ -n "${NGCPCTL_BASE:-}" ] || NGCPCTL_BASE="$(pwd)/" +[ -n "${NGCPCTL_MAIN:-}" ] || NGCPCTL_MAIN="${NGCPCTL_BASE}/fixtures/repos/" +[ -n "${NGCPCTL_CONFIG:-}" ] || NGCPCTL_CONFIG="${NGCPCTL_MAIN}/config.yml" +[ -n "${HOST_CONFIG:-}" ] || HOST_CONFIG="${NGCPCTL_MAIN}/config.$(hostname).yml" +[ -n "${LOCAL_CONFIG:-}" ] || LOCAL_CONFIG="${NGCPCTL_MAIN}/config.local.yml" +[ -n "${CONSTANTS_CONFIG:-}" ] || CONSTANTS_CONFIG="${NGCPCTL_MAIN}/constants.yml" +[ -n "${NETWORK_CONFIG:-}" ] || NETWORK_CONFIG="${NGCPCTL_MAIN}/network_network_interfaces.yml" +[ -n "${RTP_INTERFACES_CONFIG:-}" ] || RTP_INTERFACES_CONFIG="/etc/ngcp-rtpengine-daemon/interfaces.yml" +[ -n "${EXTRA_CONFIG_DIR:-}" ] || EXTRA_CONFIG_DIR="${NGCPCTL_MAIN}/config.d/" + +# configuration dirs that should be managed +[ -n "${CONFIG_POOL:-}" ] || CONFIG_POOL='/etc /var' + +# location of templates +[ -n "${TEMPLATE_POOL_BASE:-}" ] || TEMPLATE_POOL_BASE="${NGCPCTL_MAIN}/templates" + +# location of service definitions +[ -n "${SERVICES_POOL_BASE:-}" ] || SERVICES_POOL_BASE="${NGCPCTL_MAIN}/templates" + +# Backward compatibility config for upgrade mr3.4*->mr3.5* +# it can be removed when the next LTS is released: +[ -n "${TEMPLATE_POOL:-}" ] || TEMPLATE_POOL="${TEMPLATE_POOL_BASE}/etc" +[ -n "${SERVICES_POOL:-}" ] || SERVICES_POOL="${SERVICES_POOL_BASE}/etc" + +# timestamp format for console output +[ -n "${TIME_FORMAT:-}" ] || TIME_FORMAT="+%F %T" + +# Run-time state directory +[ -n "${RUN_DIR:-}" ] || RUN_DIR='/var/run' + +# directory holding files for internal state of ngcpcfg +[ -n "${STATE_FILES_DIR:-}" ] || STATE_FILES_DIR='/var/lib/ngcpcfg/state/' + +# validate configs using kwalify schema +[ -n "${VALIDATE_SCHEMA:-}" ] || VALIDATE_SCHEMA="false" +## END OF FILE ################################################################# diff --git a/t/fixtures/output/network_interfaces b/t/fixtures/output/network_interfaces new file mode 100644 index 00000000..20c453b7 --- /dev/null +++ b/t/fixtures/output/network_interfaces @@ -0,0 +1,74 @@ +# File autogenerated by ngcpcfg + +# eth11 ---------------------- +auto eth11 +iface eth11 inet manual + pre-up ethtool -K eth11 sg off +# -------------------------------------------- + +# eth22 ---------------------- +auto eth22 +iface eth22 inet manual + pre-up ethtool -K eth22 sg off +# -------------------------------------------- + +# bond0 ---------------------- +auto bond0 +iface bond0 inet static + address 10.10.10.2 + netmask 255.0.0.0 + gateway 10.10.10.254 + bond-slaves eth11 eth22 + bond-mode active-backup + bond-miimon 100 +# -------------------------------------------- + +# lo ---------------------- +auto lo +iface lo inet loopback +iface lo inet6 loopback +# -------------------------------------------- + +# eth46 ---------------------- +auto eth46 +iface eth46 inet static + address 127.11.22.33 + netmask 255.0.0.0 + gateway 127.11.22.254 + hwaddress c0:ff:ee:15:90:0d + dns-nameservers 127.0.0.1 1.1.1.1 +iface eth46 inet6 static + address 2001:aaaa:1111:bbbb:2222:cccc:3333:dddd + netmask 64 + gateway 2001:aaaa:1111:bbbb:2222:cccc:3333:0001 + hwaddress c0:ff:ee:15:90:0d + dns-nameservers ::1 2606:4700:4700::1111 +# -------------------------------------------- + +# vlan1111 ---------------------- +auto vlan1111 +iface vlan1111 inet static + address 10.11.11.11 + netmask 255.0.0.0 + gateway 10.11.11.254 + vlan-raw-device bond0 +# -------------------------------------------- + +# vlan2222 ---------------------- +auto vlan2222 +iface vlan2222 inet static + address 10.22.22.22 + netmask 255.0.0.0 + gateway 10.22.22.254 + vlan-raw-device bond0 +# -------------------------------------------- + +# vlan3333 ---------------------- +auto vlan3333 +iface vlan3333 inet static + address 10.33.33.33 + netmask 255.0.0.0 + gateway 10.33.33.254 + vlan-raw-device bond0 +# -------------------------------------------- + diff --git a/t/fixtures/repos/network_network_interfaces.yml b/t/fixtures/repos/network_network_interfaces.yml new file mode 100644 index 00000000..1cab754e --- /dev/null +++ b/t/fixtures/repos/network_network_interfaces.yml @@ -0,0 +1,83 @@ +--- +hosts: + self: + bond0: + bond_miimon: '100' + bond_mode: active-backup + bond_slaves: eth11 eth22 + gateway: 10.10.10.254 + ip: 10.10.10.2 + netmask: 255.0.0.0 + dbnode: '1' + eth11: + dhcp: yes + hwaddr: 11:11:11:11:11:11 + mtu: '1111' + eth22: + dhcp: yes + hwaddr: 22:22:22:22:22:22 + mtu: '2222' + eth46: + dns_nameservers: + - 127.0.0.1 + - 1.1.1.1 + - ::1 + - 2606:4700:4700::1111 + gateway: 127.11.22.254 + hwaddr: c0:ff:ee:15:90:0d + ip: 127.11.22.33 + netmask: 255.0.0.0 + v6gateway: 2001:aaaa:1111:bbbb:2222:cccc:3333:0001 + v6ip: 2001:aaaa:1111:bbbb:2222:cccc:3333:dddd + v6netmask: '64' + interfaces: + - lo + - eth46 + - bond0 + - eth11 + - eth22 + - vlan1111 + - vlan2222 + - vlan3333 + lo: + ip: 127.0.0.1 + netmask: 255.255.255.0 + type: + - sip_int + - ha_int + - web_ext + - sip_ext + - rtp_ext + - ssh_ext + - mon_ext + role: + - proxy + - lb + - mgmt + vlan1111: + gateway: 10.11.11.254 + ip: 10.11.11.11 + netmask: 255.0.0.0 + type: + - ha_int + - ssh_ext + - stor_int + vlan_raw_device: bond0 + vlan2222: + gateway: 10.22.22.254 + ip: 10.22.22.22 + netmask: 255.0.0.0 + type: + - mon_ext + - ssh_ext + vlan_raw_device: bond0 + vlan3333: + gateway: 10.33.33.254 + ip: 10.33.33.33 + netmask: 255.0.0.0 + type: + - web_ext + - web_int + vlan_raw_device: bond0 + status: online + swraiddevices: [] diff --git a/t/fixtures/repos/templates/etc/network/interfaces.tt2 b/t/fixtures/repos/templates/etc/network/interfaces.tt2 new file mode 100644 index 00000000..68e65cbf --- /dev/null +++ b/t/fixtures/repos/templates/etc/network/interfaces.tt2 @@ -0,0 +1,322 @@ +# File autogenerated by ngcpcfg + +[% +hostname = ngcp.get_hostname(); + +# in SPCE there's no proper hostname, but "self", default to it +UNLESS hosts.${hostname}; + hostname = 'self'; +END; + +MACRO print_start_interface BLOCK; + "# ${interface} ----------------------\n"; + "auto ${interface}\n"; +END; + +MACRO print_end_interface BLOCK; + "# --------------------------------------------\n\n"; +END; + +MACRO print_bond_fields BLOCK; + IF iface.bond_slaves; + bslaves = iface.bond_slaves.join(" ").trim(); + " bond-slaves ${bslaves}\n"; + END; + IF iface.bond_mode; + " bond-mode ${iface.bond_mode}\n"; + END; + IF iface.bond_miimon; + " bond-miimon ${iface.bond_miimon}\n"; + END; + IF iface.bond_primary; + " bond-primary ${iface.bond_primary}\n"; + END; + IF iface.bond_updelay; + " bond-updelay ${iface.bond_updelay}\n"; + END; + IF iface.bond_downdelay; + " bond-downdelay ${iface.bond_downdelay}\n"; + END; + IF iface.bond_xmit_hash_policy; + " bond-xmit-hash-policy ${iface.bond_xmit_hash_policy}\n"; + END; +END; + +MACRO print_hwaddress BLOCK; + IF iface.hwaddr; + " hwaddress ${iface.hwaddr}\n"; + END; +END; + +MACRO print_mtu BLOCK; + IF iface.mtu; + IF (iface.ip or iface.v6ip or iface.dhcp == 'yes' or iface.v6dhcp == 'yes'); + " mtu ${iface.mtu}\n"; + ELSE; + " pre-up ip link set dev ${iface.name} mtu ${iface.mtu}\n"; + END; + END; +END; + +MACRO print_hardware_options BLOCK; + print_hwaddress; + print_mtu; +END; + +MACRO print_address_v4 BLOCK; + IF iface.ip; + " address ${iface.ip}\n"; + END; +END; + +MACRO print_address_v6 BLOCK; + IF iface.v6ip; + " address ${iface.v6ip}\n"; + END; +END; + +MACRO print_netmask_v4 BLOCK; + IF iface.netmask; + " netmask ${iface.netmask}\n"; + END; +END; + +MACRO print_netmask_v6 BLOCK; + IF iface.v6netmask; + " netmask ${iface.v6netmask}\n"; + END; +END; + +MACRO print_gateway_v4 BLOCK; + gateway = iface.gateway.trim(); + IF gateway; + " gateway ${gateway}\n"; + END; +END; + +MACRO print_gateway_v6 BLOCK; + gateway = iface.v6gateway.trim(); + IF gateway; + " gateway ${gateway}\n"; + END; +END; + +MACRO print_dns_nameservers_v4 BLOCK; + nservers = iface.dns_nameservers.grep('\.').join(" ").trim(); + IF nservers; + " dns-nameservers ${nservers}\n"; + END; +END; + +MACRO print_dns_nameservers_v6 BLOCK; + nservers = iface.dns_nameservers.grep(':').join(" ").trim(); + IF nservers; + " dns-nameservers ${nservers}\n"; + END; +END; + +MACRO print_vlan_raw_device BLOCK; + IF iface.vlan_raw_device; + devs = iface.vlan_raw_device.join(" ").trim(); + " vlan-raw-device ${devs}\n"; + END; +END; + +MACRO print_pre_up_v4 BLOCK; + IF iface.pre_up; + FOREACH rule IN iface.pre_up; + " pre-up ${rule}\n"; + END; + END; +END; + +MACRO print_pre_up_v6 BLOCK; + IF iface.v6pre_up; + FOREACH rule IN iface.v6pre_up; + " pre-up ${rule}\n"; + END; + END; +END; + +MACRO print_post_up_v4 BLOCK; + IF iface.post_up; + FOREACH rule IN iface.post_up; + " post-up ${rule}\n"; + END; + END; +END; + +MACRO print_post_up_v6 BLOCK; + IF iface.v6post_up; + FOREACH rule IN iface.v6post_up; + " post-up ${rule}\n"; + END; + END; +END; + +MACRO print_pre_down_v4 BLOCK; + IF iface.pre_down; + FOREACH rule IN iface.pre_down; + " pre-down ${rule}\n"; + END; + END; +END; + +MACRO print_pre_down_v6 BLOCK; + IF iface.v6pre_down; + FOREACH rule IN iface.v6pre_down; + " pre-down ${rule}\n"; + END; + END; +END; + +MACRO print_post_down_v4 BLOCK; + IF iface.post_down; + FOREACH rule IN iface.post_down; + " post-down ${rule}\n"; + END; + END; +END; + +MACRO print_post_down_v6 BLOCK; + IF iface.v6post_down; + FOREACH rule IN iface.v6post_down; + " post-down ${rule}\n"; + END; + END; +END; + +MACRO print_up_down_v4 BLOCK; + print_pre_up_v4; + print_post_up_v4; + print_pre_down_v4; + print_post_down_v4; +END; + +MACRO print_up_down_v6 BLOCK; + print_pre_up_v6; + print_post_up_v6; + print_pre_down_v6; + print_post_down_v6; +END; + +MACRO print_interface BLOCK; + iface = hosts.${hostname}.${interface}; + + print_start_interface(interface=interface); + + IF interface == 'lo'; + "iface ${interface} inet loopback\n"; + "iface ${interface} inet6 loopback\n"; + ELSE; + + # ipv4 + IF iface.manual == 'yes'; + "iface ${interface} inet manual\n"; + print_hardware_options(iface=iface); + print_up_down_v4(iface=iface); + ELSIF iface.dhcp == 'yes'; + "iface ${interface} inet dhcp\n"; + ELSIF iface.ip; + "iface ${interface} inet static\n"; + print_address_v4(iface=iface); + print_netmask_v4(iface=iface); + print_gateway_v4(iface=iface); + ELSE; + ipv4=no; + END; + + # ipv4-related fields + IF iface.ip or iface.dhcp == 'yes'; + # common, they can be attached to any protocol of the interface + print_hardware_options(iface=iface); + print_bond_fields(iface=iface); + print_vlan_raw_device(iface=iface); + + print_dns_nameservers_v4(iface=iface); + print_up_down_v4(iface=iface); + END; + + # ipv6 + IF iface.v6manual == 'yes'; + "iface ${interface} inet6 manual\n"; + print_hardware_options(iface=iface); + print_up_down_v6(iface=iface); + ELSIF iface.v6dhcp == 'yes'; + "iface ${interface} inet6 dhcp\n"; + ELSIF iface.v6ip; + "iface ${interface} inet6 static\n"; + print_address_v6(iface=iface); + print_netmask_v6(iface=iface); + print_gateway_v6(iface=iface); + ELSE; + ipv6=no; + END; + + # ipv6-related fields + IF iface.v6ip or iface.v6dhcp == 'yes'; + # common, they can be attached to any protocol of the interface + print_hardware_options(iface=iface); + print_bond_fields(iface=iface); + print_vlan_raw_device(iface=iface); + + print_dns_nameservers_v6(iface=iface); + print_up_down_v6(iface=iface); + END; + + # disable "Duplicate Address Detection", it creates problems with HA resource transfer + IF iface.shared_v6ip; + " dad-attempts 0\n"; + " pre-up echo 0 > /proc/sys/net/ipv6/conf/${interface}/accept_dad\n"; + END; + + IF ipv4 == 'no' and ipv6 == 'no'; + "# ERROR, neither IPv4 nor IPv6 present in network configuration\n"; + END; + + END; + + print_end_interface(); +END; + + +all_interfaces = hosts.${hostname}.interfaces; + +# print bond* interfaces in the beginning, needs to happen before vlan* start to +# use them, otherwise "ifup -a" does not work +FOREACH interface IN all_interfaces; + # "# debug: host ${hostname}: processing interface ${interface}\n"; + + iface = hosts.${hostname}.${interface}; + + IF iface.bond_slaves; + interfaces_in_bond = iface.bond_slaves.split(" "); + FOREACH phys_interface IN interfaces_in_bond; + + print_start_interface(interface=phys_interface); + "iface ${phys_interface} inet manual\n"; + " pre-up ethtool -K ${phys_interface} sg off\n"; + print_hardware_options(iface=iface); + print_up_down_v4(iface=iface); + print_end_interface(); + + # remove now to not process again, but lists do not allow to remove + # elements straight away + all_interfaces = all_interfaces.join(" ").remove("${phys_interface}").split(" +"); + END; + + print_interface(interface=interface); + END; +END; + +# print non-bond* and non-tun* interfaces +FOREACH interface IN all_interfaces; + # "# debug: host ${hostname}: processing interface ${interface}\n"; + + iface = hosts.${hostname}.${interface}; + + UNLESS iface.bond_slaves or interface.match('^tun[0-9]+$'); + print_interface(interface=interface); + END; +END; +-%] diff --git a/t/test_ngcpcfg_build_network_interfaces.py b/t/test_ngcpcfg_build_network_interfaces.py new file mode 100644 index 00000000..52fef399 --- /dev/null +++ b/t/test_ngcpcfg_build_network_interfaces.py @@ -0,0 +1,48 @@ +#!/usr/bin/env py.test-3 + +import filecmp +import os +import pytest +import re +import tempfile +import sys + + +@pytest.mark.tt_47255 +def test_network_interfaces(ngcpcfgcli, tmpdir): + tmpdir = tempfile.mkdtemp(prefix='ngcp-', suffix='-pytest-output') + out = ngcpcfgcli("build", "--ignore-branch-check", + "/etc/network/interfaces", + env={ + 'NGCP_BASE_TT2': os.getcwd(), + 'NGCP_SOCKETFILE': '/tmp/ngcpcfg.socket', + 'OUTPUT_DIRECTORY': tmpdir, + 'NGCPCFG': 'fixtures/ngcpcfg_network_interfaces.cfg', + }) + + # debug, only printed in logs in case of error + print("stdout:") + print(out.stdout.replace("\\n", "\n")) + print("stderr:") + print(out.stderr.replace("\\n", "\n")) + + regex1 = re.compile(r"Generating .*/etc/network/interfaces: OK") + assert re.search(regex1, out.stdout) + + regex2 = re.compile(r"Error") + assert not re.search(regex2, out.stdout) + assert not re.search(regex2, out.stderr) + + output_file = os.path.join(tmpdir, "etc/network/interfaces") + test_file = "fixtures/output/network_interfaces" + + assert os.path.exists(output_file) + assert os.path.exists(test_file) + + # debug + if not filecmp.cmp(output_file, test_file): + print("output_file:") + print(open(output_file).read()) + print("test_file:") + print(open(test_file).read()) + assert filecmp.cmp(output_file, test_file)