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 <lindley.vieira@windriver.com>
This commit is contained in:
Lindley Vieira
2025-10-02 18:47:39 -04:00
parent 1eb4f99bf9
commit 68174dc155
3 changed files with 73 additions and 17 deletions

View File

@@ -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"

View File

@@ -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
<depoyment_dir>/usr and <depoyment_dir>/etc respectively
:param deployment_dir: a path on the filesystem which points to the pending
deployment
example: /ostree/deploy/debian/deploy/<deployment_id>
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 -> <pending_dir>/usr
2) /etc -> <pending_dir>/etc
3) <active_dir>/usr/etc -> <pending_dir>/usr/etc
4) <active_dir>/etc -> <pending_dir>/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/<deployment_id>)
: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.

View File

@@ -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"],