
Sometimes the current script can trigger an issue while restarting horizon so this commit updates the test patches script to use a simpler script. Test Plan: PASS: build test patch and check the restart script inside the patch file was updated. PASS: Applied test patch on latest build Closes-bug: 2031330 Signed-off-by: Luis Sampaio <luis.sampaio@windriver.com> Change-Id: Ia907d8b95526c35ba5992d7106178bf480c97986
514 lines
20 KiB
Python
Executable File
514 lines
20 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright (c) 2023 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
"""
|
|
Debian Build Test Patches:
|
|
|
|
Default option (type=default) builds 4 patches using the logmgmt package:
|
|
PATCH A) Reboot required - all nodes
|
|
Update package - logmgmt
|
|
rebuild the pkg
|
|
build-image to generate a new commit in the build ostree_repo
|
|
build a patch
|
|
|
|
PATCH B) In Service patch - Example restart
|
|
Update the metadata
|
|
Uses the example-restart script
|
|
Uses the same ostree commit as PATCH A so they can't be applied together
|
|
build a patch
|
|
|
|
PATCH C) In Service patch - Restart failure
|
|
Update the metadata
|
|
Uses the restart-failure script
|
|
Uses the same ostree commit as PATCH A so they can't be applied together
|
|
build a patch
|
|
|
|
PATCH D) Patch with dependency (reboot required, depends on PATCH A)
|
|
build PATCH A
|
|
update package - logmgmt
|
|
build-image to generate a new commit in the build ostree_repo
|
|
build Patch C (requires A)
|
|
|
|
Kernel option (type=kernel) builds 1 patch after rebuilding the kernel:
|
|
PATCH E) Reboot required - all nodes
|
|
update kernel-std and kernel-rt
|
|
rebuild the packages linux and linux-rt
|
|
build-image to generate a new commit in the build ostree_repo
|
|
build a patch with new initramfs
|
|
build a patch reusing initramfs
|
|
|
|
Large option (type=large)
|
|
PATCH F) Reboot required - all nodes (Large Patch)
|
|
upverion all packages
|
|
rebuild all packages
|
|
build-image to generate a new commit in the build ostree_repo
|
|
build a patch with new initramfs
|
|
|
|
Steps to run:
|
|
# Setup debian build env
|
|
# For more information about how to setup the environment:
|
|
https://wiki.openstack.org/wiki/StarlingX/DebianBuildEnvironment
|
|
|
|
# Sample variables
|
|
export PROJECT="stx-debian-build"
|
|
export STX_BUILD_HOME="/localdisk/designer/${USER}/${PROJECT}"
|
|
export MY_REPO="/localdisk/designer/${USER}/${PROJECT}/repo/cgcs-root
|
|
# Initialize the build containers
|
|
stx control start
|
|
./build_test_patches.py --help
|
|
|
|
"""
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import yaml
|
|
import xml.etree.ElementTree as ET
|
|
|
|
logging.basicConfig(
|
|
level=logging.DEBUG,
|
|
format='%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s',
|
|
datefmt='%Y-%m-%d %H:%M:%S',
|
|
)
|
|
log = logging.getLogger('build_test_patches')
|
|
|
|
SAMPLE_RR_XML = "patch_recipe_rr_sample.xml"
|
|
SAMPLE_INSVC_XML = "patch_recipe_insvc_sample.xml"
|
|
|
|
# IN Service restart scripts
|
|
RESTART_SCRIPT = "patch-scripts/EXAMPLE_0002/scripts/example-cgcs-patch-restart"
|
|
RESTART_FAILURE_SCRIPT = "patch-scripts/test-patches/INSVC_RESTART_FAILURE/scripts/restart-failure"
|
|
|
|
|
|
def run_cmd(cmd):
|
|
'''
|
|
Run a cmd and return
|
|
param command: string representing the command to be executed
|
|
'''
|
|
log.debug("Running: %s", cmd)
|
|
return subprocess.run(
|
|
cmd,
|
|
shell=True,
|
|
executable='/bin/bash',
|
|
check=True)
|
|
|
|
|
|
class TestPatchInitException(Exception):
|
|
"""TestPatch initialization error"""
|
|
|
|
|
|
class TestPatchCreationException(Exception):
|
|
"""Patch creation error"""
|
|
|
|
|
|
class TestPatchBuilder():
|
|
"""
|
|
Build test patches
|
|
"""
|
|
|
|
def __init__(self, sw_version, time_out):
|
|
try:
|
|
self.project = os.environ.get("PROJECT")
|
|
self.build_home = os.environ.get("STX_BUILD_HOME")
|
|
self.deploy_dir = os.path.join(os.environ.get("STX_BUILD_HOME"), "localdisk", "deploy")
|
|
self.repo_root = os.environ.get("MY_REPO")
|
|
self.stx_tools = os.path.join(self.repo_root, "..", "stx-tools")
|
|
self.patch_repo_base = os.path.join(self.repo_root, "stx", "update")
|
|
self.patch_tools_dir = os.path.join(self.patch_repo_base, "sw-patch", "cgcs-patch", "cgcs_make_patch")
|
|
self.sw_version = sw_version
|
|
self.time_out = time_out
|
|
except TestPatchInitException:
|
|
log.exception("TestPatchBuilder initialization failure")
|
|
sys.exit(1)
|
|
|
|
def __upversion_pkg(self, pkg_dir):
|
|
"""
|
|
Update package version using stx_patch
|
|
"""
|
|
log.info("Upversioning package %s", pkg_dir)
|
|
pwd = os.getcwd()
|
|
os.chdir(pkg_dir)
|
|
meta_data_path = os.path.join(pkg_dir, "debian", "meta_data.yaml")
|
|
with open(meta_data_path) as f:
|
|
meta_data = yaml.safe_load(f)
|
|
|
|
if "revision" in meta_data:
|
|
if "stx_patch" in meta_data["revision"]:
|
|
meta_data["revision"]["stx_patch"] += 1
|
|
else:
|
|
meta_data["revision"]["stx_patch"] = 1
|
|
|
|
# Save updated meta_data.yaml
|
|
with open(meta_data_path, "w") as f:
|
|
yaml.dump(meta_data, f)
|
|
|
|
os.chdir(pwd)
|
|
|
|
def delete_ostree_prepatch(self, dir_name):
|
|
"""
|
|
Deletes ostree_repo prepatch generated during prepare
|
|
"""
|
|
cmd = f'''
|
|
source import-stx
|
|
stx shell --container lat -c \
|
|
"cd \\$DEPLOY_DIR; rm -rf {dir_name}"
|
|
'''
|
|
ret = run_cmd(cmd)
|
|
log.info("Clean up ostree prepatch directory returned %s", ret.returncode)
|
|
if ret.returncode != 0:
|
|
raise Exception("Failed to delete directory")
|
|
|
|
def build_pkg(self, pkg_name=None):
|
|
"""
|
|
Build package(s)
|
|
"""
|
|
extra_args = ''
|
|
if self.time_out:
|
|
extra_args = f'export REPOMGR_REQ_TIMEOUT_FACTOR={self.time_out};'
|
|
|
|
if pkg_name:
|
|
cmd = f'''
|
|
source import-stx
|
|
stx shell -c "{extra_args}build-pkgs -c -p {pkg_name}"
|
|
'''
|
|
else:
|
|
cmd = f'''
|
|
source import-stx
|
|
stx shell -c "{extra_args}build-pkgs --parallel 10"
|
|
'''
|
|
ret = run_cmd(cmd)
|
|
log.info("Build pkgs return code %s", ret.returncode)
|
|
if ret.returncode != 0:
|
|
raise Exception("Failed to build packages")
|
|
|
|
def build_image(self):
|
|
"""
|
|
Build image - generates new ostree commit
|
|
"""
|
|
cmd = '''
|
|
source import-stx
|
|
stx shell -c "build-image --keep"
|
|
'''
|
|
ret = run_cmd(cmd)
|
|
log.info("Build image return code %s", ret.returncode)
|
|
if ret.returncode != 0:
|
|
raise Exception("Failed to build image")
|
|
|
|
# Update gpg
|
|
log.info("Updating gpg settings")
|
|
self.add_gpg_pinentry()
|
|
|
|
def update_logmgmt_pkg(self, pname):
|
|
"""
|
|
Make a change on the logmgmt package and upversions it
|
|
param pname: patch name that is added to the script and can be used as patch validation
|
|
"""
|
|
pkg_name = "logmgmt"
|
|
log.info("Updating package %s", pkg_name)
|
|
pkg_dir = os.path.join(self.repo_root, "stx/utilities/utilities", pkg_name)
|
|
pkg_script = os.path.join(pkg_dir, "scripts/init.d/logmgmt")
|
|
# Insert a message into /etc/init.d/$(basename $SCRIPT)
|
|
cmd = "sed -i 's|start).*|start) logger -t \\$(basename \\$0) \"" + pname + " patch is applied\"|' " + pkg_script
|
|
run_cmd(cmd)
|
|
self.__upversion_pkg(pkg_dir)
|
|
# build the pkg to apply the change
|
|
self.build_pkg(pkg_name)
|
|
|
|
def prepare_env(self, ostree_clone_name):
|
|
"""
|
|
Generates ostree_repo snapshot before creating a patch
|
|
It executes the command inside the LAT container
|
|
"""
|
|
cmd = f'''
|
|
source import-stx
|
|
stx shell --container lat -c \
|
|
"cd \\$PATCH_TOOLS; python3 make_patch.py prepare --clone-repo {ostree_clone_name}"
|
|
'''
|
|
ret = run_cmd(cmd)
|
|
log.info("Patch prepare return code %s", ret.returncode)
|
|
if ret.returncode != 0:
|
|
raise Exception("Failed to run patch prepare")
|
|
|
|
def create_patch_xml(self, patch_id, sw_version, require_id, reboot=True, insvc_script=None):
|
|
"""
|
|
Create patch xml at the patch_tools_dir
|
|
param patch_id: patch name/id
|
|
param sw_version: software version, e.g: 21.12
|
|
param required_id: patch id for prereq patch
|
|
param reboot: reboot required or insvc patch
|
|
param insvc_script: full path to restart script
|
|
|
|
return: file name
|
|
"""
|
|
os.chdir(self.patch_tools_dir)
|
|
|
|
tree = ET.parse(SAMPLE_RR_XML) if reboot else ET.parse(SAMPLE_INSVC_XML)
|
|
metadata = tree.find("METADATA")
|
|
metadata.find("ID").text = patch_id
|
|
metadata.find("SW_VERSION").text = sw_version
|
|
|
|
if require_id:
|
|
requires_tag = metadata.find("REQUIRES")
|
|
reqid_tag = ET.SubElement(requires_tag, "ID")
|
|
reqid_tag.text = require_id
|
|
|
|
if not reboot and insvc_script:
|
|
# Copy restart script to localdisk/deploy and update path
|
|
shutil.copy2(insvc_script, self.deploy_dir)
|
|
metadata.find("RESTART_SCRIPT").text = os.path.join("/localdisk", "deploy", os.path.basename(insvc_script))
|
|
|
|
file_name = f"{patch_id}.xml"
|
|
tree.write(file_name)
|
|
os.chdir(self.stx_tools)
|
|
return file_name
|
|
|
|
def make_patch_lat(self, xml_path, ostree_clone, formal=False, reuse_initramfs=True):
|
|
"""
|
|
Calls the make_patch utility inside LAT container to generate our patch
|
|
param xml_path: path to the patch recipe/xml
|
|
param ostree_clone: path to the ostree_clone repo generated during the prepare stage
|
|
"""
|
|
ostree_clone_lat = os.path.join("/localdisk", "deploy", ostree_clone)
|
|
delta_dir_lat = os.path.join("/localdisk", "deploy", "delta-dir")
|
|
|
|
if not reuse_initramfs:
|
|
reuse_env_var = "export NO_REUSE_INITRAMFS=True;"
|
|
else:
|
|
reuse_env_var = ""
|
|
|
|
cmd = f'''
|
|
source import-stx
|
|
stx shell --container lat -c "cd \\$PATCH_TOOLS; {reuse_env_var} python3 make_patch.py create \
|
|
--patch-recipe {xml_path} --clone-repo {ostree_clone_lat}/ \
|
|
--delta-dir {delta_dir_lat}"
|
|
'''
|
|
if formal:
|
|
index = cmd.find("--clone-repo")
|
|
cmd = f"{cmd[:index]} --formal {cmd[index:]}"
|
|
|
|
ret = run_cmd(cmd)
|
|
log.info("Patch create return code %s", ret.returncode)
|
|
if ret.returncode != 0:
|
|
raise Exception("Failed to create patch")
|
|
|
|
def pull_local_patch_repo(self):
|
|
cmd = f'''
|
|
source import-stx
|
|
stx shell --container lat -c "cd \\$DEPLOY_DIR; \
|
|
ostree --repo=ostree_repo pull-local \
|
|
{os.path.join('patch_work', 'patch_repo')} starlingx; \
|
|
ostree --repo=ostree_repo summary --update"
|
|
'''
|
|
ret = run_cmd(cmd)
|
|
if ret.returncode != 0:
|
|
raise Exception("Failed to pull ostree from patch_repo process returned non-zero exit status %i", ret.returncode)
|
|
|
|
def add_gpg_pinentry(self):
|
|
'''
|
|
Configures the gpg pinentry settings used by ostree commit
|
|
'''
|
|
lat_yaml = os.path.join(
|
|
self.repo_root,
|
|
"..",
|
|
"stx-tools/debian-mirror-tools/config/debian/common",
|
|
"base-bullseye.yaml")
|
|
with open(lat_yaml) as f:
|
|
data = yaml.safe_load(f)
|
|
gpg_id = data["gpg"]["ostree"]["gpgid"]
|
|
gpg_pass = data["gpg"]["ostree"]["gpg_password"]
|
|
|
|
cmd = f'''
|
|
source import-stx
|
|
stx shell --container lat -c ' \
|
|
echo "allow-loopback-pinentry" > /tmp/.lat_gnupg_root/gpg-agent.conf; \
|
|
echo "default-cache-ttl 34560000" >> /tmp/.lat_gnupg_root/gpg-agent.conf; \
|
|
echo "max-cache-ttl 34560000" >> /tmp/.lat_gnupg_root/gpg-agent.conf; \
|
|
gpg-connect-agent --homedir /tmp/.lat_gnupg_root reloadagent /bye; \
|
|
gpg --homedir=/tmp/.lat_gnupg_root -o /dev/null -u {gpg_id} --pinentry=loopback --passphrase {gpg_pass} -s /dev/null;
|
|
'
|
|
'''
|
|
ret = run_cmd(cmd)
|
|
if ret.returncode != 0:
|
|
raise Exception("Failed to set gpg pinentry %i", ret.returncode)
|
|
|
|
def create_test_patches(self, pname, requires=False, inservice=False, formal=False):
|
|
"""
|
|
Creates test patches:
|
|
RR, INSVC and RR_Requires
|
|
param pname: Patch ID and file name
|
|
param requires: If set it will build the 2nd patch
|
|
param inservice: If set it will build the insvc patch
|
|
param formal: Signs the patch with formal key
|
|
"""
|
|
ostree_clone_name = "ostree_repo_patch"
|
|
os.chdir(self.stx_tools)
|
|
# Generating ostree_repo clone
|
|
self.prepare_env(ostree_clone_name)
|
|
# Update pkg
|
|
self.update_logmgmt_pkg(pname)
|
|
log.info("Generating Reboot required patch")
|
|
# build image to trigger a new ostree commit
|
|
self.build_image()
|
|
|
|
rr_patch_name = pname + "_RR_ALL_NODES"
|
|
rr_xml_path = self.create_patch_xml(rr_patch_name, self.sw_version, None)
|
|
|
|
# In service patch
|
|
if inservice:
|
|
insvc_patch_name = pname + "_NRR_INSVC"
|
|
insvc_script_path = os.path.join(self.patch_repo_base, RESTART_SCRIPT)
|
|
insvc_xml_path = self.create_patch_xml(insvc_patch_name, self.sw_version, None, reboot=False, insvc_script=insvc_script_path)
|
|
# build patch
|
|
log.info("Creating inservice sample restart patch %s", insvc_patch_name)
|
|
log.info("restart script %s", insvc_script_path)
|
|
self.make_patch_lat(insvc_xml_path, ostree_clone_name, formal)
|
|
log.info("Inservice sample restart patch build done")
|
|
|
|
# Restart failure
|
|
insvc_patch_name = pname + "_RESTART_FAILURE_INSVC"
|
|
insvc_script_path = os.path.join(self.patch_repo_base, RESTART_FAILURE_SCRIPT)
|
|
insvc_xml_path = self.create_patch_xml(insvc_patch_name, self.sw_version, None, reboot=False, insvc_script=insvc_script_path)
|
|
# build patch
|
|
log.info("Creating inservice restart failure patch %s", insvc_patch_name)
|
|
log.info("restart script %s/%s", insvc_script_path)
|
|
self.make_patch_lat(insvc_xml_path, ostree_clone_name, formal)
|
|
log.info("Inservice restart failure patch build done")
|
|
|
|
# RR Patch
|
|
log.info("Creating RR patch %s", rr_xml_path)
|
|
self.make_patch_lat(rr_xml_path, ostree_clone_name, formal)
|
|
log.info("RR Patch build done")
|
|
|
|
# Cleans up the ostree_clone to generate a new one for the requires patch
|
|
self.delete_ostree_prepatch(ostree_clone_name)
|
|
|
|
if requires:
|
|
# Build the 2nd patch which will follow similar steps but will set the requires flag
|
|
# If re-using initramfs it needs to pull the previous patch commit into ostree_repo
|
|
rr_req_patch_name = pname + "_RR_ALL_NODES_REQUIRES"
|
|
rr_req_xml_path = self.create_patch_xml(rr_req_patch_name, self.sw_version, rr_patch_name)
|
|
|
|
self.prepare_env(ostree_clone_name)
|
|
# Update pkg
|
|
self.update_logmgmt_pkg(rr_req_patch_name)
|
|
# build image to trigger a new ostree commit
|
|
self.build_image()
|
|
# Create a patch
|
|
log.info("Creating RR Requires patch %s", rr_req_patch_name)
|
|
self.make_patch_lat(rr_req_xml_path, ostree_clone_name, formal)
|
|
log.info("Requires patch build done")
|
|
self.delete_ostree_prepatch(ostree_clone_name)
|
|
|
|
def create_kernel_patch(self, sw_version, formal=False):
|
|
'''
|
|
Upversion and rebuilds the kernel
|
|
param sw_version: software version, e.g 22.12
|
|
param formal: Signs the patch with formal key
|
|
'''
|
|
ostree_clone_name = "ostree_repo_patch"
|
|
os.chdir(self.patch_tools_dir)
|
|
# Create patch recipe/xml
|
|
patch_name = sw_version + "_KERNEL"
|
|
kernel_patch_xml = self.create_patch_xml(patch_name, self.sw_version, None)
|
|
kernel_patch_reuse_xml = self.create_patch_xml(patch_name + "_REUSE", self.sw_version, None)
|
|
|
|
os.chdir(self.stx_tools)
|
|
# Generating ostree_repo clone
|
|
self.prepare_env(ostree_clone_name)
|
|
# Update pkg
|
|
kernel_std_dir = os.path.join(self.repo_root, "stx/kernel", "kernel-std")
|
|
kernel_rt_dir = os.path.join(self.repo_root, "stx/kernel", "kernel-rt")
|
|
self.__upversion_pkg(kernel_std_dir)
|
|
self.__upversion_pkg(kernel_rt_dir)
|
|
log.info("Generating Kernel patch")
|
|
# Rebuild the kernel
|
|
self.build_pkg("linux")
|
|
self.build_pkg("linux-rt")
|
|
# build image to trigger a new ostree commit
|
|
self.build_image()
|
|
|
|
# Create a patch
|
|
log.info("Creating Kernel patch %s", kernel_patch_xml)
|
|
# Create patch with new initramfs
|
|
self.make_patch_lat(kernel_patch_xml, ostree_clone_name, formal, reuse_initramfs=False)
|
|
# Create patch reusing initramfs
|
|
self.make_patch_lat(kernel_patch_reuse_xml, ostree_clone_name, formal)
|
|
log.info("Kernel patch build done")
|
|
self.delete_ostree_prepatch(ostree_clone_name)
|
|
|
|
def create_large_patch(self, sw_version, formal=False):
|
|
'''
|
|
Upversion all available packages and creates a patch
|
|
This step takes time as all packages are rebuilt
|
|
param sw_version: software version, e.g 22.12
|
|
param formal: Signs the patch with formal key
|
|
'''
|
|
log.info("Creating Large Patch")
|
|
|
|
ostree_clone_name = "ostree_repo_patch"
|
|
os.chdir(self.patch_tools_dir)
|
|
# Create patch recipe/xml
|
|
patch_name = sw_version + "_LARGE"
|
|
large_patch_xml = self.create_patch_xml(patch_name, self.sw_version, None)
|
|
|
|
os.chdir(self.repo_root)
|
|
file_list = []
|
|
for root, dirs, files in os.walk(os.getcwd()):
|
|
for file in files:
|
|
if file == "meta_data.yaml":
|
|
file_list.append(os.path.join(root, file))
|
|
|
|
log.info("Total files found %s" % len(file_list))
|
|
log.info("Upversioning all packages")
|
|
for f in file_list:
|
|
pkg_dir = f.split("/debian/")[0]
|
|
self.__upversion_pkg(pkg_dir)
|
|
|
|
os.chdir(self.stx_tools)
|
|
# Generating ostree_repo clone
|
|
self.prepare_env(ostree_clone_name)
|
|
# Rebuild the packages
|
|
self.build_pkg()
|
|
# build image to trigger a new ostree commit
|
|
self.build_image()
|
|
# Create a patch
|
|
log.info("large patch xml %s", large_patch_xml)
|
|
# Create patch with new initramfs
|
|
self.make_patch_lat(large_patch_xml, ostree_clone_name, formal, reuse_initramfs=False)
|
|
log.info("Large patch build done")
|
|
self.delete_ostree_prepatch(ostree_clone_name)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="Debian build_test_patches")
|
|
|
|
parser.add_argument("-t", "--type", default="default", type=str, help="Default (logmgmt patches), kernel or large")
|
|
parser.add_argument("-sw", "--software-version", type=str, help="Patch Software version, will prefix the patch name", default=None, required=True)
|
|
parser.add_argument("-r", "--requires", action="store_true", help="Builds the 2nd patch which requires the rr_patch")
|
|
parser.add_argument("-i", "--inservice", action="store_true", help="Builds the in service patch")
|
|
parser.add_argument("-f", "--formal", action="store_true", help="Signs the patch with formal key")
|
|
parser.add_argument("-o", "--time-out", type=str, help="Set the timeout multiplier. \
|
|
For example: set it to 5 can increase the timeout value by 5 times", default=None)
|
|
args = parser.parse_args()
|
|
log.debug("Args: %s", args)
|
|
|
|
try:
|
|
log.info("Building test patches")
|
|
sw_version = args.software_version
|
|
test_patch_builder = TestPatchBuilder(args.software_version, args.time_out)
|
|
if args.type == "default":
|
|
test_patch_builder.create_test_patches(sw_version, args.requires, args.inservice, args.formal)
|
|
elif args.type == "kernel":
|
|
test_patch_builder.create_kernel_patch(sw_version, args.formal)
|
|
elif args.type == "large":
|
|
test_patch_builder.create_large_patch(sw_version, args.formal)
|
|
|
|
log.info("Test patch build completed")
|
|
except TestPatchCreationException:
|
|
log.exception("Error while creating test patches")
|