diff options
-rw-r--r-- | data/templates/load-balancing/haproxy.cfg.j2 | 12 | ||||
-rw-r--r-- | interface-definitions/container.xml.in | 29 | ||||
-rw-r--r-- | interface-definitions/include/haproxy/timeout-check.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/include/haproxy/timeout-client.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/include/haproxy/timeout-connect.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/include/haproxy/timeout-server.xml.i | 14 | ||||
-rw-r--r-- | interface-definitions/include/haproxy/timeout.xml.i | 39 | ||||
-rw-r--r-- | interface-definitions/interfaces_geneve.xml.in | 4 | ||||
-rw-r--r-- | interface-definitions/load-balancing_haproxy.xml.in | 31 | ||||
-rw-r--r-- | python/vyos/ifconfig/geneve.py | 2 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_interfaces_bonding.py | 13 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_interfaces_bridge.py | 15 | ||||
-rwxr-xr-x | smoketest/scripts/cli/test_load-balancing_haproxy.py | 48 | ||||
-rwxr-xr-x | src/conf_mode/container.py | 26 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_bonding.py | 28 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_bridge.py | 5 | ||||
-rwxr-xr-x | src/conf_mode/interfaces_geneve.py | 2 |
17 files changed, 249 insertions, 61 deletions
diff --git a/data/templates/load-balancing/haproxy.cfg.j2 b/data/templates/load-balancing/haproxy.cfg.j2 index c98b739e2..70ea5d2b0 100644 --- a/data/templates/load-balancing/haproxy.cfg.j2 +++ b/data/templates/load-balancing/haproxy.cfg.j2 @@ -38,9 +38,10 @@ defaults log global mode http option dontlognull - timeout connect 10s - timeout client 50s - timeout server 50s + timeout check {{ timeout.check }}s + timeout connect {{ timeout.connect }}s + timeout client {{ timeout.client }}s + timeout server {{ timeout.server }}s errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http @@ -134,6 +135,11 @@ frontend {{ front }} default_backend {{ backend }} {% endfor %} {% endif %} +{% if front_config.timeout is vyos_defined %} +{% if front_config.timeout.client is vyos_defined %} + timeout client {{ front_config.timeout.client }}s +{% endif %} +{% endif %} {% endfor %} {% endif %} diff --git a/interface-definitions/container.xml.in b/interface-definitions/container.xml.in index 04318a7c9..65ac99e12 100644 --- a/interface-definitions/container.xml.in +++ b/interface-definitions/container.xml.in @@ -412,6 +412,35 @@ </constraint> </properties> </leafNode> + <tagNode name="tmpfs"> + <properties> + <help>Mount a tmpfs filesystem into the container</help> + </properties> + <children> + <leafNode name="destination"> + <properties> + <help>Destination container directory</help> + <valueHelp> + <format>txt</format> + <description>Destination container directory</description> + </valueHelp> + </properties> + </leafNode> + <leafNode name="size"> + <properties> + <help>tmpfs filesystem size in MB</help> + <valueHelp> + <format>u32:1-65536</format> + <description>tmpfs filesystem size in MB</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-65535"/> + </constraint> + <constraintErrorMessage>Container tmpfs size must be between 1 and 65535 MB</constraintErrorMessage> + </properties> + </leafNode> + </children> + </tagNode> <tagNode name="volume"> <properties> <help>Mount a volume into the container</help> diff --git a/interface-definitions/include/haproxy/timeout-check.xml.i b/interface-definitions/include/haproxy/timeout-check.xml.i new file mode 100644 index 000000000..d1217fac3 --- /dev/null +++ b/interface-definitions/include/haproxy/timeout-check.xml.i @@ -0,0 +1,14 @@ +<!-- include start from haproxy/timeout-check.xml.i --> +<leafNode name="check"> + <properties> + <help>Timeout in seconds for established connections</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Check timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/timeout-client.xml.i b/interface-definitions/include/haproxy/timeout-client.xml.i new file mode 100644 index 000000000..2250ccdef --- /dev/null +++ b/interface-definitions/include/haproxy/timeout-client.xml.i @@ -0,0 +1,14 @@ +<!-- include start from haproxy/timeout-client.xml.i --> +<leafNode name="client"> + <properties> + <help>Maximum inactivity time on the client side</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/timeout-connect.xml.i b/interface-definitions/include/haproxy/timeout-connect.xml.i new file mode 100644 index 000000000..da4f983af --- /dev/null +++ b/interface-definitions/include/haproxy/timeout-connect.xml.i @@ -0,0 +1,14 @@ +<!-- include start from haproxy/timeout-connect.xml.i --> +<leafNode name="connect"> + <properties> + <help>Set the maximum time to wait for a connection attempt to a server to succeed</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Connect timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/timeout-server.xml.i b/interface-definitions/include/haproxy/timeout-server.xml.i new file mode 100644 index 000000000..f27d415c1 --- /dev/null +++ b/interface-definitions/include/haproxy/timeout-server.xml.i @@ -0,0 +1,14 @@ +<!-- include start from haproxy/timeout-server.xml.i --> +<leafNode name="server"> + <properties> + <help>Set the maximum inactivity time on the server side</help> + <valueHelp> + <format>u32:1-3600</format> + <description>Server timeout in seconds</description> + </valueHelp> + <constraint> + <validator name="numeric" argument="--range 1-3600"/> + </constraint> + </properties> +</leafNode> +<!-- include end --> diff --git a/interface-definitions/include/haproxy/timeout.xml.i b/interface-definitions/include/haproxy/timeout.xml.i index 79e7303b1..a3a5a8a3e 100644 --- a/interface-definitions/include/haproxy/timeout.xml.i +++ b/interface-definitions/include/haproxy/timeout.xml.i @@ -4,42 +4,9 @@ <help>Timeout options</help> </properties> <children> - <leafNode name="check"> - <properties> - <help>Timeout in seconds for established connections</help> - <valueHelp> - <format>u32:1-3600</format> - <description>Check timeout in seconds</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-3600"/> - </constraint> - </properties> - </leafNode> - <leafNode name="connect"> - <properties> - <help>Set the maximum time to wait for a connection attempt to a server to succeed</help> - <valueHelp> - <format>u32:1-3600</format> - <description>Connect timeout in seconds</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-3600"/> - </constraint> - </properties> - </leafNode> - <leafNode name="server"> - <properties> - <help>Set the maximum inactivity time on the server side</help> - <valueHelp> - <format>u32:1-3600</format> - <description>Server timeout in seconds</description> - </valueHelp> - <constraint> - <validator name="numeric" argument="--range 1-3600"/> - </constraint> - </properties> - </leafNode> + #include <include/haproxy/timeout-check.xml.i> + #include <include/haproxy/timeout-connect.xml.i> + #include <include/haproxy/timeout-server.xml.i> </children> </node> <!-- include end --> diff --git a/interface-definitions/interfaces_geneve.xml.in b/interface-definitions/interfaces_geneve.xml.in index 990c5bd91..c1e6c33d5 100644 --- a/interface-definitions/interfaces_geneve.xml.in +++ b/interface-definitions/interfaces_geneve.xml.in @@ -23,6 +23,10 @@ #include <include/interface/ipv6-options.xml.i> #include <include/interface/mac.xml.i> #include <include/interface/mtu-1200-16000.xml.i> + #include <include/port-number.xml.i> + <leafNode name="port"> + <defaultValue>6081</defaultValue> + </leafNode> <node name="parameters"> <properties> <help>GENEVE tunnel parameters</help> diff --git a/interface-definitions/load-balancing_haproxy.xml.in b/interface-definitions/load-balancing_haproxy.xml.in index ca089d3f0..b95e02337 100644 --- a/interface-definitions/load-balancing_haproxy.xml.in +++ b/interface-definitions/load-balancing_haproxy.xml.in @@ -48,6 +48,14 @@ <valueless/> </properties> </leafNode> + <node name="timeout"> + <properties> + <help>Timeout options</help> + </properties> + <children> + #include <include/haproxy/timeout-client.xml.i> + </children> + </node> <node name="http-compression"> <properties> <help>Compress HTTP responses</help> @@ -368,6 +376,29 @@ </leafNode> </children> </node> + <node name="timeout"> + <properties> + <help>Timeout options</help> + </properties> + <children> + #include <include/haproxy/timeout-check.xml.i> + <leafNode name="check"> + <defaultValue>5</defaultValue> + </leafNode> + #include <include/haproxy/timeout-connect.xml.i> + <leafNode name="connect"> + <defaultValue>10</defaultValue> + </leafNode> + #include <include/haproxy/timeout-client.xml.i> + <leafNode name="client"> + <defaultValue>50</defaultValue> + </leafNode> + #include <include/haproxy/timeout-server.xml.i> + <leafNode name="server"> + <defaultValue>50</defaultValue> + </leafNode> + </children> + </node> #include <include/interface/vrf.xml.i> </children> </node> diff --git a/python/vyos/ifconfig/geneve.py b/python/vyos/ifconfig/geneve.py index f7fddb812..f53ef4166 100644 --- a/python/vyos/ifconfig/geneve.py +++ b/python/vyos/ifconfig/geneve.py @@ -48,7 +48,7 @@ class GeneveIf(Interface): 'parameters.ipv6.flowlabel' : 'flowlabel', } - cmd = 'ip link add name {ifname} type geneve id {vni} remote {remote}' + cmd = 'ip link add name {ifname} type geneve id {vni} remote {remote} dstport {port}' for vyos_key, iproute2_key in mapping.items(): # dict_search will return an empty dict "{}" for valueless nodes like # "parameters.nolearning" - thus we need to test the nodes existence diff --git a/smoketest/scripts/cli/test_interfaces_bonding.py b/smoketest/scripts/cli/test_interfaces_bonding.py index 1a72f9dc4..f99fd0363 100755 --- a/smoketest/scripts/cli/test_interfaces_bonding.py +++ b/smoketest/scripts/cli/test_interfaces_bonding.py @@ -167,18 +167,25 @@ class BondingInterfaceTest(BasicInterfaceTest.TestCase): def test_bonding_multi_use_member(self): # Define available bonding hash policies - for interface in ['bond10', 'bond20']: + bonds = ['bond10', 'bond20', 'bond30'] + for interface in bonds: for member in self._members: self.cli_set(self._base_path + [interface, 'member', 'interface', member]) # check validate() - can not use the same member interfaces multiple times with self.assertRaises(ConfigSessionError): self.cli_commit() - - self.cli_delete(self._base_path + ['bond20']) + # only keep the first bond interface configuration + for interface in bonds[1:]: + self.cli_delete(self._base_path + [interface]) self.cli_commit() + bond = bonds[0] + member_ifaces = read_file(f'/sys/class/net/{bond}/bonding/slaves').split() + for member in self._members: + self.assertIn(member, member_ifaces) + def test_bonding_source_interface(self): # Re-use member interface that is already a source-interface bond = 'bond99' diff --git a/smoketest/scripts/cli/test_interfaces_bridge.py b/smoketest/scripts/cli/test_interfaces_bridge.py index 54c981adc..4041b3ef3 100755 --- a/smoketest/scripts/cli/test_interfaces_bridge.py +++ b/smoketest/scripts/cli/test_interfaces_bridge.py @@ -158,6 +158,21 @@ class BridgeInterfaceTest(BasicInterfaceTest.TestCase): # verify member is assigned to the bridge self.assertEqual(interface, tmp['master']) + def test_bridge_multi_use_member(self): + # Define available bonding hash policies + bridges = ['br10', 'br20', 'br30'] + for interface in bridges: + for member in self._members: + self.cli_set(self._base_path + [interface, 'member', 'interface', member]) + + # check validate() - can not use the same member interfaces multiple times + with self.assertRaises(ConfigSessionError): + self.cli_commit() + # only keep the first bond interface configuration + for interface in bridges[1:]: + self.cli_delete(self._base_path + [interface]) + + self.cli_commit() def test_add_remove_bridge_member(self): # Add member interfaces to bridge and set STP cost/priority diff --git a/smoketest/scripts/cli/test_load-balancing_haproxy.py b/smoketest/scripts/cli/test_load-balancing_haproxy.py index 9f412aa95..077f1974f 100755 --- a/smoketest/scripts/cli/test_load-balancing_haproxy.py +++ b/smoketest/scripts/cli/test_load-balancing_haproxy.py @@ -521,5 +521,53 @@ class TestLoadBalancingReverseProxy(VyOSUnitTestSHIM.TestCase): with self.assertRaises(ConfigSessionError) as e: self.cli_commit() + def test_11_lb_haproxy_timeout(self): + t_default_check = '5' + t_default_client = '50' + t_default_connect = '10' + t_default_server ='50' + t_check = '4' + t_client = '300' + t_connect = '12' + t_server ='120' + t_front_client = '600' + + self.base_config() + self.cli_commit() + # Check default timeout options + config_entries = ( + f'timeout check {t_default_check}s', + f'timeout connect {t_default_connect}s', + f'timeout client {t_default_client}s', + f'timeout server {t_default_server}s', + ) + # Check default timeout options + config = read_file(HAPROXY_CONF) + for config_entry in config_entries: + self.assertIn(config_entry, config) + + # Set custom timeout options + self.cli_set(base_path + ['timeout', 'check', t_check]) + self.cli_set(base_path + ['timeout', 'client', t_client]) + self.cli_set(base_path + ['timeout', 'connect', t_connect]) + self.cli_set(base_path + ['timeout', 'server', t_server]) + self.cli_set(base_path + ['service', 'https_front', 'timeout', 'client', t_front_client]) + + self.cli_commit() + + # Check custom timeout options + config_entries = ( + f'timeout check {t_check}s', + f'timeout connect {t_connect}s', + f'timeout client {t_client}s', + f'timeout server {t_server}s', + f'timeout client {t_front_client}s', + ) + + # Check configured options + config = read_file(HAPROXY_CONF) + for config_entry in config_entries: + self.assertIn(config_entry, config) + if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py index 594de3eb0..3636b0871 100755 --- a/src/conf_mode/container.py +++ b/src/conf_mode/container.py @@ -22,6 +22,7 @@ from ipaddress import ip_address from ipaddress import ip_network from json import dumps as json_write +import psutil from vyos.base import Warning from vyos.config import Config from vyos.configdict import dict_merge @@ -223,6 +224,21 @@ def verify(container): if not os.path.exists(source): raise ConfigError(f'Volume "{volume}" source path "{source}" does not exist!') + if 'tmpfs' in container_config: + for tmpfs, tmpfs_config in container_config['tmpfs'].items(): + if 'destination' not in tmpfs_config: + raise ConfigError(f'tmpfs "{tmpfs}" has no destination path configured!') + if 'size' in tmpfs_config: + free_mem_mb: int = psutil.virtual_memory().available / 1024 / 1024 + if int(tmpfs_config['size']) > free_mem_mb: + Warning(f'tmpfs "{tmpfs}" size is greater than the current free memory!') + + total_mem_mb: int = (psutil.virtual_memory().total / 1024 / 1024) / 2 + if int(tmpfs_config['size']) > total_mem_mb: + raise ConfigError(f'tmpfs "{tmpfs}" size should not be more than 50% of total system memory!') + else: + raise ConfigError(f'tmpfs "{tmpfs}" has no size configured!') + if 'port' in container_config: for tmp in container_config['port']: if not {'source', 'destination'} <= set(container_config['port'][tmp]): @@ -362,6 +378,14 @@ def generate_run_arguments(name, container_config): prop = vol_config['propagation'] volume += f' --volume {svol}:{dvol}:{mode},{prop}' + # Mount tmpfs + tmpfs = '' + if 'tmpfs' in container_config: + for tmpfs_config in container_config['tmpfs'].values(): + dest = tmpfs_config['destination'] + size = tmpfs_config['size'] + tmpfs += f' --mount=type=tmpfs,tmpfs-size={size}M,destination={dest}' + host_pid = '' if 'allow_host_pid' in container_config: host_pid = '--pid host' @@ -373,7 +397,7 @@ def generate_run_arguments(name, container_config): container_base_cmd = f'--detach --interactive --tty --replace {capabilities} --cpus {cpu_quota} {sysctl_opt} ' \ f'--memory {memory}m --shm-size {shared_memory}m --memory-swap 0 --restart {restart} ' \ - f'--name {name} {hostname} {device} {port} {name_server} {volume} {env_opt} {label} {uid} {host_pid}' + f'--name {name} {hostname} {device} {port} {name_server} {volume} {tmpfs} {env_opt} {label} {uid} {host_pid}' entrypoint = '' if 'entrypoint' in container_config: diff --git a/src/conf_mode/interfaces_bonding.py b/src/conf_mode/interfaces_bonding.py index 4f1141dcb..84316c16e 100755 --- a/src/conf_mode/interfaces_bonding.py +++ b/src/conf_mode/interfaces_bonding.py @@ -126,9 +126,8 @@ def get_config(config=None): # Restore existing config level conf.set_level(old_level) - if dict_search('member.interface', bond): - for interface, interface_config in bond['member']['interface'].items(): - + if dict_search('member.interface', bond) is not None: + for interface in bond['member']['interface']: interface_ethernet_config = conf.get_config_dict( ['interfaces', 'ethernet', interface], key_mangling=('-', '_'), @@ -137,44 +136,45 @@ def get_config(config=None): with_defaults=False, with_recursive_defaults=False) - interface_config['config_paths'] = dict_to_paths_values(interface_ethernet_config) + bond['member']['interface'][interface].update({'config_paths' : + dict_to_paths_values(interface_ethernet_config)}) # Check if member interface is a new member if not conf.exists_effective(base + [ifname, 'member', 'interface', interface]): bond['shutdown_required'] = {} - interface_config['new_added'] = {} + bond['member']['interface'][interface].update({'new_added' : {}}) # Check if member interface is disabled conf.set_level(['interfaces']) section = Section.section(interface) # this will be 'ethernet' for 'eth0' if conf.exists([section, interface, 'disable']): - interface_config['disable'] = '' + if tmp: bond['member']['interface'][interface].update({'disable': ''}) conf.set_level(old_level) # Check if member interface is already member of another bridge tmp = is_member(conf, interface, 'bridge') - if tmp: interface_config['is_bridge_member'] = tmp + if tmp: bond['member']['interface'][interface].update({'is_bridge_member' : tmp}) # Check if member interface is already member of a bond tmp = is_member(conf, interface, 'bonding') - for tmp in is_member(conf, interface, 'bonding'): - if bond['ifname'] == tmp: - continue - interface_config['is_bond_member'] = tmp + if ifname in tmp: + del tmp[ifname] + if tmp: bond['member']['interface'][interface].update({'is_bond_member' : tmp}) # Check if member interface is used as source-interface on another interface tmp = is_source_interface(conf, interface) - if tmp: interface_config['is_source_interface'] = tmp + if tmp: bond['member']['interface'][interface].update({'is_source_interface' : tmp}) # bond members must not have an assigned address tmp = has_address_configured(conf, interface) - if tmp: interface_config['has_address'] = {} + if tmp: bond['member']['interface'][interface].update({'has_address' : ''}) # bond members must not have a VRF attached tmp = has_vrf_configured(conf, interface) - if tmp: interface_config['has_vrf'] = {} + if tmp: bond['member']['interface'][interface].update({'has_vrf' : ''}) + return bond diff --git a/src/conf_mode/interfaces_bridge.py b/src/conf_mode/interfaces_bridge.py index 637db442a..aff93af2a 100755 --- a/src/conf_mode/interfaces_bridge.py +++ b/src/conf_mode/interfaces_bridge.py @@ -74,8 +74,9 @@ def get_config(config=None): for interface in list(bridge['member']['interface']): # Check if member interface is already member of another bridge tmp = is_member(conf, interface, 'bridge') - if tmp and bridge['ifname'] not in tmp: - bridge['member']['interface'][interface].update({'is_bridge_member' : tmp}) + if ifname in tmp: + del tmp[ifname] + if tmp: bridge['member']['interface'][interface].update({'is_bridge_member' : tmp}) # Check if member interface is already member of a bond tmp = is_member(conf, interface, 'bonding') diff --git a/src/conf_mode/interfaces_geneve.py b/src/conf_mode/interfaces_geneve.py index 007708d4a..1c5b4d0e7 100755 --- a/src/conf_mode/interfaces_geneve.py +++ b/src/conf_mode/interfaces_geneve.py @@ -47,7 +47,7 @@ def get_config(config=None): # GENEVE interfaces are picky and require recreation if certain parameters # change. But a GENEVE interface should - of course - not be re-created if # it's description or IP address is adjusted. Feels somehow logic doesn't it? - for cli_option in ['remote', 'vni', 'parameters']: + for cli_option in ['remote', 'vni', 'parameters', 'port']: if is_node_changed(conf, base + [ifname, cli_option]): geneve.update({'rebuild_required': {}}) |