summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--interface-definitions/container.xml.in29
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bonding.py13
-rwxr-xr-xsmoketest/scripts/cli/test_interfaces_bridge.py15
-rwxr-xr-xsrc/conf_mode/container.py26
-rwxr-xr-xsrc/conf_mode/interfaces_bonding.py28
-rwxr-xr-xsrc/conf_mode/interfaces_bridge.py5
6 files changed, 96 insertions, 20 deletions
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/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/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')