From 68174dc155e1f453202f3e8a3a0eee5d19728637 Mon Sep 17 00:00:00 2001 From: Lindley Vieira Date: Thu, 2 Oct 2025 18:47:39 -0400 Subject: [PATCH] Fix inservice 3-way merge Ostree 3-way merge always uses the active deployment as base, but in the cases we have an inservice patch, the inservice deployment is listed as pending causing the ostree to loose all inservice changes in a new 3-way merge. This commit fixes it by point the active deployment folders, used by ostree 3-way merge, to the pending folders. This way, ostree do the correctly merge. Test-Plan: PASS: Apply a major release on top of an inservice patch PASS: Apply and remove an inservice patch PASS: Apply a inservice patch on top of a previous inservice patch Closes-Bug: 2126705 Change-Id: I11282068d2148673d5225cf97e6a71099e990ac2 Signed-off-by: Lindley Vieira --- software/software/constants.py | 8 +++ software/software/ostree_utils.py | 78 +++++++++++++++++++++++------ software/software/software_agent.py | 4 +- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/software/software/constants.py b/software/software/constants.py index a80f43dc..83a74445 100644 --- a/software/software/constants.py +++ b/software/software/constants.py @@ -109,6 +109,7 @@ OSTREE_REPO = 'ostree_repo' SYSROOT_OSTREE_REF = "debian:starlingx" OSTREE_CONFIG = "config" OSTREE_GPG_VERIFY = "gpg-verify" +OSTREE_ACTIVE_DEPLOYMENT_TAG = "'* '" # Sysroot SYSROOT_OSTREE = "/sysroot/ostree/repo" @@ -262,3 +263,10 @@ RESERVED_WORDS_SET = {"OS_AUTH_TOKEN", "OS_AUTH_TYPE", "OS_AUTH_URL", "OS_ENDPOI "OS_PROJECT_NAME", "OS_REGION_NAME", "OS_SERVICE_TOKEN", "OS_USERNAME", "OS_USER_DOMAIN_NAME", "OS_SERVICE_TYPE", "OS_TENANT_ID", "OS_TENANT_NAME", "OS_USER_DOMAIN_ID", "SYSTEM_API_VERSION", "SYSTEM_URL"} + +# Mount bind +READ_ONLY_PERMISSION = "ro,noatime" +READ_WRITE_PERMISSION = "rw,noatime" +ETC = "/etc" +USR = "/usr" +USR_ETC = "/usr/etc" diff --git a/software/software/ostree_utils.py b/software/software/ostree_utils.py index 2ba6551e..affcb580 100644 --- a/software/software/ostree_utils.py +++ b/software/software/ostree_utils.py @@ -462,31 +462,77 @@ def fetch_pending_deployment(): return pending_deployment -def mount_new_deployment(deployment_dir): +def fetch_active_deployment(): """ - Unmount /usr and /etc from the file system and remount it to directory - /usr and /etc respectively - :param deployment_dir: a path on the filesystem which points to the pending - deployment - example: /ostree/deploy/debian/deploy/ + Fetch the deployment ID of the active deployment + :return: The deployment ID of the active deployment """ + + cmd = "ostree admin status | grep %s | awk '{printf $3}'" % constants.OSTREE_ACTIVE_DEPLOYMENT_TAG + try: - new_usr_mount_dir = "%s/usr" % (deployment_dir) - new_etc_mount_dir = "%s/etc" % (deployment_dir) - sh.mount("--bind", "-o", "ro,noatime", new_usr_mount_dir, "/usr") - sh.mount("--bind", "-o", "rw,noatime", new_etc_mount_dir, "/etc") + output = subprocess.run(cmd, shell=True, check=True, capture_output=True) + except subprocess.CalledProcessError as e: + msg = "Failed to fetch ostree admin status for active deployment." + info_msg = "OSTree Admin Status Error: return code: %s , Output: %s" \ + % (e.returncode, e.stderr.decode("utf-8")) + LOG.info(info_msg) + raise OSTreeCommandFail(msg) + + # Store the output of the above command in a string + active_deployment = output.stdout.decode('utf-8') + + return active_deployment + + +def create_bind_mount(source, target, permissions=constants.READ_ONLY_PERMISSION): + """ + Create a bind mount. + :param source: The source directory + :param target: The directory where the bind will be mounted + :param permissions: The access permissions + """ + LOG.info("Creating bind mount from: %s, to: %s, with permissions: %s" + % (source, target, permissions)) + + try: + sh.mount("--bind", "-o", permissions, source, target) except sh.ErrorReturnCode: - LOG.warning("Mount failed. Retrying to mount /usr and /etc again after 5 secs.") + LOG.warning("Mount failed. Retrying to mount %s again after 5 secs." % target) time.sleep(5) try: - sh.mount("--bind", "-o", "ro,noatime", new_usr_mount_dir, "/usr") - sh.mount("--bind", "-o", "rw,noatime", new_etc_mount_dir, "/etc") + sh.mount("--bind", "-o", permissions, source, target) except sh.ErrorReturnCode as e: - msg = "Failed to re-mount /usr and /etc." - info_msg = "OSTree Deployment Mount Error: Output: %s" \ - % (e.stderr.decode("utf-8")) + msg = "Failed to re-mount %s" % target + info_msg = "OSTree Deployment Mount Error: Output: %s" % (e.stderr.decode("utf-8")) LOG.warning(info_msg) raise OSTreeCommandFail(msg) + + +def mount_new_deployment(pending_dir, active_dir): + """ + Create the following mounts used by an inservice patch: + 1) /usr -> /usr + 2) /etc -> /etc + 3) /usr/etc -> /usr/etc + 4) /etc -> /etc + Also mounts K8s version files + :param pending_dir: a path on the filesystem which points to the pending + deployment (e.g.: /ostree/deploy/debian/deploy/) + :param active_dir: a path on the filesystem which points to the active + deployment + """ + try: + new_usr_mount_dir = "%s%s" % (pending_dir, constants.USR) + new_etc_mount_dir = "%s%s" % (pending_dir, constants.ETC) + new_usr_etc_mount_dir = "%s%s" % (pending_dir, constants.USR_ETC) + act_usr_etc_mount_dir = "%s%s" % (active_dir, constants.USR_ETC) + act_etc_mount_dir = "%s%s" % (active_dir, constants.ETC) + + create_bind_mount(new_usr_mount_dir, constants.USR) + create_bind_mount(new_etc_mount_dir, constants.ETC, constants.READ_WRITE_PERMISSION) + create_bind_mount(new_usr_etc_mount_dir, act_usr_etc_mount_dir) + create_bind_mount(new_etc_mount_dir, act_etc_mount_dir) finally: # Handle the switch from bind mounts to symlinks for K8s versions. # Can be removed once the switch is complete. diff --git a/software/software/software_agent.py b/software/software/software_agent.py index 118d5782..a9e4dabc 100644 --- a/software/software/software_agent.py +++ b/software/software/software_agent.py @@ -777,8 +777,10 @@ class PatchAgent(PatchService): try: pending_deployment = ostree_utils.fetch_pending_deployment() deployment_dir = constants.OSTREE_BASE_DEPLOYMENT_DIR + pending_deployment + active_deployment = ostree_utils.fetch_active_deployment() + active_dir = constants.OSTREE_BASE_DEPLOYMENT_DIR + active_deployment setflag(mount_pending_file) - ostree_utils.mount_new_deployment(deployment_dir) + ostree_utils.mount_new_deployment(deployment_dir, active_dir) clearflag(mount_pending_file) LOG.info("Running post-install patch-scripts") subprocess.check_output([run_install_software_scripts_cmd, "postinstall"],