Blob Blame History Raw
From 731070a68e54e01015b520e2c19141a912923218 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= <jskarvad@redhat.com>
Date: Sun, 9 Dec 2018 20:16:50 +0100
Subject: [PATCH] Added support for Boot loader specification (BLS)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Resolves: rhbz#1576435

Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com>
---
 91-tuned.install                   | 31 ++++++++++++++++++++++
 Makefile                           |  6 ++++-
 tuned.spec                         | 19 ++++++++++++++
 tuned/consts.py                    |  3 +++
 tuned/plugins/plugin_bootloader.py | 41 +++++++++++++++++++++++++++---
 tuned/utils/commands.py            | 15 ++++++-----
 6 files changed, 104 insertions(+), 11 deletions(-)
 create mode 100644 91-tuned.install

diff --git a/91-tuned.install b/91-tuned.install
new file mode 100644
index 0000000..18d00d9
--- /dev/null
+++ b/91-tuned.install
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+COMMAND="$1"
+KERNEL_VERSION="$2"
+BOOT_DIR_ABS="$3"
+KERNEL_IMAGE="$4"
+
+if ! [[ $KERNEL_INSTALL_MACHINE_ID ]]; then
+  exit 0
+fi
+
+MACHINE_ID=$KERNEL_INSTALL_MACHINE_ID
+
+# with grub2 always /boot
+BOOT_ROOT="/boot"
+LOADER_ENTRIES="$BOOT_ROOT/loader/entries"
+
+[ -d "$LOADER_ENTRIES" ] || exit 0
+
+[ "$COMMAND" = "add" ] || exit 0
+
+pushd "$LOADER_ENTRIES"
+for f in `basename "$MACHINE_ID"`-*.conf; do
+  if [ -f "$f" -a "${f: -12}" != "-rescue.conf" ]; then
+    grep -q '^\s*options\s\+.*\$tuned_params' "$f" || sed -i '/^\s*options\s\+/ s/\(.*\)/\1 \$tuned_params/' "$f"
+    grep -q '^\s*initrd\s\+.*\$tuned_initrd' "$f" || sed -i '/^\s*initrd\s\+/ s/\(.*\)/\1 \$tuned_initrd/' "$f"
+  fi
+done
+popd
+
+exit 0
diff --git a/Makefile b/Makefile
index 816fcb1..f61cd01 100644
--- a/Makefile
+++ b/Makefile
@@ -40,6 +40,7 @@ PYTHON_SITELIB = $(shell $(PYTHON) -c 'from distutils.sysconfig import get_pytho
 ifeq ($(PYTHON_SITELIB),)
 $(error Failed to determine python library directory)
 endif
+KERNELINSTALLHOOKDIR = /usr/lib/kernel/install.d
 TUNED_PROFILESDIR = /usr/lib/tuned
 TUNED_RECOMMEND_DIR = $(TUNED_PROFILESDIR)/recommend.d
 TUNED_USER_RECOMMEND_DIR = $(SYSCONFDIR)/tuned/recommend.d
@@ -59,7 +60,7 @@ release-cp: release-dir
 
 	cp -a tuned.py tuned.spec tuned.service tuned.tmpfiles Makefile tuned-adm.py \
 		tuned-adm.bash dbus.conf recommend.conf tuned-main.conf 00_tuned \
-		bootcmdline modules.conf com.redhat.tuned.policy \
+		91-tuned.install bootcmdline modules.conf com.redhat.tuned.policy \
 		com.redhat.tuned.gui.policy tuned-gui.py tuned-gui.glade \
 		tuned-gui.desktop $(VERSIONED_NAME)
 	cp -a doc experiments libexec man profiles systemtap tuned contrib icons \
@@ -180,6 +181,9 @@ install: install-dirs
 	# grub template
 	install -Dpm 0755 00_tuned $(DESTDIR)$(SYSCONFDIR)/grub.d/00_tuned
 
+	# kernel install hook
+	install -Dpm 0755 91-tuned.install $(DESTDIR)$(KERNELINSTALLHOOKDIR)/91-tuned.install
+
 	# polkit configuration
 	install -Dpm 0644 com.redhat.tuned.policy $(DESTDIR)$(DATADIR)/polkit-1/actions/com.redhat.tuned.policy
 	install -Dpm 0644 com.redhat.tuned.gui.policy $(DESTDIR)$(DATADIR)/polkit-1/actions/com.redhat.tuned.gui.policy
diff --git a/tuned.spec b/tuned.spec
index 1ecf320..b7ee60d 100644
--- a/tuned.spec
+++ b/tuned.spec
@@ -278,6 +278,24 @@ if [ "$1" == 0 ]; then
   if [ -r "%{_sysconfdir}/default/grub" ]; then
     sed -i '/GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_DEFAULT:+$GRUB_CMDLINE_LINUX_DEFAULT }\\$tuned_params"/d' %{_sysconfdir}/default/grub
   fi
+
+# cleanup for Boot loader specification (BLS)
+
+# clear grubenv variables
+  grub2-editenv - unset tuned_params tuned_initrd &>/dev/null || :
+# unpatch BLS entries
+  MACHINE_ID=`cat /etc/machine-id 2>/dev/null`
+  if [ "$MACHINE_ID" ]
+  then
+    for f in /boot/loader/entries/$MACHINE_ID-*.conf
+    do
+      if [ -f "$f" -a "${f: -12}" != "-rescue.conf" ]
+      then
+        sed -i '/^\s*options\s\+.*\$tuned_params/ s/\s\+\$tuned_params\b//g' "$f" &>/dev/null || :
+        sed -i '/^\s*initrd\s\+.*\$tuned_initrd/ s/\s\+\$tuned_initrd\b//g' "$f" &>/dev/null || :
+      fi
+    done
+  fi
 fi
 
 
@@ -374,6 +392,7 @@ fi
 %{_datadir}/tuned/grub2
 %{_datadir}/polkit-1/actions/com.redhat.tuned.policy
 %ghost %{_sysconfdir}/modprobe.d/kvm.rt.tuned.conf
+%{_prefix}/lib/kernel/install.d/91-tuned.install
 
 %files gtk
 %defattr(-,root,root,-)
diff --git a/tuned/consts.py b/tuned/consts.py
index ab87c70..de94059 100644
--- a/tuned/consts.py
+++ b/tuned/consts.py
@@ -37,6 +37,9 @@ BOOT_CMDLINE_TUNED_VAR = "TUNED_BOOT_CMDLINE"
 BOOT_CMDLINE_INITRD_ADD_VAR = "TUNED_BOOT_INITRD_ADD"
 BOOT_CMDLINE_FILE = "/etc/tuned/bootcmdline"
 PETITBOOT_DETECT_DIR = "/sys/firmware/opal"
+MACHINE_ID_FILE = "/etc/machine-id"
+KERNEL_UPDATE_HOOK_FILE = "/usr/lib/kernel/install.d/91-tuned.install"
+BLS_ENTRIES_PATH = "/boot/loader/entries"
 
 # modules plugin configuration
 MODULES_FILE = "/etc/modprobe.d/tuned.conf"
diff --git a/tuned/plugins/plugin_bootloader.py b/tuned/plugins/plugin_bootloader.py
index d88b62c..400b07e 100644
--- a/tuned/plugins/plugin_bootloader.py
+++ b/tuned/plugins/plugin_bootloader.py
@@ -94,7 +94,7 @@ class BootloaderPlugin(base.Plugin):
 
 	def _remove_grub2_tuning(self):
 		if not self._grub2_cfg_file_names:
-			log.error("cannot find grub.cfg to patch, you need to regenerate it by hand using grub2-mkconfig")
+			log.info("cannot find grub.cfg to patch")
 			return
 		self._patch_bootcmdline({consts.BOOT_CMDLINE_TUNED_VAR : "", consts.BOOT_CMDLINE_INITRD_ADD_VAR : ""})
 		for f in self._grub2_cfg_file_names:
@@ -107,6 +107,7 @@ class BootloaderPlugin(base.Plugin):
 		if full_rollback:
 			log.info("removing grub2 tuning previously added by Tuned")
 			self._remove_grub2_tuning()
+			self._update_grubenv({"tuned_params" : "", "tuned_initrd" : ""})
 
 	def _grub2_cfg_unpatch(self, grub2_cfg):
 		log.debug("unpatching grub.cfg")
@@ -138,7 +139,7 @@ class BootloaderPlugin(base.Plugin):
 	def _grub2_default_env_patch(self):
 		grub2_default_env = self._cmd.read_file(consts.GRUB2_DEFAULT_ENV_FILE)
 		if len(grub2_default_env) <= 0:
-			log.error("error reading '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
+			log.info("cannot read '%s'" % consts.GRUB2_DEFAULT_ENV_FILE)
 			return False
 
 		d = {"GRUB_CMDLINE_LINUX_DEFAULT" : consts.GRUB2_TUNED_VAR, "GRUB_INITRD_OVERLAY" : consts.GRUB2_TUNED_INITRD_VAR}
@@ -157,12 +158,12 @@ class BootloaderPlugin(base.Plugin):
 	def _grub2_cfg_patch(self, d):
 		log.debug("patching grub.cfg")
 		if not self._grub2_cfg_file_names:
-			log.error("cannot find grub.cfg to patch, you need to regenerate it by hand by grub2-mkconfig")
+			log.info("cannot find grub.cfg to patch")
 			return False
 		for f in self._grub2_cfg_file_names:
 			grub2_cfg = self._cmd.read_file(f)
 			if len(grub2_cfg) <= 0:
-				log.error("error patching %s, you need to regenerate it by hand by grub2-mkconfig" % f)
+				log.info("cannot patch %s" % f)
 				return False
 			log.debug("adding boot command line parameters to '%s'" % f)
 			grub2_cfg_new = grub2_cfg
@@ -187,6 +188,37 @@ class BootloaderPlugin(base.Plugin):
 		self._grub2_cfg_patch({consts.GRUB2_TUNED_VAR : self._cmdline_val, consts.GRUB2_TUNED_INITRD_VAR : self._initrd_val})
 		self._patch_bootcmdline({consts.BOOT_CMDLINE_TUNED_VAR : self._cmdline_val, consts.BOOT_CMDLINE_INITRD_ADD_VAR : self._initrd_val})
 
+	def _has_bls(self):
+		return os.path.exists(consts.BLS_ENTRIES_PATH)
+
+	def _update_grubenv(self, d):
+		log.debug("updating grubenv, setting %s" % str(d));
+		l = ["%s=%s" % (str(option), str(value)) for option, value in d.items()]
+		(rc, out) = self._cmd.execute(["grub2-editenv", "-", "set"] + l)
+		if rc != 0:
+			log.warn("cannot update grubenv: '%s'" % out)
+			return False;
+		return True
+
+	def _bls_entries_patch_initial(self):
+		machine_id = self._cmd.get_machine_id()
+		if machine_id == "":
+			return False
+		log.debug("running kernel update hook '%s' to patch BLS entries" % consts.KERNEL_UPDATE_HOOK_FILE)
+		(rc, out) = self._cmd.execute([consts.KERNEL_UPDATE_HOOK_FILE, "add"], env = {"KERNEL_INSTALL_MACHINE_ID" : machine_id})
+		if rc != 0:
+			log.warn("cannot patch BLS entries: '%s'" % out)
+			return False
+		return True
+
+	def _bls_update(self):
+		log.debug("updating BLS")
+		if self._has_bls() and \
+			self._update_grubenv({"tuned_params" : self._cmdline_val, "tuned_initrd" : self._initrd_val}) and \
+			self._bls_entries_patch_initial():
+				return True
+		return False
+
 	def _init_initrd_dst_img(self, name):
 		if self._initrd_dst_img_val is None:
 			self._initrd_dst_img_val = os.path.join(consts.BOOT_DIR, os.path.basename(name))
@@ -307,4 +339,5 @@ class BootloaderPlugin(base.Plugin):
 	def _instance_post_static(self, instance, enabling):
 		if enabling and self.update_grub2_cfg:
 			self._grub2_update()
+			self._bls_update()
 			self.update_grub2_cfg = False
diff --git a/tuned/utils/commands.py b/tuned/utils/commands.py
index 5692450..68d2ea5 100644
--- a/tuned/utils/commands.py
+++ b/tuned/utils/commands.py
@@ -25,7 +25,6 @@ log = tuned.logs.get()
 class commands:
 
 	def __init__(self, logging = True):
-		self._environment = None
 		self._logging = logging
 
 	def _error(self, msg):
@@ -208,21 +207,25 @@ class commands:
 
 		return self.write_to_file(f, data)
 
+	# returns machine ID or empty string "" in case of error
+	def get_machine_id(self, no_error = True):
+		return self.read_file(consts.MACHINE_ID_FILE, no_error).strip()
+
 	# "no_errors" can be list of return codes not treated as errors, if 0 is in no_errors, it means any error
 	# returns (retcode, out), where retcode is exit code of the executed process or -errno if
 	# OSError or IOError exception happened
-	def execute(self, args, shell = False, cwd = None, no_errors = [], return_err = False):
+	def execute(self, args, shell = False, cwd = None, env = {}, no_errors = [], return_err = False):
 		retcode = 0
-		if self._environment is None:
-			self._environment = os.environ.copy()
-			self._environment["LC_ALL"] = "C"
+		_environment = os.environ.copy()
+		_environment["LC_ALL"] = "C"
+		_environment.update(env)
 
 		self._debug("Executing %s." % str(args))
 		out = ""
 		err_msg = None
 		try:
 			proc = Popen(args, stdout = PIPE, stderr = PIPE, \
-					env = self._environment, \
+					env = _environment, \
 					shell = shell, cwd = cwd, \
 					close_fds = True, \
 					universal_newlines = True)
-- 
2.17.2