distcloud/distributedcloud/dccommon/ostree_mount.py
Kyle MacLeod 93e1012af8 Prestaging support for --for-sw-deploy/--for-install
Add USM prestaging support. This commit introduces support for the
--for-sw-deploy and --for-install prestaging options. The --for-install
option is the default, and is the equivalent of the previous release
prestaging behaviour - the subcloud ostree repo is prestaged to the
platform-backup partition. The --for-sw-deploy option is the new
prestaging behaviour for USM major and minor releases. This commit
focuses on prestaging minor release updates.

The --for-sw-deploy option requires synchronization of ostree commits
and /opt/software/metadata contents from the system controller to the
subcloud.

Notes on --release YY.MM and --release YY.MM.nn handling:
- The --release value can either be a major release, in format YY.MM, or
  a specific minor release, in format YY.MM.nn.
- If format is YY.MM, the behaviour is to prestage ALL minor releases
  present on the system controller to the subcloud.
- If format is YY.MM.nn, the behaviour is to prestage ONLY the given
  minor release
    - Note: there is no sanity checking for dependent minor updates.
      If update YY.MM.nn is given but YY.MM.nn-1 is not present, the
      operation is currently allowed. This will be addressed in a future
      commit (see TODO below)
- TODO: Future commit will expand on this behaviour for YY.MM.nn to
  prestage all missing commits up to and including the YY.MM.nn commit

This update also fixes the /opt/platform/iso/YY.MM/ostree_repo bind
mount if it has not already been established via a subcloud install.

Test Cases
PASS
- For prestaging operation, initiate ansible playbook with correct
  values of for_install / for_sw_deploy variables, and correct
  software_version / software_major_release values corresponding to the
  values input by the client.
- Verify that a stale or missing /opt/platform/iso/YY.MM/ostree_repo
  bind mount is re-established upon start of prestage operation.

Story: 2010676
Task: 50322

Signed-off-by: Kyle MacLeod <kyle.macleod@windriver.com>
Change-Id: I5cc08499e44bcb66eecdb28dc227cc032ee9890a
2024-06-18 23:31:02 -04:00

105 lines
3.6 KiB
Python

# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
import os
from oslo_log import log as logging
import sh
from dccommon import consts
from dcmanager.common import utils
# The 'sh' library is magical - it looks up CLI functions dynamically.
# Disable the pylint warnings here:
# pylint: disable=not-callable,no-member
LOG = logging.getLogger(__name__)
def check_stale_bind_mount(mount_path, source_path):
"""Check if the mount has become stale.
We do this by comparing the directory inodes. If the bind mount is
valid, the two directories should have the same inode number; otherwise
the original directory has been replaced and we are no longer tracking
the actual location of source_path. In this case we teardown the bind
mount.
"""
mount_path_inode = sh.stat("--format", "%i", mount_path)
source_path_inode = sh.stat("--format", "%i", source_path)
if mount_path_inode != source_path_inode:
failure_prefix = f"Failed to repair bind mount at {mount_path}"
LOG.error(f"Found stale bind mount: {mount_path}: attempting repair")
try:
sh.umount(mount_path)
except sh.ErrorReturnCode_32:
# Exit code 32 is "mount failure"
# Log the exception, but proceed with the rmdir, allowing a
# remount attempt
LOG.exception(f"{failure_prefix}: unmount failed (continuing)")
except Exception:
LOG.error(f"{failure_prefix}: unexpected umount failure")
raise
try:
os.rmdir(mount_path)
except Exception:
LOG.error(f"{failure_prefix}: rmdir failed")
raise
return True
return False
# TODO(kmacleod): utils.synchronized should be moved into dccommon
@utils.synchronized("ostree-mount-subclouds", external=True)
def validate_ostree_iso_mount(software_version):
"""Ensure the ostree_repo is properly mounted under the iso path.
Validity check includes if the mount is stale.
If stale, the bind mount is recreated.
Note that ostree_repo is mounted in a location not specific to a subcloud.
"""
ostree_repo_mount_path = os.path.join(
consts.SUBCLOUD_ISO_PATH, software_version, "ostree_repo"
)
ostree_repo_source_path = os.path.join(
consts.SUBCLOUD_FEED_PATH,
"rel-{version}".format(version=software_version),
"ostree_repo",
)
LOG.debug(
"Checking ostree_repo mount: %s against %s",
ostree_repo_mount_path,
ostree_repo_source_path,
)
if os.path.exists(ostree_repo_mount_path):
check_stale_bind_mount(ostree_repo_mount_path, ostree_repo_source_path)
# Check for the config file inside the ostree_repo
check_path = os.path.join(ostree_repo_mount_path, "config")
if not os.path.exists(check_path):
LOG.info("Mounting ostree_repo at %s", ostree_repo_mount_path)
if not os.path.exists(ostree_repo_mount_path):
os.makedirs(ostree_repo_mount_path, mode=0o755)
mount_args = (
"--bind",
ostree_repo_source_path,
ostree_repo_mount_path,
)
try:
sh.mount(*mount_args)
except Exception as exc:
LOG.warn(
f"Command 'mount {' '.join(mount_args)}' failed; "
f"attempting to rebuild: {str(exc)}"
)
try:
sh.umount(ostree_repo_mount_path)
except Exception:
LOG.exception("rebuild: umount failed, continuing")
os.rmdir(ostree_repo_mount_path)
os.makedirs(ostree_repo_mount_path, mode=0o755)
sh.mount(*mount_args)