summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--data/templates/conserver/dropbear@.service.tmpl2
-rw-r--r--data/templates/dhcp-client/ipv4.tmpl3
-rw-r--r--data/templates/frr/vrf-vni.frr.tmpl10
-rw-r--r--data/templates/ipsec/swanctl/profile.tmpl2
-rw-r--r--debian/vyos-1x.install1
-rw-r--r--interface-definitions/containers.xml.in8
-rw-r--r--interface-definitions/include/bgp/afi-route-target-vpn.xml.i2
-rw-r--r--interface-definitions/include/nat-translation-options.xml.i2
-rw-r--r--interface-definitions/vrf.xml.in2
-rw-r--r--op-mode-definitions/show-ip-route.xml.in7
-rw-r--r--python/vyos/ifconfig/ethernet.py17
-rw-r--r--python/vyos/ifconfig/wwan.py17
-rw-r--r--python/vyos/util.py16
-rw-r--r--smoketest/scripts/cli/test_container.py1
-rwxr-xr-xsmoketest/scripts/cli/test_ha_vrrp.py1
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_geneve.py1
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_openvpn.py1
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_pppoe.py1
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_vxlan.py1
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_wireguard.py1
-rwxr-xr-xsmoketest/scripts/cli/test_nat.py1
-rwxr-xr-xsmoketest/scripts/cli/test_nat66.py1
-rwxr-xr-xsmoketest/scripts/cli/test_pki.py1
-rwxr-xr-xsmoketest/scripts/cli/test_policy.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_bfd.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_igmp-proxy.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_isis.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ospfv3.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rip.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_ripng.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_rpki.py1
-rwxr-xr-xsmoketest/scripts/cli/test_protocols_static.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_bcast-relay.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-relay.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcp-server.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcpv6-relay.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_dhcpv6-server.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_dynamic.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_dns_forwarding.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_https.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_mdns-repeater.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_router-advert.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_snmp.py34
-rwxr-xr-xsmoketest/scripts/cli/test_service_ssh.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_tftp-server.py1
-rwxr-xr-xsmoketest/scripts/cli/test_service_webproxy.py1
-rwxr-xr-xsmoketest/scripts/cli/test_system_acceleration_qat.py1
-rwxr-xr-xsmoketest/scripts/cli/test_system_conntrack.py1
-rwxr-xr-xsmoketest/scripts/cli/test_system_flow-accounting.py80
-rwxr-xr-xsmoketest/scripts/cli/test_system_ip.py1
-rwxr-xr-xsmoketest/scripts/cli/test_system_ipv6.py1
-rwxr-xr-xsmoketest/scripts/cli/test_system_lcd.py1
-rwxr-xr-xsmoketest/scripts/cli/test_system_login.py1
-rwxr-xr-xsmoketest/scripts/cli/test_system_nameserver.py1
-rwxr-xr-xsmoketest/scripts/cli/test_system_ntp.py1
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_ipsec.py2
-rwxr-xr-xsmoketest/scripts/cli/test_vpn_openconnect.py1
-rwxr-xr-xsmoketest/scripts/cli/test_vrf.py46
-rwxr-xr-xsrc/conf_mode/interfaces-openvpn.py4
-rwxr-xr-xsrc/conf_mode/interfaces-wwan.py4
-rwxr-xr-xsrc/conf_mode/snmp.py19
-rwxr-xr-xsrc/conf_mode/system-login-banner.py2
-rwxr-xr-xsrc/conf_mode/vrf.py14
-rwxr-xr-xsrc/conf_mode/vrf_vni.py27
-rw-r--r--src/etc/cron.d/check-wwan1
-rw-r--r--src/etc/systemd/system/openvpn@.service.d/10-override.conf1
-rwxr-xr-xsrc/helpers/vyos-check-wwan.py37
-rwxr-xr-xsrc/helpers/vyos_net_name14
-rwxr-xr-xsrc/op_mode/connect_disconnect.py68
-rwxr-xr-xsrc/op_mode/show_interfaces.py4
-rw-r--r--src/services/api/graphql/README.graphql62
-rw-r--r--src/services/api/graphql/bindings.py13
-rw-r--r--src/services/api/graphql/graphql/directives.py32
-rw-r--r--src/services/api/graphql/graphql/mutations.py34
-rw-r--r--src/services/api/graphql/graphql/schema/config_file.graphql27
-rw-r--r--src/services/api/graphql/graphql/schema/dhcp_server.graphql8
-rw-r--r--src/services/api/graphql/graphql/schema/firewall_group.graphql47
-rw-r--r--src/services/api/graphql/graphql/schema/interface_ethernet.graphql8
-rw-r--r--src/services/api/graphql/graphql/schema/schema.graphql12
-rw-r--r--src/services/api/graphql/recipes/dhcp_server.py13
-rw-r--r--src/services/api/graphql/recipes/interface_ethernet.py13
-rw-r--r--src/services/api/graphql/recipes/remove_firewall_address_group_members.py21
-rw-r--r--src/services/api/graphql/recipes/session.py (renamed from src/services/api/graphql/recipes/recipe.py)44
-rw-r--r--src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl (renamed from src/services/api/graphql/recipes/templates/dhcp_server.tmpl)0
-rw-r--r--src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl4
-rw-r--r--src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl (renamed from src/services/api/graphql/recipes/templates/interface_ethernet.tmpl)0
-rw-r--r--src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl3
-rw-r--r--src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl3
-rwxr-xr-xsrc/services/vyos-http-api-server44
89 files changed, 632 insertions, 243 deletions
diff --git a/data/templates/conserver/dropbear@.service.tmpl b/data/templates/conserver/dropbear@.service.tmpl
index 4bb73f751..e355dab43 100644
--- a/data/templates/conserver/dropbear@.service.tmpl
+++ b/data/templates/conserver/dropbear@.service.tmpl
@@ -1,4 +1,4 @@
[Service]
ExecStart=
-ExecStart=/usr/sbin/dropbear -w -j -k -r /etc/dropbear/dropbear_rsa_host_key -c "/usr/bin/console {{ device }}" -P /run/conserver/dropbear.%I.pid -p %I
+ExecStart=/usr/sbin/dropbear -w -j -k -r /etc/dropbear/dropbear_rsa_host_key -b /etc/issue.net -c "/usr/bin/console {{ device }}" -P /run/conserver/dropbear.%I.pid -p %I
PIDFile=/run/conserver/dropbear.%I.pid
diff --git a/data/templates/dhcp-client/ipv4.tmpl b/data/templates/dhcp-client/ipv4.tmpl
index 11e961166..b3e74c22b 100644
--- a/data/templates/dhcp-client/ipv4.tmpl
+++ b/data/templates/dhcp-client/ipv4.tmpl
@@ -2,7 +2,8 @@
option rfc3442-classless-static-routes code 121 = array of unsigned integer 8;
timeout 60;
-retry 300;
+retry 60;
+initial-interval 2;
interface "{{ ifname }}" {
send host-name "{{ dhcp_options.host_name }}";
diff --git a/data/templates/frr/vrf-vni.frr.tmpl b/data/templates/frr/vrf-vni.frr.tmpl
index 51d4ede1b..299c9719e 100644
--- a/data/templates/frr/vrf-vni.frr.tmpl
+++ b/data/templates/frr/vrf-vni.frr.tmpl
@@ -1,7 +1,9 @@
-{% if vrf is defined and vrf is not none %}
+{% if name is defined and name is not none %}
+{% for vrf, vrf_config in name.items() %}
vrf {{ vrf }}
-{% if vni is defined and vni is not none %}
- vni {{ vni }}
-{% endif %}
+{% if vrf_config.vni is defined and vrf_config.vni is not none %}
+ vni {{ vrf_config.vni }}
+{% endif %}
exit-vrf
+{% endfor %}
{% endif %}
diff --git a/data/templates/ipsec/swanctl/profile.tmpl b/data/templates/ipsec/swanctl/profile.tmpl
index 948dd8f87..a5cae31c0 100644
--- a/data/templates/ipsec/swanctl/profile.tmpl
+++ b/data/templates/ipsec/swanctl/profile.tmpl
@@ -7,7 +7,7 @@
dmvpn-{{ name }}-{{ interface }} {
proposals = {{ ike_group[profile_conf.ike_group] | get_esp_ike_cipher | join(',') }}
version = {{ ike.key_exchange[4:] if ike is defined and ike.key_exchange is defined else "0" }}
- life_time = {{ ike.lifetime }}s
+ rekey_time = {{ ike.lifetime }}s
keyingtries = 0
{% if profile_conf.authentication is defined and profile_conf.authentication.mode is defined and profile_conf.authentication.mode == 'pre-shared-secret' %}
local {
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 10426260a..29d74390f 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -1,3 +1,4 @@
+etc/cron.d
etc/cron.hourly
etc/dhcp
etc/ipsec.d
diff --git a/interface-definitions/containers.xml.in b/interface-definitions/containers.xml.in
index 1e9c36ee5..30c7110b8 100644
--- a/interface-definitions/containers.xml.in
+++ b/interface-definitions/containers.xml.in
@@ -25,13 +25,17 @@
<properties>
<help>Container capabilities/permissions</help>
<completionHelp>
- <list>net-admin net-raw setpcap sys-admin sys-time</list>
+ <list>net-admin net-bind-service net-raw setpcap sys-admin sys-time</list>
</completionHelp>
<valueHelp>
<format>net-admin</format>
<description>Network operations (interface, firewall, routing tables)</description>
</valueHelp>
<valueHelp>
+ <format>net-bind-service</format>
+ <description>Bind a socket to privileged ports (port numbers less than 1024)</description>
+ </valueHelp>
+ <valueHelp>
<format>net-raw</format>
<description>Permission to create raw network sockets</description>
</valueHelp>
@@ -48,7 +52,7 @@
<description>Permission to set system clock</description>
</valueHelp>
<constraint>
- <regex>^(net-admin|net-raw|setpcap|sys-admin|sys-time)$</regex>
+ <regex>^(net-admin|net-bind-service|net-raw|setpcap|sys-admin|sys-time)$</regex>
</constraint>
<multi/>
</properties>
diff --git a/interface-definitions/include/bgp/afi-route-target-vpn.xml.i b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i
index 1dc184a02..0cd0fdd76 100644
--- a/interface-definitions/include/bgp/afi-route-target-vpn.xml.i
+++ b/interface-definitions/include/bgp/afi-route-target-vpn.xml.i
@@ -1,7 +1,7 @@
<!-- include start from bgp/route-target-both.xml.i -->
<node name="route-target">
<properties>
- <help>Specify route distinguisher</help>
+ <help>Specify route target list</help>
</properties>
<children>
<node name="vpn">
diff --git a/interface-definitions/include/nat-translation-options.xml.i b/interface-definitions/include/nat-translation-options.xml.i
index defc8c0d5..df2f76397 100644
--- a/interface-definitions/include/nat-translation-options.xml.i
+++ b/interface-definitions/include/nat-translation-options.xml.i
@@ -16,7 +16,7 @@
</valueHelp>
<valueHelp>
<format>random</format>
- <description>Random source or destination address allocation for each connection (defaut)</description>
+ <description>Random source or destination address allocation for each connection (default)</description>
</valueHelp>
<constraint>
<regex>^(persistent|random)$</regex>
diff --git a/interface-definitions/vrf.xml.in b/interface-definitions/vrf.xml.in
index a82c0b2a6..d6a602f53 100644
--- a/interface-definitions/vrf.xml.in
+++ b/interface-definitions/vrf.xml.in
@@ -85,7 +85,7 @@
<constraintErrorMessage>VRF routing table must be in range from 100 to 65535</constraintErrorMessage>
</properties>
</leafNode>
- <leafNode name="vni" owner="${vyos_conf_scripts_dir}/vrf_vni.py $VAR(../@)">
+ <leafNode name="vni" owner="${vyos_conf_scripts_dir}/vrf_vni.py">
<properties>
<help>Virtual Network Identifier</help>
<!-- priority must be after BGP -->
diff --git a/op-mode-definitions/show-ip-route.xml.in b/op-mode-definitions/show-ip-route.xml.in
index 0a24bc45a..fdbb6859d 100644
--- a/op-mode-definitions/show-ip-route.xml.in
+++ b/op-mode-definitions/show-ip-route.xml.in
@@ -125,16 +125,11 @@
</properties>
<command>vtysh -c "show ip route tag $5"</command>
</tagNode>
- <node name="vrf">
- <properties>
- <help>Show IP routes in VRF</help>
- </properties>
- </node>
<tagNode name="vrf">
<properties>
<help>Show IP routes in VRF</help>
<completionHelp>
- <list>&lt;vrf&gt;</list>
+ <list>all</list>
<path>vrf name</path>
</completionHelp>
</properties>
diff --git a/python/vyos/ifconfig/ethernet.py b/python/vyos/ifconfig/ethernet.py
index 2e59a7afc..9d54dc78e 100644
--- a/python/vyos/ifconfig/ethernet.py
+++ b/python/vyos/ifconfig/ethernet.py
@@ -80,6 +80,23 @@ class EthernetIf(Interface):
super().__init__(ifname, **kargs)
self.ethtool = Ethtool(ifname)
+ def remove(self):
+ """
+ Remove interface from config. Removing the interface deconfigures all
+ assigned IP addresses.
+ Example:
+ >>> from vyos.ifconfig import WWANIf
+ >>> i = EthernetIf('eth0')
+ >>> i.remove()
+ """
+
+ if self.exists(self.ifname):
+ # interface is placed in A/D state when removed from config! It
+ # will remain visible for the operating system.
+ self.set_admin_state('down')
+
+ super().remove()
+
def set_flow_control(self, enable):
"""
Changes the pause parameters of the specified Ethernet device.
diff --git a/python/vyos/ifconfig/wwan.py b/python/vyos/ifconfig/wwan.py
index f18959a60..845c9bef9 100644
--- a/python/vyos/ifconfig/wwan.py
+++ b/python/vyos/ifconfig/wwan.py
@@ -26,3 +26,20 @@ class WWANIf(Interface):
'eternal': 'wwan[0-9]+$',
},
}
+
+ def remove(self):
+ """
+ Remove interface from config. Removing the interface deconfigures all
+ assigned IP addresses.
+ Example:
+ >>> from vyos.ifconfig import WWANIf
+ >>> i = WWANIf('wwan0')
+ >>> i.remove()
+ """
+
+ if self.exists(self.ifname):
+ # interface is placed in A/D state when removed from config! It
+ # will remain visible for the operating system.
+ self.set_admin_state('down')
+
+ super().remove()
diff --git a/python/vyos/util.py b/python/vyos/util.py
index 9c4c29322..9aa1f98d2 100644
--- a/python/vyos/util.py
+++ b/python/vyos/util.py
@@ -931,3 +931,19 @@ def install_into_config(conf, config_paths, override_prompt=True):
if count > 0:
print(f'{count} value(s) installed. Use "compare" to see the pending changes, and "commit" to apply.')
+
+def is_wwan_connected(interface):
+ """ Determine if a given WWAN interface, e.g. wwan0 is connected to the
+ carrier network or not """
+ import json
+
+ if not interface.startswith('wwan'):
+ raise ValueError(f'Specified interface "{interface}" is not a WWAN interface')
+
+ modem = interface.lstrip('wwan')
+
+ tmp = cmd(f'mmcli --modem {modem} --output-json')
+ tmp = json.loads(tmp)
+
+ # return True/False if interface is in connected state
+ return dict_search('modem.generic.state', tmp) == 'connected'
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index 09ca89721..cc0cdaec0 100644
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -19,7 +19,6 @@ import json
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_ha_vrrp.py b/smoketest/scripts/cli/test_ha_vrrp.py
index 8c5bb86d8..2524bf2b1 100755
--- a/smoketest/scripts/cli/test_ha_vrrp.py
+++ b/smoketest/scripts/cli/test_ha_vrrp.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.ifconfig.vrrp import VRRP
from vyos.util import cmd
diff --git a/smoketest/scripts/cli/test_interfaces_geneve.py b/smoketest/scripts/cli/test_interfaces_geneve.py
index 129ee71e5..6233ade6e 100755
--- a/smoketest/scripts/cli/test_interfaces_geneve.py
+++ b/smoketest/scripts/cli/test_interfaces_geneve.py
@@ -16,7 +16,6 @@
import unittest
-from vyos.configsession import ConfigSession
from vyos.ifconfig import Interface
from vyos.util import get_interface_config
diff --git a/smoketest/scripts/cli/test_interfaces_openvpn.py b/smoketest/scripts/cli/test_interfaces_openvpn.py
index 7ce1b9872..f8a6ae986 100755
--- a/smoketest/scripts/cli/test_interfaces_openvpn.py
+++ b/smoketest/scripts/cli/test_interfaces_openvpn.py
@@ -23,7 +23,6 @@ from netifaces import interfaces
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_interfaces_pppoe.py b/smoketest/scripts/cli/test_interfaces_pppoe.py
index 67edce2a0..4f1e1ee99 100755
--- a/smoketest/scripts/cli/test_interfaces_pppoe.py
+++ b/smoketest/scripts/cli/test_interfaces_pppoe.py
@@ -20,7 +20,6 @@ import unittest
from psutil import process_iter
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
config_file = '/etc/ppp/peers/{}'
diff --git a/smoketest/scripts/cli/test_interfaces_vxlan.py b/smoketest/scripts/cli/test_interfaces_vxlan.py
index 7b420cd51..f63c850d8 100755
--- a/smoketest/scripts/cli/test_interfaces_vxlan.py
+++ b/smoketest/scripts/cli/test_interfaces_vxlan.py
@@ -16,7 +16,6 @@
import unittest
-from vyos.configsession import ConfigSession
from vyos.ifconfig import Interface
from vyos.util import get_interface_config
diff --git a/smoketest/scripts/cli/test_interfaces_wireguard.py b/smoketest/scripts/cli/test_interfaces_wireguard.py
index 3707eaac3..aaf27a2c4 100755
--- a/smoketest/scripts/cli/test_interfaces_wireguard.py
+++ b/smoketest/scripts/cli/test_interfaces_wireguard.py
@@ -18,7 +18,6 @@ import os
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
base_path = ['interfaces', 'wireguard']
diff --git a/smoketest/scripts/cli/test_nat.py b/smoketest/scripts/cli/test_nat.py
index 0706f234e..75c628244 100755
--- a/smoketest/scripts/cli/test_nat.py
+++ b/smoketest/scripts/cli/test_nat.py
@@ -20,7 +20,6 @@ import json
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import dict_search
diff --git a/smoketest/scripts/cli/test_nat66.py b/smoketest/scripts/cli/test_nat66.py
index 7721105e0..8afe0da26 100755
--- a/smoketest/scripts/cli/test_nat66.py
+++ b/smoketest/scripts/cli/test_nat66.py
@@ -21,7 +21,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import dict_search
diff --git a/smoketest/scripts/cli/test_pki.py b/smoketest/scripts/cli/test_pki.py
index deaf23b05..45a4bd61e 100755
--- a/smoketest/scripts/cli/test_pki.py
+++ b/smoketest/scripts/cli/test_pki.py
@@ -17,7 +17,6 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
base_path = ['pki']
diff --git a/smoketest/scripts/cli/test_policy.py b/smoketest/scripts/cli/test_policy.py
index c2288a86a..1286a768d 100755
--- a/smoketest/scripts/cli/test_policy.py
+++ b/smoketest/scripts/cli/test_policy.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
diff --git a/smoketest/scripts/cli/test_protocols_bfd.py b/smoketest/scripts/cli/test_protocols_bfd.py
index a57f8d5f2..297398d3c 100755
--- a/smoketest/scripts/cli/test_protocols_bfd.py
+++ b/smoketest/scripts/cli/test_protocols_bfd.py
@@ -17,7 +17,6 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_protocols_igmp-proxy.py b/smoketest/scripts/cli/test_protocols_igmp-proxy.py
index 1eaf21722..079b5bee5 100755
--- a/smoketest/scripts/cli/test_protocols_igmp-proxy.py
+++ b/smoketest/scripts/cli/test_protocols_igmp-proxy.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import read_file
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_protocols_isis.py b/smoketest/scripts/cli/test_protocols_isis.py
index 8170f2b56..f4b0a690d 100755
--- a/smoketest/scripts/cli/test_protocols_isis.py
+++ b/smoketest/scripts/cli/test_protocols_isis.py
@@ -17,7 +17,6 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_protocols_ospfv3.py b/smoketest/scripts/cli/test_protocols_ospfv3.py
index 0b4b01993..c0673629e 100755
--- a/smoketest/scripts/cli/test_protocols_ospfv3.py
+++ b/smoketest/scripts/cli/test_protocols_ospfv3.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.ifconfig import Section
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_protocols_rip.py b/smoketest/scripts/cli/test_protocols_rip.py
index 423cd811a..80d4e79f9 100755
--- a/smoketest/scripts/cli/test_protocols_rip.py
+++ b/smoketest/scripts/cli/test_protocols_rip.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.ifconfig import Section
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_protocols_ripng.py b/smoketest/scripts/cli/test_protocols_ripng.py
index add92b73d..40585e778 100755
--- a/smoketest/scripts/cli/test_protocols_ripng.py
+++ b/smoketest/scripts/cli/test_protocols_ripng.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.ifconfig import Section
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_protocols_rpki.py b/smoketest/scripts/cli/test_protocols_rpki.py
index 6d334a9f8..d9792ce8d 100755
--- a/smoketest/scripts/cli/test_protocols_rpki.py
+++ b/smoketest/scripts/cli/test_protocols_rpki.py
@@ -19,7 +19,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_protocols_static.py b/smoketest/scripts/cli/test_protocols_static.py
index 0d3228cc7..4c4eb5a7c 100755
--- a/smoketest/scripts/cli/test_protocols_static.py
+++ b/smoketest/scripts/cli/test_protocols_static.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.template import is_ipv6
from vyos.util import get_interface_config
diff --git a/smoketest/scripts/cli/test_service_bcast-relay.py b/smoketest/scripts/cli/test_service_bcast-relay.py
index 58b730ab4..87901869e 100755
--- a/smoketest/scripts/cli/test_service_bcast-relay.py
+++ b/smoketest/scripts/cli/test_service_bcast-relay.py
@@ -19,7 +19,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from psutil import process_iter
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
base_path = ['service', 'broadcast-relay']
diff --git a/smoketest/scripts/cli/test_service_dhcp-relay.py b/smoketest/scripts/cli/test_service_dhcp-relay.py
index db2edba54..bbfd9e032 100755
--- a/smoketest/scripts/cli/test_service_dhcp-relay.py
+++ b/smoketest/scripts/cli/test_service_dhcp-relay.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_service_dhcp-server.py b/smoketest/scripts/cli/test_service_dhcp-server.py
index 301f8fa31..14666db15 100755
--- a/smoketest/scripts/cli/test_service_dhcp-server.py
+++ b/smoketest/scripts/cli/test_service_dhcp-server.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import process_named_running
from vyos.util import read_file
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-relay.py b/smoketest/scripts/cli/test_service_dhcpv6-relay.py
index 5a9dd1aa6..fc206435b 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-relay.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-relay.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Section
from vyos.template import address_from_cidr
diff --git a/smoketest/scripts/cli/test_service_dhcpv6-server.py b/smoketest/scripts/cli/test_service_dhcpv6-server.py
index 3f9564e59..7177f1505 100755
--- a/smoketest/scripts/cli/test_service_dhcpv6-server.py
+++ b/smoketest/scripts/cli/test_service_dhcpv6-server.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.template import inc_ip
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_service_dns_dynamic.py b/smoketest/scripts/cli/test_service_dns_dynamic.py
index 134254186..90d10d40b 100755
--- a/smoketest/scripts/cli/test_service_dns_dynamic.py
+++ b/smoketest/scripts/cli/test_service_dns_dynamic.py
@@ -20,7 +20,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_service_dns_forwarding.py b/smoketest/scripts/cli/test_service_dns_forwarding.py
index 45ca618cb..5929f8cba 100755
--- a/smoketest/scripts/cli/test_service_dns_forwarding.py
+++ b/smoketest/scripts/cli/test_service_dns_forwarding.py
@@ -19,7 +19,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import read_file
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_service_https.py b/smoketest/scripts/cli/test_service_https.py
index 3af63636a..8e69efd9c 100755
--- a/smoketest/scripts/cli/test_service_https.py
+++ b/smoketest/scripts/cli/test_service_https.py
@@ -17,7 +17,6 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.util import run
base_path = ['service', 'https']
diff --git a/smoketest/scripts/cli/test_service_mdns-repeater.py b/smoketest/scripts/cli/test_service_mdns-repeater.py
index 8941f065c..f99a98da1 100755
--- a/smoketest/scripts/cli/test_service_mdns-repeater.py
+++ b/smoketest/scripts/cli/test_service_mdns-repeater.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.util import process_named_running
base_path = ['service', 'mdns', 'repeater']
diff --git a/smoketest/scripts/cli/test_service_router-advert.py b/smoketest/scripts/cli/test_service_router-advert.py
index 26b4626c2..4875fb5d1 100755
--- a/smoketest/scripts/cli/test_service_router-advert.py
+++ b/smoketest/scripts/cli/test_service_router-advert.py
@@ -19,7 +19,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.util import read_file
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_service_snmp.py b/smoketest/scripts/cli/test_service_snmp.py
index 008271102..058835c72 100755
--- a/smoketest/scripts/cli/test_service_snmp.py
+++ b/smoketest/scripts/cli/test_service_snmp.py
@@ -19,9 +19,9 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.template import is_ipv4
+from vyos.template import address_from_cidr
from vyos.util import read_file
from vyos.util import process_named_running
@@ -36,16 +36,29 @@ def get_config_value(key):
return tmp[0]
class TestSNMPService(VyOSUnitTestSHIM.TestCase):
- def setUp(self):
+ @classmethod
+ def setUpClass(cls):
+ super(cls, cls).setUpClass()
+
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ def tearDown(self):
+ # delete testing SNMP config
self.cli_delete(base_path)
+ self.cli_commit()
def test_snmp_basic(self):
+ dummy_if = 'dum7312'
+ dummy_addr = '100.64.0.1/32'
+ self.cli_set(['interfaces', 'dummy', dummy_if, 'address', dummy_addr])
+
# Check if SNMP can be configured and service runs
clients = ['192.0.2.1', '2001:db8::1']
networks = ['192.0.2.128/25', '2001:db8:babe::/48']
- listen = ['127.0.0.1', '::1']
+ listen = ['127.0.0.1', '::1', address_from_cidr(dummy_addr)]
+ port = '5000'
for auth in ['ro', 'rw']:
community = 'VyOS' + auth
@@ -56,7 +69,7 @@ class TestSNMPService(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['community', community, 'network', network])
for addr in listen:
- self.cli_set(base_path + ['listen-address', addr])
+ self.cli_set(base_path + ['listen-address', addr, 'port', port])
self.cli_set(base_path + ['contact', 'maintainers@vyos.io'])
self.cli_set(base_path + ['location', 'qemu'])
@@ -68,16 +81,18 @@ class TestSNMPService(VyOSUnitTestSHIM.TestCase):
# thus we need to transfor this into a proper list
config = get_config_value('agentaddress')
expected = 'unix:/run/snmpd.socket'
+ self.assertIn(expected, config)
+
for addr in listen:
if is_ipv4(addr):
- expected += ',udp:{}:161'.format(addr)
+ expected = f'udp:{addr}:{port}'
else:
- expected += ',udp6:[{}]:161'.format(addr)
-
- self.assertTrue(expected in config)
+ expected = f'udp6:[{addr}]:{port}'
+ self.assertIn(expected, config)
# Check for running process
self.assertTrue(process_named_running(PROCESS_NAME))
+ self.cli_delete(['interfaces', 'dummy', dummy_if])
def test_snmpv3_sha(self):
@@ -86,7 +101,7 @@ class TestSNMPService(VyOSUnitTestSHIM.TestCase):
self.cli_set(base_path + ['v3', 'engineid', '000000000000000000000002'])
self.cli_set(base_path + ['v3', 'group', 'default', 'mode', 'ro'])
- # check validate() - a view must be created before this can be comitted
+ # check validate() - a view must be created before this can be committed
with self.assertRaises(ConfigSessionError):
self.cli_commit()
@@ -152,4 +167,3 @@ class TestSNMPService(VyOSUnitTestSHIM.TestCase):
if __name__ == '__main__':
unittest.main(verbosity=2)
-
diff --git a/smoketest/scripts/cli/test_service_ssh.py b/smoketest/scripts/cli/test_service_ssh.py
index ded4d8301..a54c03919 100755
--- a/smoketest/scripts/cli/test_service_ssh.py
+++ b/smoketest/scripts/cli/test_service_ssh.py
@@ -20,7 +20,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_service_tftp-server.py b/smoketest/scripts/cli/test_service_tftp-server.py
index aed4c6beb..1a1bf0cdf 100755
--- a/smoketest/scripts/cli/test_service_tftp-server.py
+++ b/smoketest/scripts/cli/test_service_tftp-server.py
@@ -19,7 +19,6 @@ import unittest
from psutil import process_iter
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import read_file
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_service_webproxy.py b/smoketest/scripts/cli/test_service_webproxy.py
index 6780a93f9..8a1a03ce7 100755
--- a/smoketest/scripts/cli/test_service_webproxy.py
+++ b/smoketest/scripts/cli/test_service_webproxy.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import process_named_running
diff --git a/smoketest/scripts/cli/test_system_acceleration_qat.py b/smoketest/scripts/cli/test_system_acceleration_qat.py
index 9584888d6..9e60bb211 100755
--- a/smoketest/scripts/cli/test_system_acceleration_qat.py
+++ b/smoketest/scripts/cli/test_system_acceleration_qat.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
base_path = ['system', 'acceleration', 'qat']
diff --git a/smoketest/scripts/cli/test_system_conntrack.py b/smoketest/scripts/cli/test_system_conntrack.py
index a2380981b..b2934cf04 100755
--- a/smoketest/scripts/cli/test_system_conntrack.py
+++ b/smoketest/scripts/cli/test_system_conntrack.py
@@ -19,7 +19,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.util import cmd
from vyos.util import read_file
diff --git a/smoketest/scripts/cli/test_system_flow-accounting.py b/smoketest/scripts/cli/test_system_flow-accounting.py
new file mode 100755
index 000000000..a2b5b1481
--- /dev/null
+++ b/smoketest/scripts/cli/test_system_flow-accounting.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+import unittest
+
+from base_vyostest_shim import VyOSUnitTestSHIM
+
+from vyos.configsession import ConfigSessionError
+from vyos.ifconfig import Section
+from vyos.util import cmd
+from vyos.util import process_named_running
+from vyos.util import read_file
+
+PROCESS_NAME = 'uacctd'
+base_path = ['system', 'flow-accounting']
+
+uacctd_conf = '/etc/pmacct/uacctd.conf'
+
+class TestSystemFlowAccounting(VyOSUnitTestSHIM.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ super(cls, cls).setUpClass()
+
+ # ensure we can also run this test on a live system - so lets clean
+ # out the current configuration :)
+ cls.cli_delete(cls, base_path)
+
+ def tearDown(self):
+ self.cli_delete(base_path)
+ self.cli_commit()
+
+ # after service removal process must no longer run
+ self.assertFalse(process_named_running(PROCESS_NAME))
+
+ def test_basic(self):
+ buffer_size = '5' # MiB
+ self.cli_set(base_path + ['buffer-size', buffer_size])
+
+ # You need to configure at least one interface for flow-accounting
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+ for interface in Section.interfaces('ethernet'):
+ self.cli_set(base_path + ['interface', interface])
+
+ # commit changes
+ self.cli_commit()
+
+ # verify configuration
+ tmp = cmd('sudo iptables-save -t raw')
+ for interface in Section.interfaces('ethernet'):
+ self.assertIn(f'-A VYATTA_CT_PREROUTING_HOOK -i {interface} -m comment --comment FLOW_ACCOUNTING_RULE -j NFLOG --nflog-group 2 --nflog-size 128 --nflog-threshold 100', tmp)
+
+ uacctd = read_file(uacctd_conf)
+ # circular queue size - buffer_size
+ tmp = int(buffer_size) *1024 *1024
+ self.assertIn(f'plugin_pipe_size: {tmp}', uacctd)
+ # transfer buffer size - recommended value from pmacct developers 1/1000 of pipe size
+ tmp = int(buffer_size) *1024 *1024
+ # do an integer division
+ tmp //= 1000
+ self.assertIn(f'plugin_buffer_size: {tmp}', uacctd)
+
+ # Check for running process
+ self.assertTrue(process_named_running(PROCESS_NAME))
+
+if __name__ == '__main__':
+ unittest.main(verbosity=2)
diff --git a/smoketest/scripts/cli/test_system_ip.py b/smoketest/scripts/cli/test_system_ip.py
index e98a4e234..83df9d99e 100755
--- a/smoketest/scripts/cli/test_system_ip.py
+++ b/smoketest/scripts/cli/test_system_ip.py
@@ -17,7 +17,6 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.util import read_file
base_path = ['system', 'ip']
diff --git a/smoketest/scripts/cli/test_system_ipv6.py b/smoketest/scripts/cli/test_system_ipv6.py
index c9c9e833d..1325d4b39 100755
--- a/smoketest/scripts/cli/test_system_ipv6.py
+++ b/smoketest/scripts/cli/test_system_ipv6.py
@@ -17,7 +17,6 @@
import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.util import read_file
base_path = ['system', 'ipv6']
diff --git a/smoketest/scripts/cli/test_system_lcd.py b/smoketest/scripts/cli/test_system_lcd.py
index 7a39e2986..831fba979 100755
--- a/smoketest/scripts/cli/test_system_lcd.py
+++ b/smoketest/scripts/cli/test_system_lcd.py
@@ -19,7 +19,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
from configparser import ConfigParser
-from vyos.configsession import ConfigSession
from vyos.util import process_named_running
config_file = '/run/LCDd/LCDd.conf'
diff --git a/smoketest/scripts/cli/test_system_login.py b/smoketest/scripts/cli/test_system_login.py
index 0addd630e..69a06eeac 100755
--- a/smoketest/scripts/cli/test_system_login.py
+++ b/smoketest/scripts/cli/test_system_login.py
@@ -24,7 +24,6 @@ from distutils.version import LooseVersion
from platform import release as kernel_version
from subprocess import Popen, PIPE
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import cmd
from vyos.util import read_file
diff --git a/smoketest/scripts/cli/test_system_nameserver.py b/smoketest/scripts/cli/test_system_nameserver.py
index 50dc466c2..58c84988e 100755
--- a/smoketest/scripts/cli/test_system_nameserver.py
+++ b/smoketest/scripts/cli/test_system_nameserver.py
@@ -19,7 +19,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.util import read_file
diff --git a/smoketest/scripts/cli/test_system_ntp.py b/smoketest/scripts/cli/test_system_ntp.py
index 2b86ebd7c..e8cc64463 100755
--- a/smoketest/scripts/cli/test_system_ntp.py
+++ b/smoketest/scripts/cli/test_system_ntp.py
@@ -19,7 +19,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.template import address_from_cidr
from vyos.template import netmask_from_cidr
diff --git a/smoketest/scripts/cli/test_vpn_ipsec.py b/smoketest/scripts/cli/test_vpn_ipsec.py
index 93569c4ec..c710aec6e 100755
--- a/smoketest/scripts/cli/test_vpn_ipsec.py
+++ b/smoketest/scripts/cli/test_vpn_ipsec.py
@@ -307,7 +307,7 @@ class TestVPNIPsec(VyOSUnitTestSHIM.TestCase):
swanctl_lines = [
f'proposals = aes128-sha1-modp1024,aes256-sha1-modp1024',
f'version = 1',
- f'life_time = {ike_lifetime}s',
+ f'rekey_time = {ike_lifetime}s',
f'rekey_time = {esp_lifetime}s',
f'esp_proposals = aes128-sha1-modp1024,aes256-sha1-modp1024,3des-md5-modp1024',
f'local_ts = dynamic[gre]',
diff --git a/smoketest/scripts/cli/test_vpn_openconnect.py b/smoketest/scripts/cli/test_vpn_openconnect.py
index cad3b1182..b0e859b5c 100755
--- a/smoketest/scripts/cli/test_vpn_openconnect.py
+++ b/smoketest/scripts/cli/test_vpn_openconnect.py
@@ -18,7 +18,6 @@ import unittest
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.util import process_named_running
OCSERV_CONF = '/run/ocserv/ocserv.conf'
diff --git a/smoketest/scripts/cli/test_vrf.py b/smoketest/scripts/cli/test_vrf.py
index f36d16344..5ffa9c086 100755
--- a/smoketest/scripts/cli/test_vrf.py
+++ b/smoketest/scripts/cli/test_vrf.py
@@ -22,7 +22,6 @@ import unittest
from netifaces import interfaces
from base_vyostest_shim import VyOSUnitTestSHIM
-from vyos.configsession import ConfigSession
from vyos.configsession import ConfigSessionError
from vyos.ifconfig import Interface
from vyos.ifconfig import Section
@@ -58,7 +57,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
for vrf in vrfs:
self.assertNotIn(vrf, interfaces())
- def test_vrf_table_id(self):
+ def test_vrf_vni_and_table_id(self):
table = '1000'
for vrf in vrfs:
base = base_path + ['name', vrf]
@@ -70,6 +69,7 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
self.cli_commit()
self.cli_set(base + ['table', table])
+ self.cli_set(base + ['vni', table])
if vrf == 'green':
self.cli_set(base + ['disable'])
@@ -101,6 +101,11 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
# ...
regex = f'{table}\s+{vrf}\s+#\s+{description}'
self.assertTrue(re.findall(regex, iproute2_config))
+
+ frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ self.assertIn(f' vni {table}', frrconfig)
+
+ # Increment table ID for the next run
table = str(int(table) + 1)
def test_vrf_loopback_ips(self):
@@ -178,5 +183,42 @@ class VRFTest(VyOSUnitTestSHIM.TestCase):
section = Section.section(interface)
self.cli_delete(['interfaces', section, interface, 'vrf'])
+ def test_vrf_static_route(self):
+ table = '100'
+ for vrf in vrfs:
+ next_hop = f'192.0.{table}.1'
+ prefix = f'10.0.{table}.0/24'
+ base = base_path + ['name', vrf]
+
+ self.cli_set(base + ['vni', table])
+
+ # check validate() - a table ID is mandatory
+ with self.assertRaises(ConfigSessionError):
+ self.cli_commit()
+
+ self.cli_set(base + ['table', table])
+ self.cli_set(base + ['protocols', 'static', 'route', prefix, 'next-hop', next_hop])
+
+ table = str(int(table) + 1)
+
+ # commit changes
+ self.cli_commit()
+
+ # Verify VRF configuration
+ table = '100'
+ for vrf in vrfs:
+ next_hop = f'192.0.{table}.1'
+ prefix = f'10.0.{table}.0/24'
+
+ self.assertTrue(vrf in interfaces())
+ vrf_if = Interface(vrf)
+
+ frrconfig = self.getFRRconfig(f'vrf {vrf}')
+ self.assertIn(f' vni {table}', frrconfig)
+ self.assertIn(f' ip route {prefix} {next_hop}', frrconfig)
+
+ # Increment table ID for the next run
+ table = str(int(table) + 1)
+
if __name__ == '__main__':
unittest.main(verbosity=2)
diff --git a/src/conf_mode/interfaces-openvpn.py b/src/conf_mode/interfaces-openvpn.py
index 1e76147dd..3b8fae710 100755
--- a/src/conf_mode/interfaces-openvpn.py
+++ b/src/conf_mode/interfaces-openvpn.py
@@ -634,10 +634,10 @@ def generate(openvpn):
def apply(openvpn):
interface = openvpn['ifname']
- call(f'systemctl stop openvpn@{interface}.service')
# Do some cleanup when OpenVPN is disabled/deleted
if 'deleted' in openvpn or 'disable' in openvpn:
+ call(f'systemctl stop openvpn@{interface}.service')
for cleanup_file in glob(f'/run/openvpn/{interface}.*'):
if os.path.isfile(cleanup_file):
os.unlink(cleanup_file)
@@ -649,7 +649,7 @@ def apply(openvpn):
# No matching OpenVPN process running - maybe it got killed or none
# existed - nevertheless, spawn new OpenVPN process
- call(f'systemctl start openvpn@{interface}.service')
+ call(f'systemctl reload-or-restart openvpn@{interface}.service')
o = VTunIf(**openvpn)
o.update(openvpn)
diff --git a/src/conf_mode/interfaces-wwan.py b/src/conf_mode/interfaces-wwan.py
index faa5eb628..f013e5411 100755
--- a/src/conf_mode/interfaces-wwan.py
+++ b/src/conf_mode/interfaces-wwan.py
@@ -25,7 +25,9 @@ from vyos.configverify import verify_interface_exists
from vyos.configverify import verify_vrf
from vyos.ifconfig import WWANIf
from vyos.util import cmd
+from vyos.util import call
from vyos.util import dict_search
+from vyos.util import DEVNULL
from vyos import ConfigError
from vyos import airbag
airbag.enable()
@@ -88,7 +90,7 @@ def apply(wwan):
options += ',user={user},password={password}'.format(**wwan['authentication'])
command = f'{base_cmd} --simple-connect="{options}"'
- cmd(command)
+ call(command, stdout=DEVNULL)
w.update(wwan)
return None
diff --git a/src/conf_mode/snmp.py b/src/conf_mode/snmp.py
index 2a420b193..e1852f2ce 100755
--- a/src/conf_mode/snmp.py
+++ b/src/conf_mode/snmp.py
@@ -20,13 +20,17 @@ from sys import exit
from vyos.config import Config
from vyos.configverify import verify_vrf
-from vyos.snmpv3_hashgen import plaintext_to_md5, plaintext_to_sha1, random
+from vyos.snmpv3_hashgen import plaintext_to_md5
+from vyos.snmpv3_hashgen import plaintext_to_sha1
+from vyos.snmpv3_hashgen import random
from vyos.template import render
from vyos.template import is_ipv4
-from vyos.util import call, chmod_755
+from vyos.util import call
+from vyos.util import chmod_755
from vyos.validate import is_addr_assigned
from vyos.version import get_version_data
-from vyos import ConfigError, airbag
+from vyos import ConfigError
+from vyos import airbag
airbag.enable()
config_file_client = r'/etc/snmp/snmp.conf'
@@ -410,19 +414,20 @@ def verify(snmp):
port = listen[1]
protocol = snmp['protocol']
+ tmp = None
if is_ipv4(addr):
# example: udp:127.0.0.1:161
- listen = f'{protocol}:{addr}:{port}'
+ tmp = f'{protocol}:{addr}:{port}'
elif snmp['ipv6_enabled']:
# example: udp6:[::1]:161
- listen = f'{protocol}6:[{addr}]:{port}'
+ tmp = f'{protocol}6:[{addr}]:{port}'
# We only wan't to configure addresses that exist on the system.
# Hint the user if they don't exist
if is_addr_assigned(addr):
- snmp['listen_on'].append(listen)
+ if tmp: snmp['listen_on'].append(tmp)
else:
- print('WARNING: SNMP listen address {0} not configured!'.format(addr))
+ print(f'WARNING: SNMP listen address {addr} not configured!')
verify_vrf(snmp)
diff --git a/src/conf_mode/system-login-banner.py b/src/conf_mode/system-login-banner.py
index e9d6a339c..2220d7b66 100755
--- a/src/conf_mode/system-login-banner.py
+++ b/src/conf_mode/system-login-banner.py
@@ -37,7 +37,7 @@ PRELOGIN_NET_FILE = r'/etc/issue.net'
POSTLOGIN_FILE = r'/etc/motd'
default_config_data = {
- 'issue': 'Welcome to VyOS - \\n \\l\n',
+ 'issue': 'Welcome to VyOS - \\n \\l\n\n',
'issue_net': 'Welcome to VyOS\n',
'motd': motd
}
diff --git a/src/conf_mode/vrf.py b/src/conf_mode/vrf.py
index 919083ac4..38c0c4463 100755
--- a/src/conf_mode/vrf.py
+++ b/src/conf_mode/vrf.py
@@ -18,7 +18,6 @@ import os
from sys import exit
from json import loads
-from tempfile import NamedTemporaryFile
from vyos.config import Config
from vyos.configdict import node_changed
@@ -31,10 +30,12 @@ from vyos.util import get_interface_config
from vyos.util import popen
from vyos.util import run
from vyos import ConfigError
+from vyos import frr
from vyos import airbag
airbag.enable()
-config_file = r'/etc/iproute2/rt_tables.d/vyos-vrf.conf'
+config_file = '/etc/iproute2/rt_tables.d/vyos-vrf.conf'
+nft_vrf_config = '/tmp/nftables-vrf-zones'
def list_rules():
command = 'ip -j -4 rule show'
@@ -128,8 +129,8 @@ def verify(vrf):
def generate(vrf):
render(config_file, 'vrf/vrf.conf.tmpl', vrf)
# Render nftables zones config
- vrf['nft_vrf_zones'] = NamedTemporaryFile().name
- render(vrf['nft_vrf_zones'], 'firewall/nftables-vrf-zones.tmpl', vrf)
+
+ render(nft_vrf_config, 'firewall/nftables-vrf-zones.tmpl', vrf)
return None
@@ -165,8 +166,9 @@ def apply(vrf):
_, err = popen('nft list table inet vrf_zones')
# If not, create a table
if err:
- cmd(f'nft -f {vrf["nft_vrf_zones"]}')
- os.unlink(vrf['nft_vrf_zones'])
+ if os.path.exists(nft_vrf_config):
+ cmd(f'nft -f {nft_vrf_config}')
+ os.unlink(nft_vrf_config)
for name, config in vrf['name'].items():
table = config['table']
diff --git a/src/conf_mode/vrf_vni.py b/src/conf_mode/vrf_vni.py
index 87ee8f2d1..50d60f0dc 100755
--- a/src/conf_mode/vrf_vni.py
+++ b/src/conf_mode/vrf_vni.py
@@ -32,32 +32,23 @@ def get_config(config=None):
else:
conf = Config()
- # This script only works with a passed VRF name
- if len(argv) < 1:
- raise NotImplementedError
- vrf = argv[1]
+ base = ['vrf']
+ vrf = conf.get_config_dict(base, get_first_key=True)
+ return vrf
- # "assemble" dict - easier here then use a full blown get_config_dict()
- # on a single leafNode
- vni = { 'vrf' : vrf }
- tmp = conf.return_value(['vrf', 'name', vrf, 'vni'])
- if tmp: vni.update({ 'vni' : tmp })
-
- return vni
-
-def verify(vni):
+def verify(vrf):
return None
-def generate(vni):
- vni['new_frr_config'] = render_to_string('frr/vrf-vni.frr.tmpl', vni)
+def generate(vrf):
+ vrf['new_frr_config'] = render_to_string('frr/vrf-vni.frr.tmpl', vrf)
return None
-def apply(vni):
+def apply(vrf):
# add configuration to FRR
frr_cfg = frr.FRRConfig()
frr_cfg.load_configuration(frr_daemon)
- frr_cfg.modify_section(f'^vrf [a-zA-Z-]*$', '')
- frr_cfg.add_before(r'(interface .*|line vty)', vni['new_frr_config'])
+ frr_cfg.modify_section(f'^vrf .+$', '')
+ frr_cfg.add_before(r'(interface .*|line vty)', vrf['new_frr_config'])
frr_cfg.commit_configuration(frr_daemon)
# Save configuration to /run/frr/config/frr.conf
diff --git a/src/etc/cron.d/check-wwan b/src/etc/cron.d/check-wwan
new file mode 100644
index 000000000..28190776f
--- /dev/null
+++ b/src/etc/cron.d/check-wwan
@@ -0,0 +1 @@
+*/5 * * * * root /usr/libexec/vyos/vyos-check-wwan.py
diff --git a/src/etc/systemd/system/openvpn@.service.d/10-override.conf b/src/etc/systemd/system/openvpn@.service.d/10-override.conf
index 03fe6b587..775a2d7ba 100644
--- a/src/etc/systemd/system/openvpn@.service.d/10-override.conf
+++ b/src/etc/systemd/system/openvpn@.service.d/10-override.conf
@@ -7,6 +7,7 @@ WorkingDirectory=
WorkingDirectory=/run/openvpn
ExecStart=
ExecStart=/usr/sbin/openvpn --daemon openvpn-%i --config %i.conf --status %i.status 30 --writepid %i.pid
+ExecReload=/bin/kill -HUP $MAINPID
User=openvpn
Group=openvpn
AmbientCapabilities=CAP_IPC_LOCK CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_RAW CAP_SETGID CAP_SETUID CAP_SYS_CHROOT CAP_DAC_OVERRIDE CAP_AUDIT_WRITE
diff --git a/src/helpers/vyos-check-wwan.py b/src/helpers/vyos-check-wwan.py
new file mode 100755
index 000000000..c6e6c54b7
--- /dev/null
+++ b/src/helpers/vyos-check-wwan.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+from vyos.configquery import VbashOpRun
+from vyos.configquery import ConfigTreeQuery
+
+from vyos.util import is_wwan_connected
+from vyos.util import call
+
+conf = ConfigTreeQuery()
+dict = conf.get_config_dict(['interfaces', 'wwan'], key_mangling=('-', '_'),
+ get_first_key=True)
+
+for interface, interface_config in dict.items():
+ if not is_wwan_connected(interface):
+ if 'disable' in interface_config:
+ # do not restart this interface as it's disabled by the user
+ continue
+
+ #op = VbashOpRun()
+ #op.run(['connect', 'interface', interface])
+ call(f'VYOS_TAGNODE_VALUE={interface} /usr/libexec/vyos/conf_mode/interfaces-wwan.py')
+
+exit(0)
diff --git a/src/helpers/vyos_net_name b/src/helpers/vyos_net_name
index 13fb9e31f..e21d8c9ff 100755
--- a/src/helpers/vyos_net_name
+++ b/src/helpers/vyos_net_name
@@ -144,7 +144,19 @@ def get_configfile_interfaces() -> dict:
logging.critical(f"OSError {e}")
exit(1)
- config = ConfigTree(config_file)
+ try:
+ config = ConfigTree(config_file)
+ except Exception:
+ logging.debug(f"updating component version string syntax")
+ try:
+ # this will update the component version string in place, for
+ # updates 1.2 --> 1.3/1.4
+ os.system(f'/usr/libexec/vyos/run-config-migration.py {config_path} --virtual --set-vintage=vyos')
+ with open(config_path) as f:
+ config_file = f.read()
+ config = ConfigTree(config_file)
+ except Exception as e:
+ logging.critical(f"ConfigTree error: {e}")
base = ['interfaces', 'ethernet']
if config.exists(base):
diff --git a/src/op_mode/connect_disconnect.py b/src/op_mode/connect_disconnect.py
index a773aa28e..ffc574362 100755
--- a/src/op_mode/connect_disconnect.py
+++ b/src/op_mode/connect_disconnect.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
#
-# Copyright (C) 2020 VyOS maintainers and contributors
+# Copyright (C) 2020-2021 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
@@ -17,21 +17,19 @@
import os
import argparse
-from sys import exit
from psutil import process_iter
-from time import strftime, localtime, time
from vyos.util import call
+from vyos.util import DEVNULL
+from vyos.util import is_wwan_connected
-def check_interface(interface):
+def check_ppp_interface(interface):
if not os.path.isfile(f'/etc/ppp/peers/{interface}'):
- print(f'Interface {interface}: invalid!')
+ print(f'Interface {interface} does not exist!')
exit(1)
def check_ppp_running(interface):
- """
- Check if ppp process is running in the interface in question
- """
+ """ Check if PPP process is running in the interface in question """
for p in process_iter():
if "pppd" in p.name():
if interface in p.cmdline():
@@ -40,32 +38,46 @@ def check_ppp_running(interface):
return False
def connect(interface):
- """
- Connect PPP interface
- """
- check_interface(interface)
+ """ Connect dialer interface """
- # Check if interface is already dialed
- if os.path.isdir(f'/sys/class/net/{interface}'):
- print(f'Interface {interface}: already connected!')
- elif check_ppp_running(interface):
- print(f'Interface {interface}: connection is beeing established!')
+ if interface.startswith('ppp'):
+ check_ppp_interface(interface)
+ # Check if interface is already dialed
+ if os.path.isdir(f'/sys/class/net/{interface}'):
+ print(f'Interface {interface}: already connected!')
+ elif check_ppp_running(interface):
+ print(f'Interface {interface}: connection is beeing established!')
+ else:
+ print(f'Interface {interface}: connecting...')
+ call(f'systemctl restart ppp@{interface}.service')
+ elif interface.startswith('wwan'):
+ if is_wwan_connected(interface):
+ print(f'Interface {interface}: already connected!')
+ else:
+ call(f'VYOS_TAGNODE_VALUE={interface} /usr/libexec/vyos/conf_mode/interfaces-wwan.py')
else:
- print(f'Interface {interface}: connecting...')
- call(f'systemctl restart ppp@{interface}.service')
+ print(f'Unknown interface {interface}, can not connect. Aborting!')
def disconnect(interface):
- """
- Disconnect PPP interface
- """
- check_interface(interface)
+ """ Disconnect dialer interface """
- # Check if interface is already down
- if not check_ppp_running(interface):
- print(f'Interface {interface}: connection is already down')
+ if interface.startswith('ppp'):
+ check_ppp_interface(interface)
+
+ # Check if interface is already down
+ if not check_ppp_running(interface):
+ print(f'Interface {interface}: connection is already down')
+ else:
+ print(f'Interface {interface}: disconnecting...')
+ call(f'systemctl stop ppp@{interface}.service')
+ elif interface.startswith('wwan'):
+ if not is_wwan_connected(interface):
+ print(f'Interface {interface}: connection is already down')
+ else:
+ modem = interface.lstrip('wwan')
+ call(f'mmcli --modem {modem} --simple-disconnect', stdout=DEVNULL)
else:
- print(f'Interface {interface}: disconnecting...')
- call(f'systemctl stop ppp@{interface}.service')
+ print(f'Unknown interface {interface}, can not disconnect. Aborting!')
def main():
parser = argparse.ArgumentParser()
diff --git a/src/op_mode/show_interfaces.py b/src/op_mode/show_interfaces.py
index 3d50eb938..eac068274 100755
--- a/src/op_mode/show_interfaces.py
+++ b/src/op_mode/show_interfaces.py
@@ -94,10 +94,8 @@ def split_text(text, used=0):
used: number of characted already used in the screen
"""
no_tty = call('tty -s')
- if no_tty:
- return text.split()
- returned = cmd('stty size')
+ returned = cmd('stty size') if not no_tty else ''
if len(returned) == 2:
rows, columns = [int(_) for _ in returned]
else:
diff --git a/src/services/api/graphql/README.graphql b/src/services/api/graphql/README.graphql
index 580c0eb7f..29f58f709 100644
--- a/src/services/api/graphql/README.graphql
+++ b/src/services/api/graphql/README.graphql
@@ -10,7 +10,7 @@ to run with that address as default router by requesting these 'mutations'
in the GraphQL playground:
mutation {
- createInterfaceEthernet (data: {interface: "eth1",
+ CreateInterfaceEthernet (data: {interface: "eth1",
address: "192.168.0.1/24",
description: "BOB"}) {
success
@@ -22,7 +22,7 @@ mutation {
}
mutation {
- createDhcpServer(data: {sharedNetworkName: "BOB",
+ CreateDhcpServer(data: {sharedNetworkName: "BOB",
subnet: "192.168.0.0/24",
defaultRouter: "192.168.0.1",
nameServer: "192.168.0.1",
@@ -42,6 +42,38 @@ mutation {
}
}
+To save the configuration, use the following mutation:
+
+mutation {
+ SaveConfigFile(data: {fileName: "/config/config.boot"}) {
+ success
+ errors
+ data {
+ fileName
+ }
+ }
+}
+
+N.B. fileName can be empty (fileName: "") or data can be empty (data: {}) to
+save to /config/config.boot; to save to an alternative path, specify
+fileName.
+
+Similarly, using the same 'endpoint' (meaning the form of the request and
+resolver; the actual enpoint for all GraphQL requests is
+https://hostname/graphql), one can load an arbitrary config file from a
+path.
+
+mutation {
+ LoadConfigFile(data: {fileName: "/home/vyos/config.boot"}) {
+ success
+ errors
+ data {
+ fileName
+ }
+ }
+}
+
+
The GraphQL playground will be found at:
https://{{ host_address }}/graphql
@@ -57,22 +89,23 @@ What's here:
services
├── api
│   └── graphql
+│   ├── bindings.py
│   ├── graphql
│   │   ├── directives.py
│   │   ├── __init__.py
│   │   ├── mutations.py
│   │   └── schema
+│   │   ├── config_file.graphql
│   │   ├── dhcp_server.graphql
│   │   ├── interface_ethernet.graphql
│   │   └── schema.graphql
+│   ├── README.graphql
│   ├── recipes
-│   │   ├── dhcp_server.py
│   │   ├── __init__.py
-│   │   ├── interface_ethernet.py
-│   │   ├── recipe.py
+│   │   ├── session.py
│   │   └── templates
-│   │   ├── dhcp_server.tmpl
-│   │   └── interface_ethernet.tmpl
+│   │   ├── create_dhcp_server.tmpl
+│   │   └── create_interface_ethernet.tmpl
│   └── state.py
├── vyos-configd
├── vyos-hostsd
@@ -90,13 +123,14 @@ the Ur-data; the GraphQL schema is produced from those files, located in
Resolvers for the schema Mutation fields are dynamically generated using a
'directive' added to the respective schema field. The directive,
-'@generate', is handled by the class 'DataDirective' in
-'api/graphql/graphql/directives.py', which calls the 'make_resolver' function in
-'api/graphql/graphql/mutations.py'; the produced resolver calls the appropriate
-wrapper in 'api/graphql/recipes', with base class doing the (overridable)
-configuration steps of calling all defined 'set'/'delete' commands.
-
-Integrating the above with vyos-http-api-server is ~10 lines of code.
+'@configure', is handled by the class 'ConfigureDirective' in
+'api/graphql/graphql/directives.py', which calls the
+'make_configure_resolver' function in 'api/graphql/graphql/mutations.py';
+the produced resolver calls the appropriate wrapper in
+'api/graphql/recipes', with base class doing the (overridable) configuration
+steps of calling all defined 'set'/'delete' commands.
+
+Integrating the above with vyos-http-api-server is 4 lines of code.
What needs to be done:
diff --git a/src/services/api/graphql/bindings.py b/src/services/api/graphql/bindings.py
new file mode 100644
index 000000000..1fbe13d0c
--- /dev/null
+++ b/src/services/api/graphql/bindings.py
@@ -0,0 +1,13 @@
+import vyos.defaults
+from . graphql.mutations import mutation
+from . graphql.directives import directives_dict
+from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers
+
+def generate_schema():
+ api_schema_dir = vyos.defaults.directories['api_schema']
+
+ type_defs = load_schema_from_path(api_schema_dir)
+
+ schema = make_executable_schema(type_defs, mutation, snake_case_fallback_resolvers, directives=directives_dict)
+
+ return schema
diff --git a/src/services/api/graphql/graphql/directives.py b/src/services/api/graphql/graphql/directives.py
index 651421c35..f5cd88acd 100644
--- a/src/services/api/graphql/graphql/directives.py
+++ b/src/services/api/graphql/graphql/directives.py
@@ -1,12 +1,11 @@
from ariadne import SchemaDirectiveVisitor, ObjectType
-from . mutations import make_resolver
+from . mutations import make_configure_resolver, make_config_file_resolver
-class DataDirective(SchemaDirectiveVisitor):
- """
- Class providing implementation of 'generate' directive in schema.
+def non(arg):
+ pass
- """
- def visit_field_definition(self, field, object_type):
+class VyosDirective(SchemaDirectiveVisitor):
+ def visit_field_definition(self, field, object_type, make_resolver=non):
name = f'{field.type}'
# field.type contains the return value of the mutation; trim value
# to produce canonical name
@@ -15,3 +14,24 @@ class DataDirective(SchemaDirectiveVisitor):
func = make_resolver(name)
field.resolve = func
return field
+
+
+class ConfigureDirective(VyosDirective):
+ """
+ Class providing implementation of 'configure' directive in schema.
+
+ """
+ def visit_field_definition(self, field, object_type):
+ super().visit_field_definition(field, object_type,
+ make_resolver=make_configure_resolver)
+
+class ConfigFileDirective(VyosDirective):
+ """
+ Class providing implementation of 'configfile' directive in schema.
+
+ """
+ def visit_field_definition(self, field, object_type):
+ super().visit_field_definition(field, object_type,
+ make_resolver=make_config_file_resolver)
+
+directives_dict = {"configure": ConfigureDirective, "configfile": ConfigFileDirective}
diff --git a/src/services/api/graphql/graphql/mutations.py b/src/services/api/graphql/graphql/mutations.py
index 98c665c9a..8a28b13d7 100644
--- a/src/services/api/graphql/graphql/mutations.py
+++ b/src/services/api/graphql/graphql/mutations.py
@@ -6,10 +6,11 @@ from graphql import GraphQLResolveInfo
from makefun import with_signature
from .. import state
+from api.graphql.recipes.session import Session
mutation = ObjectType("Mutation")
-def make_resolver(mutation_name):
+def make_resolver(mutation_name, class_name, session_func):
"""Dynamically generate a resolver for the mutation named in the
schema by 'mutation_name'.
@@ -19,11 +20,11 @@ def make_resolver(mutation_name):
functools.wraps.
:raise Exception:
- encapsulating ConfigErrors, or internal errors
+ raising ConfigErrors, or internal errors
"""
- class_name = mutation_name.replace('create', '', 1).replace('delete', '', 1)
+
func_base_name = convert_camel_case_to_snake(class_name)
- resolver_name = f'resolve_create_{func_base_name}'
+ resolver_name = f'resolve_{func_base_name}'
func_sig = '(obj: Any, info: GraphQLResolveInfo, data: Dict)'
@mutation.field(mutation_name)
@@ -40,10 +41,17 @@ def make_resolver(mutation_name):
data = kwargs['data']
session = state.settings['app'].state.vyos_session
- mod = import_module(f'api.graphql.recipes.{func_base_name}')
- klass = getattr(mod, class_name)
+ # one may override the session functions with a local subclass
+ try:
+ mod = import_module(f'api.graphql.recipes.{func_base_name}')
+ klass = getattr(mod, class_name)
+ except ImportError:
+ # otherwise, dynamically generate subclass to invoke subclass
+ # name based templates
+ klass = type(class_name, (Session,), {})
k = klass(session, data)
- k.configure()
+ method = getattr(k, session_func)
+ method()
return {
"success": True,
@@ -57,4 +65,16 @@ def make_resolver(mutation_name):
return func_impl
+def make_configure_resolver(mutation_name):
+ class_name = mutation_name
+ return make_resolver(mutation_name, class_name, 'configure')
+def make_config_file_resolver(mutation_name):
+ if 'Save' in mutation_name:
+ class_name = mutation_name.replace('Save', '', 1)
+ return make_resolver(mutation_name, class_name, 'save')
+ elif 'Load' in mutation_name:
+ class_name = mutation_name.replace('Load', '', 1)
+ return make_resolver(mutation_name, class_name, 'load')
+ else:
+ raise Exception
diff --git a/src/services/api/graphql/graphql/schema/config_file.graphql b/src/services/api/graphql/graphql/schema/config_file.graphql
new file mode 100644
index 000000000..31ab26b9e
--- /dev/null
+++ b/src/services/api/graphql/graphql/schema/config_file.graphql
@@ -0,0 +1,27 @@
+input SaveConfigFileInput {
+ fileName: String
+}
+
+type SaveConfigFile {
+ fileName: String
+}
+
+type SaveConfigFileResult {
+ data: SaveConfigFile
+ success: Boolean!
+ errors: [String]
+}
+
+input LoadConfigFileInput {
+ fileName: String!
+}
+
+type LoadConfigFile {
+ fileName: String!
+}
+
+type LoadConfigFileResult {
+ data: LoadConfigFile
+ success: Boolean!
+ errors: [String]
+}
diff --git a/src/services/api/graphql/graphql/schema/dhcp_server.graphql b/src/services/api/graphql/graphql/schema/dhcp_server.graphql
index 9f741a0a5..25f091bfa 100644
--- a/src/services/api/graphql/graphql/schema/dhcp_server.graphql
+++ b/src/services/api/graphql/graphql/schema/dhcp_server.graphql
@@ -1,4 +1,4 @@
-input dhcpServerConfigInput {
+input DhcpServerConfigInput {
sharedNetworkName: String
subnet: String
defaultRouter: String
@@ -13,7 +13,7 @@ input dhcpServerConfigInput {
dnsForwardingListenAddress: String
}
-type dhcpServerConfig {
+type DhcpServerConfig {
sharedNetworkName: String
subnet: String
defaultRouter: String
@@ -28,8 +28,8 @@ type dhcpServerConfig {
dnsForwardingListenAddress: String
}
-type createDhcpServerResult {
- data: dhcpServerConfig
+type CreateDhcpServerResult {
+ data: DhcpServerConfig
success: Boolean!
errors: [String]
}
diff --git a/src/services/api/graphql/graphql/schema/firewall_group.graphql b/src/services/api/graphql/graphql/schema/firewall_group.graphql
new file mode 100644
index 000000000..efe7de632
--- /dev/null
+++ b/src/services/api/graphql/graphql/schema/firewall_group.graphql
@@ -0,0 +1,47 @@
+input CreateFirewallAddressGroupInput {
+ name: String!
+ address: [String]
+}
+
+type CreateFirewallAddressGroup {
+ name: String!
+ address: [String]
+}
+
+type CreateFirewallAddressGroupResult {
+ data: CreateFirewallAddressGroup
+ success: Boolean!
+ errors: [String]
+}
+
+input UpdateFirewallAddressGroupMembersInput {
+ name: String!
+ address: [String!]!
+}
+
+type UpdateFirewallAddressGroupMembers {
+ name: String!
+ address: [String!]!
+}
+
+type UpdateFirewallAddressGroupMembersResult {
+ data: UpdateFirewallAddressGroupMembers
+ success: Boolean!
+ errors: [String]
+}
+
+input RemoveFirewallAddressGroupMembersInput {
+ name: String!
+ address: [String!]!
+}
+
+type RemoveFirewallAddressGroupMembers {
+ name: String!
+ address: [String!]!
+}
+
+type RemoveFirewallAddressGroupMembersResult {
+ data: RemoveFirewallAddressGroupMembers
+ success: Boolean!
+ errors: [String]
+}
diff --git a/src/services/api/graphql/graphql/schema/interface_ethernet.graphql b/src/services/api/graphql/graphql/schema/interface_ethernet.graphql
index fdcf97bad..32438b315 100644
--- a/src/services/api/graphql/graphql/schema/interface_ethernet.graphql
+++ b/src/services/api/graphql/graphql/schema/interface_ethernet.graphql
@@ -1,18 +1,18 @@
-input interfaceEthernetConfigInput {
+input InterfaceEthernetConfigInput {
interface: String
address: String
replace: Boolean = true
description: String
}
-type interfaceEthernetConfig {
+type InterfaceEthernetConfig {
interface: String
address: String
description: String
}
-type createInterfaceEthernetResult {
- data: interfaceEthernetConfig
+type CreateInterfaceEthernetResult {
+ data: InterfaceEthernetConfig
success: Boolean!
errors: [String]
}
diff --git a/src/services/api/graphql/graphql/schema/schema.graphql b/src/services/api/graphql/graphql/schema/schema.graphql
index 8a5e17962..9e97a0d60 100644
--- a/src/services/api/graphql/graphql/schema/schema.graphql
+++ b/src/services/api/graphql/graphql/schema/schema.graphql
@@ -7,9 +7,15 @@ type Query {
_dummy: String
}
-directive @generate on FIELD_DEFINITION
+directive @configure on FIELD_DEFINITION
+directive @configfile on FIELD_DEFINITION
type Mutation {
- createDhcpServer(data: dhcpServerConfigInput) : createDhcpServerResult @generate
- createInterfaceEthernet(data: interfaceEthernetConfigInput) : createInterfaceEthernetResult @generate
+ CreateDhcpServer(data: DhcpServerConfigInput) : CreateDhcpServerResult @configure
+ CreateInterfaceEthernet(data: InterfaceEthernetConfigInput) : CreateInterfaceEthernetResult @configure
+ CreateFirewallAddressGroup(data: CreateFirewallAddressGroupInput) : CreateFirewallAddressGroupResult @configure
+ UpdateFirewallAddressGroupMembers(data: UpdateFirewallAddressGroupMembersInput) : UpdateFirewallAddressGroupMembersResult @configure
+ RemoveFirewallAddressGroupMembers(data: RemoveFirewallAddressGroupMembersInput) : RemoveFirewallAddressGroupMembersResult @configure
+ SaveConfigFile(data: SaveConfigFileInput) : SaveConfigFileResult @configfile
+ LoadConfigFile(data: LoadConfigFileInput) : LoadConfigFileResult @configfile
}
diff --git a/src/services/api/graphql/recipes/dhcp_server.py b/src/services/api/graphql/recipes/dhcp_server.py
deleted file mode 100644
index 3edb3028e..000000000
--- a/src/services/api/graphql/recipes/dhcp_server.py
+++ /dev/null
@@ -1,13 +0,0 @@
-
-from . recipe import Recipe
-
-class DhcpServer(Recipe):
- def __init__(self, session, command_file):
- super().__init__(session, command_file)
-
- # Define any custom processing of parameters here by overriding
- # configure:
- #
- # def configure(self):
- # self.data = transform_data(self.data)
- # super().configure()
diff --git a/src/services/api/graphql/recipes/interface_ethernet.py b/src/services/api/graphql/recipes/interface_ethernet.py
deleted file mode 100644
index f88f5924f..000000000
--- a/src/services/api/graphql/recipes/interface_ethernet.py
+++ /dev/null
@@ -1,13 +0,0 @@
-
-from . recipe import Recipe
-
-class InterfaceEthernet(Recipe):
- def __init__(self, session, command_file):
- super().__init__(session, command_file)
-
- # Define any custom processing of parameters here by overriding
- # configure:
- #
- # def configure(self):
- # self.data = transform_data(self.data)
- # super().configure()
diff --git a/src/services/api/graphql/recipes/remove_firewall_address_group_members.py b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py
new file mode 100644
index 000000000..cde30c27a
--- /dev/null
+++ b/src/services/api/graphql/recipes/remove_firewall_address_group_members.py
@@ -0,0 +1,21 @@
+
+from . session import Session
+
+class RemoveFirewallAddressGroupMembers(Session):
+ def __init__(self, session, data):
+ super().__init__(session, data)
+
+ # Define any custom processing of parameters here by overriding
+ # configure:
+ #
+ # def configure(self):
+ # self._data = transform_data(self._data)
+ # super().configure()
+ # self.clean_up()
+
+ def configure(self):
+ super().configure()
+
+ group_name = self._data['name']
+ path = ['firewall', 'group', 'address-group', group_name]
+ self.delete_path_if_childless(path)
diff --git a/src/services/api/graphql/recipes/recipe.py b/src/services/api/graphql/recipes/session.py
index 8fbb9e0bf..b96cc1753 100644
--- a/src/services/api/graphql/recipes/recipe.py
+++ b/src/services/api/graphql/recipes/session.py
@@ -1,27 +1,17 @@
from ariadne import convert_camel_case_to_snake
import vyos.defaults
+from vyos.config import Config
from vyos.template import render
-class Recipe(object):
+class Session(object):
def __init__(self, session, data):
self._session = session
- self.data = data
+ self._data = data
self._name = convert_camel_case_to_snake(type(self).__name__)
- @property
- def data(self):
- return self.__data
-
- @data.setter
- def data(self, data):
- if isinstance(data, dict):
- self.__data = data
- else:
- raise ValueError("data must be of type dict")
-
def configure(self):
session = self._session
- data = self.data
+ data = self._data
func_base_name = self._name
tmpl_file = f'{func_base_name}.tmpl'
@@ -46,4 +36,30 @@ class Recipe(object):
except Exception as error:
raise error
+ def delete_path_if_childless(self, path):
+ session = self._session
+ config = Config(session.get_session_env())
+ if not config.list_nodes(path):
+ session.delete(path)
+ session.commit()
+
+ def save(self):
+ session = self._session
+ data = self._data
+ if 'file_name' not in data or not data['file_name']:
+ data['file_name'] = '/config/config.boot'
+
+ try:
+ session.save_config(data['file_name'])
+ except Exception as error:
+ raise error
+
+ def load(self):
+ session = self._session
+ data = self._data
+ try:
+ session.load_config(data['file_name'])
+ session.commit()
+ except Exception as error:
+ raise error
diff --git a/src/services/api/graphql/recipes/templates/dhcp_server.tmpl b/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl
index 70de43183..70de43183 100644
--- a/src/services/api/graphql/recipes/templates/dhcp_server.tmpl
+++ b/src/services/api/graphql/recipes/templates/create_dhcp_server.tmpl
diff --git a/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl b/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl
new file mode 100644
index 000000000..a890d0086
--- /dev/null
+++ b/src/services/api/graphql/recipes/templates/create_firewall_address_group.tmpl
@@ -0,0 +1,4 @@
+set firewall group address-group {{ name }}
+{% for add in address %}
+set firewall group address-group {{ name }} address {{ add }}
+{% endfor %}
diff --git a/src/services/api/graphql/recipes/templates/interface_ethernet.tmpl b/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl
index d9d7ed691..d9d7ed691 100644
--- a/src/services/api/graphql/recipes/templates/interface_ethernet.tmpl
+++ b/src/services/api/graphql/recipes/templates/create_interface_ethernet.tmpl
diff --git a/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl
new file mode 100644
index 000000000..458f3e5fc
--- /dev/null
+++ b/src/services/api/graphql/recipes/templates/remove_firewall_address_group_members.tmpl
@@ -0,0 +1,3 @@
+{% for add in address %}
+delete firewall group address-group {{ name }} address {{ add }}
+{% endfor %}
diff --git a/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl b/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl
new file mode 100644
index 000000000..f56c61231
--- /dev/null
+++ b/src/services/api/graphql/recipes/templates/update_firewall_address_group_members.tmpl
@@ -0,0 +1,3 @@
+{% for add in address %}
+set firewall group address-group {{ name }} address {{ add }}
+{% endfor %}
diff --git a/src/services/vyos-http-api-server b/src/services/vyos-http-api-server
index cb4ce4072..aa7ac6708 100755
--- a/src/services/vyos-http-api-server
+++ b/src/services/vyos-http-api-server
@@ -32,16 +32,13 @@ from fastapi.responses import HTMLResponse
from fastapi.exceptions import RequestValidationError
from fastapi.routing import APIRoute
from pydantic import BaseModel, StrictStr, validator
-from starlette.datastructures import FormData, MutableHeaders
+from starlette.datastructures import FormData
from starlette.formparsers import FormParser, MultiPartParser
from multipart.multipart import parse_options_header
-from ariadne import make_executable_schema, load_schema_from_path, snake_case_fallback_resolvers
from ariadne.asgi import GraphQL
import vyos.config
-import vyos.defaults
-
from vyos.configsession import ConfigSession, ConfigSessionError
import api.graphql.state
@@ -69,11 +66,11 @@ def load_server_config():
return config
def check_auth(key_list, key):
- id = None
+ key_id = None
for k in key_list:
if k['key'] == key:
- id = k['id']
- return id
+ key_id = k['id']
+ return key_id
def error(code, msg):
resp = {"success": False, "error": msg, "data": None}
@@ -223,10 +220,10 @@ responses = {
def auth_required(data: ApiModel):
key = data.key
api_keys = app.state.vyos_keys
- id = check_auth(api_keys, key)
- if not id:
+ key_id = check_auth(api_keys, key)
+ if not key_id:
raise HTTPException(status_code=401, detail="Valid API key is required")
- app.state.vyos_id = id
+ app.state.vyos_id = key_id
# override Request and APIRoute classes in order to convert form request to json;
# do all explicit validation here, for backwards compatability of error messages;
@@ -613,16 +610,11 @@ def show_op(data: ShowModel):
# GraphQL integration
###
-api.graphql.state.init()
-
-from api.graphql.graphql.mutations import mutation
-from api.graphql.graphql.directives import DataDirective
+from api.graphql.bindings import generate_schema
-api_schema_dir = vyos.defaults.directories['api_schema']
-
-type_defs = load_schema_from_path(api_schema_dir)
+api.graphql.state.init()
-schema = make_executable_schema(type_defs, mutation, snake_case_fallback_resolvers, directives={"generate": DataDirective})
+schema = generate_schema()
app.add_route('/graphql', GraphQL(schema, debug=True))
@@ -640,16 +632,16 @@ if __name__ == '__main__':
try:
server_config = load_server_config()
- except Exception as e:
- logger.critical("Failed to load the HTTP API server config: {0}".format(e))
+ except Exception as err:
+ logger.critical(f"Failed to load the HTTP API server config: {err}")
- session = ConfigSession(os.getpid())
+ config_session = ConfigSession(os.getpid())
- app.state.vyos_session = session
+ app.state.vyos_session = config_session
app.state.vyos_keys = server_config['api_keys']
- app.state.vyos_debug = True if server_config['debug'] == 'true' else False
- app.state.vyos_strict = True if server_config['strict'] == 'true' else False
+ app.state.vyos_debug = bool(server_config['debug'] == 'true')
+ app.state.vyos_strict = bool(server_config['strict'] == 'true')
api.graphql.state.settings['app'] = app
@@ -657,6 +649,6 @@ if __name__ == '__main__':
uvicorn.run(app, host=server_config["listen_address"],
port=int(server_config["port"]),
proxy_headers=True)
- except OSError as e:
- logger.critical(f"OSError {e}")
+ except OSError as err:
+ logger.critical(f"OSError {err}")
sys.exit(1)