summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Breunig <christian@breunig.cc>2025-11-02 09:30:05 +0100
committerChristian Breunig <christian@breunig.cc>2025-11-03 21:09:14 +0100
commitae6d3437dc3247c396444b7f65eb49a98b0dd800 (patch)
tree79b3e008ad916e476c67eea1af327ddcfd67552c
parentf61656f9a0010498b1778c43a599862e84250b7d (diff)
downloadvyos-1x-ae6d3437dc3247c396444b7f65eb49a98b0dd800.tar.gz
vyos-1x-ae6d3437dc3247c396444b7f65eb49a98b0dd800.zip
container: T7305: fix VRF loss when restarting pods
Container networks are only started when there is at least one active consumer. If a network is created without any attached containers, it does not need to be assigned to a VRF yet. When the last container in a pod is stopped, its associated container network is removed. Upon container restart, the kernel recreates the network, but the VRF assignment may be lost in the process. This change ensures that all container networks are correctly reattached to their designated VRFs when a pod restarts.
-rw-r--r--python/vyos/container.py41
-rwxr-xr-xsmoketest/scripts/cli/test_container.py27
-rwxr-xr-xsrc/conf_mode/container.py25
-rwxr-xr-xsrc/op_mode/container.py9
4 files changed, 81 insertions, 21 deletions
diff --git a/python/vyos/container.py b/python/vyos/container.py
new file mode 100644
index 000000000..475d796f2
--- /dev/null
+++ b/python/vyos/container.py
@@ -0,0 +1,41 @@
+# Copyright VyOS maintainers and contributors <maintainers@vyos.io>
+#
+# 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.config import Config
+from vyos.ifconfig import Interface
+from vyos.utils.dict import dict_search
+from vyos.utils.network import interface_exists
+
+def restart_network(config: Config) -> None:
+ """
+ Start network and assign it to given VRF if requested.
+
+ This can only be done after the containers got started as the podman network
+ interface will only be enabled by the first container and yet I do not know
+ how to enable the network interface in advance.
+ """
+ if 'network' in config:
+ for network, network_config in config['network'].items():
+ type_config = dict_search('type', network_config)
+ if not dict_search('macvlan', type_config):
+ network_name = f'pod-{network}'
+ # T5147: Networks are started only as soon as there is a consumer.
+ # If only a network is created in the first place, no need to assign
+ # it to a VRF as there's no consumer, yet.
+ if interface_exists(network_name):
+ tmp = Interface(network_name)
+ tmp.set_vrf(network_config.get('vrf', ''))
+ tmp.add_ipv6_eui64_address('fe80::/64')
+
+ return None
diff --git a/smoketest/scripts/cli/test_container.py b/smoketest/scripts/cli/test_container.py
index b41a7dd53..ae85273e8 100755
--- a/smoketest/scripts/cli/test_container.py
+++ b/smoketest/scripts/cli/test_container.py
@@ -23,6 +23,7 @@ from base_vyostest_shim import VyOSUnitTestSHIM
from ipaddress import ip_interface
from vyos.configsession import ConfigSessionError
+from vyos.utils.network import get_interface_vrf
from vyos.utils.process import cmd
from vyos.utils.process import process_named_running
@@ -51,6 +52,7 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
# ensure we can also run this test on a live system - so lets clean
# out the current configuration :)
cls.cli_delete(cls, base_path)
+ cls.cli_delete(cls, ['vrf'])
@classmethod
def tearDownClass(cls):
@@ -133,7 +135,6 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
self.assertEqual(l['Config']['Healthcheck']['Timeout'], 1000000000)
self.assertEqual(l['Config']['Healthcheck']['Retries'], 2)
-
def test_name_server(self):
cont_name = 'dns-test'
net_name = 'net-test'
@@ -517,6 +518,30 @@ class TestContainer(VyOSUnitTestSHIM.TestCase):
# We expect the same amount of containers from the API that we started above
self.assertEqual(len(container_list), len(tmp))
+ def test_network_vrf(self):
+ cont_name = 'vrf-test50'
+ net_name = 'vrf-test50'
+ vrf_name = 'red-15'
+
+ # create temporary VRF for testing
+ self.cli_set(['vrf', 'name', vrf_name, 'table', '100'])
+
+ self.cli_set(base_path + ['name', cont_name, 'image', busybox_image])
+ self.cli_set(base_path + ['name', cont_name, 'network', net_name])
+ self.cli_set(base_path + ['network', net_name, 'prefix', '192.168.0.0/24'])
+ self.cli_set(base_path + ['network', net_name, 'vrf', vrf_name])
+
+ self.cli_commit()
+
+ tmp = get_interface_vrf(f'pod-{net_name}')
+ self.assertEqual(tmp, vrf_name)
+
+ # Restart container and validate VRF assignment
+ self.op_mode(['restart', 'container', cont_name])
+ tmp = get_interface_vrf(f'pod-{net_name}')
+ self.assertEqual(tmp, vrf_name)
+
+ self.cli_delete(['vrf', 'name', vrf_name])
if __name__ == '__main__':
unittest.main(verbosity=2, failfast=VyOSUnitTestSHIM.TestCase.debug_on())
diff --git a/src/conf_mode/container.py b/src/conf_mode/container.py
index a57a9d054..af09ac52a 100755
--- a/src/conf_mode/container.py
+++ b/src/conf_mode/container.py
@@ -29,7 +29,7 @@ from vyos.configdict import dict_merge
from vyos.configdict import node_changed
from vyos.configdict import is_node_changed
from vyos.configverify import verify_vrf
-from vyos.ifconfig import Interface
+from vyos.container import restart_network
from vyos.utils.configfs import delete_cli_node
from vyos.utils.configfs import add_cli_node
from vyos.utils.cpu import get_core_count
@@ -161,7 +161,7 @@ def verify(container):
if 'name_server' in container_config and 'no_name_server' not in container['network'][network_name]:
raise ConfigError(f'Setting name server has no effect when attached container network has DNS enabled!')
- mac = dict_search(f'network.{network_name}.mac', container_config)
+ mac = dict_search(f'network.{network_name}.mac', container_config)
if mac:
if mac in net_dict['mac'].keys():
raise ConfigError(f'MAC address "{mac}" is already used by container "{net_dict["mac"][mac]}"!')
@@ -194,7 +194,7 @@ def verify(container):
if ip_address(address) == ip_network(network)[1]:
raise ConfigError(f'IP address "{address}" can not be used for a container, ' \
'reserved for the container engine!')
-
+
if address in net_dict['address'].keys():
raise ConfigError(f'IP address "{address}" is already used by container "{net_dict["address"][address]}"!')
net_dict['address'][address] = name
@@ -673,23 +673,8 @@ def apply(container):
if disabled_new:
call('systemctl daemon-reload')
- # Start network and assign it to given VRF if requested. this can only be done
- # after the containers got started as the podman network interface will
- # only be enabled by the first container and yet I do not know how to enable
- # the network interface in advance
- if 'network' in container:
- for network, network_config in container['network'].items():
- type_config = dict_search('type', network_config)
- if not dict_search('macvlan', type_config):
- network_name = f'pod-{network}'
- # T5147: Networks are started only as soon as there is a consumer.
- # If only a network is created in the first place, no need to assign
- # it to a VRF as there's no consumer, yet.
- if interface_exists(network_name):
- tmp = Interface(network_name)
- tmp.set_vrf(network_config.get('vrf', ''))
- tmp.add_ipv6_eui64_address('fe80::/64')
-
+ # Re-Start network and assign it to given VRF if requested.
+ restart_network(container)
return None
diff --git a/src/op_mode/container.py b/src/op_mode/container.py
index a34901cb4..281c95e2c 100755
--- a/src/op_mode/container.py
+++ b/src/op_mode/container.py
@@ -166,6 +166,8 @@ def show_network(raw: bool):
def restart(name: str):
from vyos.utils.process import rc_cmd
+ from vyos.config import Config
+ from vyos.container import restart_network
rc, output = rc_cmd(f'systemctl restart vyos-container-{name}.service')
if rc != 0:
@@ -173,6 +175,13 @@ def restart(name: str):
if rc2 != 0:
print(output)
return None
+ if rc == 0:
+ conf = Config()
+ container = conf.get_config_dict(['container'], key_mangling=('-', '_'),
+ no_tag_node_value_mangle=True,
+ get_first_key=True,
+ with_recursive_defaults=True)
+ restart_network(container)
print(f'Container "{name}" restarted!')
return output