From 389d2b7cb151321a518bc04a3578cb0f35c5577d Mon Sep 17 00:00:00 2001 From: Rodrigo Barbieri Date: Mon, 15 Apr 2024 13:45:45 -0300 Subject: [PATCH] Load nf_conntrack at boot time Sysctl rules fined in charm config fails to be applied in ovn enviroments because the rules are applied before nf_conntrack is loaded. By adding nf_conntrack to the /etc/modules file it guarantees that it will be loaded before the rules are applied. Closes-Bug: #1922778 Change-Id: I51dae65cdc06e35230160bcaedda99710a72617d --- hooks/nova_compute_hooks.py | 27 +++++++++ unit_tests/test_nova_compute_hooks.py | 83 +++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/hooks/nova_compute_hooks.py b/hooks/nova_compute_hooks.py index 8ab61556..373a6d5b 100755 --- a/hooks/nova_compute_hooks.py +++ b/hooks/nova_compute_hooks.py @@ -24,9 +24,12 @@ import os import subprocess import grp import shutil +import yaml import charmhelpers.core.unitdata as unitdata +from charmhelpers.core.kernel import modprobe + from charmhelpers.core.hookenv import ( Hooks, config, @@ -250,6 +253,7 @@ def config_changed(): send_remote_restart = True sysctl_settings = config('sysctl') + ensure_nf_conntrack_module_loaded(sysctl_settings) if sysctl_settings and not is_container(): create_sysctl( sysctl_settings, @@ -330,6 +334,29 @@ def config_changed(): check_and_start_iscsid() +def ensure_nf_conntrack_module_loaded(sysctl_str): + """Loads and writes nf_conntrack to /etc/modules if present in sysctls.""" + # abort if empty or container + if not sysctl_str or is_container(): + return + + try: + sysctl_dict = yaml.safe_load(sysctl_str) + except yaml.YAMLError: + log("Error parsing YAML sysctl_dict: {}".format(sysctl_str), + level=ERROR) + return + + if any("nf_conntrack" in key for key in sysctl_dict.keys()): + try: + # this call loads the module and writes an entry in /etc/modules + # for automatic loading at early boot + modprobe("nf_conntrack") + except Exception as e: + log("Failed to load or persist nf_conntrack" + " kernel module: {}".format(e), level=WARNING) + + def update_all_configs(): CONFIGS.write_all() diff --git a/unit_tests/test_nova_compute_hooks.py b/unit_tests/test_nova_compute_hooks.py index 074946c1..a47c980d 100644 --- a/unit_tests/test_nova_compute_hooks.py +++ b/unit_tests/test_nova_compute_hooks.py @@ -453,6 +453,89 @@ class NovaComputeRelationsTests(CharmTestCase): user='nova' ) + @patch.object(hooks, 'is_container') + @patch('yaml.safe_load') + def test_ensure_nf_conntrack_module_loaded_empty( + self, safe_load, is_container): + is_container.return_value = False + hooks.ensure_nf_conntrack_module_loaded("") + safe_load.assert_not_called() + + @patch.object(hooks, 'is_container') + @patch('yaml.safe_load') + def test_ensure_nf_conntrack_module_loaded_container( + self, safe_load, is_container): + is_container.return_value = True + hooks.ensure_nf_conntrack_module_loaded("foo") + safe_load.assert_not_called() + + @patch.object(hooks, 'modprobe') + @patch.object(hooks, 'is_container') + @patch('yaml.safe_load') + def test_ensure_nf_conntrack_module_loaded_failed_yaml( + self, safe_load, is_container, modprobe): + is_container.return_value = False + hooks.ensure_nf_conntrack_module_loaded("foo") + safe_load.assert_called_once_with("foo") + modprobe.assert_not_called() + + @patch.object(hooks, 'log') + @patch.object(hooks, 'modprobe') + @patch.object(hooks, 'is_container') + def test_ensure_nf_conntrack_module_loaded_failed_mobprobe( + self, is_container, modprobe, log): + is_container.return_value = False + modprobe.side_effect = Exception("FOO") + data = ("{ net.ipv4.neigh.default.gc_thresh1 : 128," + "net.ipv4.neigh.default.gc_thresh2 : 28672," + "net.ipv4.neigh.default.gc_thresh3 : 32768," + "net.ipv6.neigh.default.gc_thresh1 : 128," + "net.ipv6.neigh.default.gc_thresh2 : 28672," + "net.ipv6.neigh.default.gc_thresh3 : 32768," + "net.nf_conntrack_max : 1000000," + "net.netfilter.nf_conntrack_buckets : 204800," + "net.netfilter.nf_conntrack_max : 1000000 }") + hooks.ensure_nf_conntrack_module_loaded(data) + log.assert_called_once_with( + "Failed to load or persist nf_conntrack kernel module: FOO", + level="WARNING") + modprobe.assert_called_once_with("nf_conntrack") + + @patch.object(hooks, 'log') + @patch.object(hooks, 'modprobe') + @patch.object(hooks, 'is_container') + def test_ensure_nf_conntrack_module_loaded( + self, is_container, modprobe, log): + is_container.return_value = False + data = ("{ net.ipv4.neigh.default.gc_thresh1 : 128," + "net.ipv4.neigh.default.gc_thresh2 : 28672," + "net.ipv4.neigh.default.gc_thresh3 : 32768," + "net.ipv6.neigh.default.gc_thresh1 : 128," + "net.ipv6.neigh.default.gc_thresh2 : 28672," + "net.ipv6.neigh.default.gc_thresh3 : 32768," + "net.nf_conntrack_max : 1000000," + "net.netfilter.nf_conntrack_buckets : 204800," + "net.netfilter.nf_conntrack_max : 1000000 }") + hooks.ensure_nf_conntrack_module_loaded(data) + log.assert_not_called() + modprobe.assert_called_once_with("nf_conntrack") + + @patch.object(hooks, 'log') + @patch.object(hooks, 'modprobe') + @patch.object(hooks, 'is_container') + def test_ensure_nf_conntrack_module_loaded_no_sysctl( + self, is_container, modprobe, log): + is_container.return_value = False + data = ("{ net.ipv4.neigh.default.gc_thresh1 : 128," + "net.ipv4.neigh.default.gc_thresh2 : 28672," + "net.ipv4.neigh.default.gc_thresh3 : 32768," + "net.ipv6.neigh.default.gc_thresh1 : 128," + "net.ipv6.neigh.default.gc_thresh2 : 28672," + "net.ipv6.neigh.default.gc_thresh3 : 32768 }") + hooks.ensure_nf_conntrack_module_loaded(data) + modprobe.assert_not_called() + log.assert_not_called() + def test_amqp_joined(self): hooks.amqp_joined() self.relation_set.assert_called_with(