#!/usr/bin/python3 # -*- encoding: utf-8 -*- # # vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright (c) 2023-2024 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # """ This script is run during 'software upload' command. It is used to copy the required files from uploaded iso image to the controller. """ import argparse import configparser import glob import logging import os import shutil import subprocess import sys import upgrade_utils LOG = logging.getLogger('main_logger') AVAILABLE_DIR = "/opt/software/metadata/available" UNAVAILABLE_DIR = "/opt/software/metadata/unavailable" COMMITTED_DIR = "/opt/software/metadata/committed" PATCHING_COMMITTED_DIR = "/opt/patching/metadata/committed" FEED_OSTREE_BASE_DIR = "/var/www/pages/feed" RELEASE_GA_NAME = "starlingx-%s" SOFTWARE_STORAGE_DIR = "/opt/software" TMP_DIR = "/tmp" VAR_PXEBOOT_DIR = "/var/pxeboot" FEED_REMOTE = "starlingx" FEED_BRANCH = "starlingx" def setup_from_release_load(from_release, to_feed_dir): """ Setup from release load :param from_release: from release version :param to_feed_dir: to release feed directory """ # 'None' is passed from this script argument if from_release == 'None': LOG.info("From release is not specified. Skipping from release load") return try: from_major_rel = upgrade_utils.get_major_release_version(from_release) # Copy install_uuid to /var/www/pages/feed/rel- from_feed_dir = os.path.join(FEED_OSTREE_BASE_DIR, ("rel-%s" % from_major_rel)) shutil.copyfile(os.path.join(from_feed_dir, "install_uuid"), os.path.join(to_feed_dir, "install_uuid")) LOG.info("Copied install_uuid to %s", to_feed_dir) # Copy pxeboot-update-${from_major_release}.sh to from-release feed /upgrades from_iso_upgrades_dir = os.path.join(from_feed_dir, "upgrades") os.makedirs(from_iso_upgrades_dir, exist_ok=True) shutil.copyfile(os.path.join("/etc", "pxeboot-update-%s.sh" % from_major_rel), os.path.join(from_iso_upgrades_dir, "pxeboot-update-%s.sh" % from_major_rel)) LOG.info("Copied pxeboot-update-%s.sh to %s", from_major_rel, from_iso_upgrades_dir) # Copy pxelinux.cfg.files to from-release feed /pxeboot from_feed_pxeboot_dir = os.path.join(from_feed_dir, "pxeboot") os.makedirs(from_feed_pxeboot_dir, exist_ok=True) # Find from-release pxelinux.cfg.files pxe_dir = os.path.join(VAR_PXEBOOT_DIR, "pxelinux.cfg.files") from_pxe_files = glob.glob(os.path.join(pxe_dir, '*' + from_major_rel)) for from_pxe_file in from_pxe_files: if os.path.isfile(from_pxe_file): shutil.copyfile(from_pxe_file, os.path.join(from_feed_pxeboot_dir, os.path.basename(from_pxe_file))) LOG.info("Copied %s to %s", from_pxe_file, from_feed_pxeboot_dir) except Exception: raise def load_import(from_release, to_release, iso_mount_dir): """ Import the iso files to the feed and pxeboot directories :param from_release: from release version (MM.mm/MM.mm.p) :param to_release: to release version (MM.mm.p) :param iso_mount_dir: iso mount dir """ to_major_rel = upgrade_utils.get_major_release_version(to_release) to_feed_dir = os.path.join(FEED_OSTREE_BASE_DIR, ("rel-%s" % to_major_rel)) try: # Copy the iso file to /var/www/pages/feed/rel- os.makedirs(FEED_OSTREE_BASE_DIR, exist_ok=True) if os.path.exists(to_feed_dir): shutil.rmtree(to_feed_dir) LOG.info("Removed existing %s", to_feed_dir) os.makedirs(to_feed_dir, exist_ok=True) feed_contents = ["install_uuid", "efi.img", "kickstart", "ostree_repo", "pxeboot", "upgrades"] for content in feed_contents: src_abs_path = os.path.join(iso_mount_dir, content) if os.path.isfile(src_abs_path): shutil.copyfile(src_abs_path, os.path.join(to_feed_dir, content)) LOG.info("Copied %s to %s", src_abs_path, to_feed_dir) elif os.path.isdir(src_abs_path): shutil.copytree(src_abs_path, os.path.join(to_feed_dir, content,), symlinks=True) LOG.info("Copied %s to %s", src_abs_path, to_feed_dir) # Add min-free-space-percent to feed ostree config file config_path = os.path.join(to_feed_dir, "ostree_repo/config") if os.path.exists(config_path): config = configparser.ConfigParser() config.read(config_path) config.set("core", "min-free-space-percent", "0") with open(config_path, 'w') as file: config.write(file, space_around_delimiters=False) # Create 'starlingx' remote on the feed ostree_repo cmd = ["ostree", "remote", "add", "--no-gpg-verify", "--repo=%s/ostree_repo/" % to_feed_dir, FEED_REMOTE, "http://controller:8080/feed/rel-%s/ostree_repo/" % to_major_rel, FEED_BRANCH] try: subprocess.check_call(cmd) LOG.info("Created feed remote '%s'" % FEED_REMOTE) except subprocess.CalledProcessError as e: LOG.exception("Feed remote '%s' creation failed. Error: %s" % (FEED_REMOTE, str(e))) raise # Converted from upgrade package extraction script shutil.copyfile(os.path.join(to_feed_dir, "kickstart", "kickstart.cfg"), os.path.join(to_feed_dir, "kickstart.cfg")) # Copy bzImage and initrd bzimage_files = glob.glob(os.path.join(to_feed_dir, 'pxeboot', 'bzImage*')) for bzimage_file in bzimage_files: if os.path.isfile(bzimage_file): shutil.copyfile(bzimage_file, os.path.join(VAR_PXEBOOT_DIR, os.path.basename(bzimage_file))) LOG.info("Copied %s to %s", bzimage_file, VAR_PXEBOOT_DIR) initrd_files = glob.glob(os.path.join(to_feed_dir, 'pxeboot', 'initrd*')) for initrd_file in initrd_files: if os.path.isfile(initrd_file): shutil.copyfile(initrd_file, os.path.join(VAR_PXEBOOT_DIR, os.path.basename(initrd_file))) LOG.info("Copied %s to %s", initrd_file, VAR_PXEBOOT_DIR) # Copy pxeboot-update.sh to /etc pxeboot_update_filename = "pxeboot-update-%s.sh" % to_major_rel shutil.copyfile(os.path.join(to_feed_dir, "upgrades", pxeboot_update_filename), os.path.join("/etc", pxeboot_update_filename)) os.chmod(os.path.join("/etc", pxeboot_update_filename), mode=0o755) LOG.info("Copied pxeboot-update-%s.sh to %s", to_major_rel, "/etc") # Setup from release load setup_from_release_load(from_release, to_feed_dir) except Exception as e: LOG.exception("Load import failed. Error: %s" % str(e)) shutil.rmtree(to_feed_dir) LOG.info("Removed %s", to_feed_dir) raise def move_metadata_file_to_target_dir(to_release, iso_mount_dir, target_dir): """ Copy release metadata file to target_dir :param to_release: release version :param iso_mount_dir: iso mount dir :param target_dir: target directory the metadata file moves to """ try: # Copy all *-metadata.xml to target_dir os.makedirs(target_dir, exist_ok=True) for metadata_file in glob.glob(os.path.join(iso_mount_dir, "patches", "*-metadata.xml")): metadata_name = os.path.basename(metadata_file) shutil.copyfile(metadata_file, os.path.join(target_dir, metadata_name)) LOG.info("Copied %s to %s", metadata_name, target_dir) except shutil.Error: LOG.exception("Failed to copy the %s metadata files to %s" % (to_release, target_dir)) raise def sync_inactive_load_standby_controller(to_release): """ Sync inactive load to controller-1 Upload is only allowed in controller-0 so sync to controller-1 is needed :param to_release: release version """ to_major_rel = upgrade_utils.get_major_release_version(to_release) feed_dir = os.path.join(FEED_OSTREE_BASE_DIR, ("rel-%s" % to_major_rel)) sync_cmd = [ "rsync", "-ac", "--delete", "--exclude", "tmp", feed_dir, "rsync://controller-1/feed"] LOG.info("Syncing inactive load to controllers %s", ' '.join(sync_cmd)) subprocess.run(sync_cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, check=True, text=True) LOG.info("Sync controllers completed") def main(): parser = argparse.ArgumentParser( description="Import files from uploaded iso image to controller.", epilog="Use %(prog)s -h for help.", ) parser.add_argument( "--from-release", required=True, help="The from-release version.", ) parser.add_argument( "--to-release", required=True, help="The to-release version, MM.mm.p", ) parser.add_argument( "--iso-dir", required=True, help="The mounted iso image directory.", ) args = parser.parse_args() try: LOG.info("Load import from %s to %s started", args.from_release, args.to_release) load_import(args.from_release, args.to_release, args.iso_dir) if args.from_release in ['None', None]: # This is N-1 load move_metadata_file_to_target_dir(args.to_release, args.iso_dir, UNAVAILABLE_DIR) sync_inactive_load_standby_controller(args.to_release) else: move_metadata_file_to_target_dir(args.to_release, args.iso_dir, AVAILABLE_DIR) except Exception as e: LOG.exception(e) return 1 if __name__ == "__main__": upgrade_utils.configure_logging('/var/log/software.log', log_level=logging.INFO) sys.exit(main())