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 in 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.

See the header file of
playbookconfig/src/playbooks/roles/prestage/
    sync-software-metadata/tasks/main.yml
for a detailed overview of the algorithm to synchronize the ostree
commits and metadata from the system controller onto 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

Unit tests for ostree-metadata-sync.sh:
A local BATS unit test script is added to unit test the
ostree-metadata-sync.sh script. The unit tests must be run manually -
there is no zuul support for BATS testing. See the README.md file in the
test directory for information on running the tests for this file.

This commit also handles the metadata synchronization when a subcloud is
installed. In this case, the /opt/software/metadata is synchronized
during the subcloud install, so that when the subcloud pulls from the
system controller feed ostree repo, the subcloud is bootstrapped with
the correct /opt/software/metadata hierarchy.

NOTE: this commit also turns off line-length checking for bashate. We do
not enforce line-length for bash scripts in metal, since it arguably
makes it more difficult to write bash scripts (due to challenges with
quoting, etc.)

Test Cases
PASS
- Verify when --release YY.MM is given, all minor releases are included
  for synchronizaton to the subcloud
- Verify when there is only one release on system controller, the
  synchronization is skipped (there are no updates to synchronize)
- For prestaging a minor release, verify the minor release appears in
  the correct location/state onthe subcloud:

  | System Controller state | Subcloud state | Subcloud result |
  | ----------------------- | -------------- | --------------- |
  | available               | -              | available       |
  | deploying               | -              | available       |
  | deployed                | -              | available       |
  | deployed                | deployed       | deployed        |
  |                         |                |                 |

- Verify prestaging --for-install
    - verify that prestaging works as in previous release:
        - ostree_repo staged in /opt/platform-backup

Story: 2010676
Task: 50325

Signed-off-by: Kyle MacLeod <kyle.macleod@windriver.com>
Change-Id: I4c67f03d0cfcf60e1bf78fc4c80ec18271fc49c1
This commit is contained in:
Kyle MacLeod 2024-06-09 23:28:16 -04:00
parent 2091178a11
commit 311386a353
31 changed files with 1658 additions and 33 deletions

View File

@ -119,4 +119,5 @@
check_bootstrap_address: false
check_patches: false
sync_patch_metadata: true
sync_software_metadata: true
enforce_password_change: true

View File

@ -11,13 +11,13 @@
#
# Usage:
# ansible-playbook prestage_images.yml -i <inventory-file> \
# -e "software_version=<release-number> image_list_file=<images-file>"
# -e "software_version=<MM.YY.nn> software_major_release=<MM.YY> image_list_file=<images-file>"
#
# Images file is mandatory if the specified software version is greater
# than the current software version of the subcloud.
- hosts: all
gather_facts: no
gather_facts: false
vars:
prestage_type: "images"
@ -28,4 +28,4 @@
roles:
- prestage/prepare-env
- { role: prestage/prestage-images, become: yes }
- { role: prestage/prestage-images, become: true }

View File

@ -13,29 +13,35 @@
#
# Usage:
# ansible-playbook prestage_sw_packages.yml -i <inventory-file> \
# -e "software_version=<release-number>"
# -e "software_version=<MM.YY.nn> software_major_release=<MM.YY> prestage_install=<for_install|for_sw_deploy>"
- hosts: all
gather_facts: no
gather_facts: false
vars:
prestage_type: "packages"
prestage_install: for_install
platform_backup_dir: /opt/platform-backup
roles:
- role: prestage/prepare-env
- role: prestage/sync-software-metadata
when: prestage_install == 'for_sw_deploy' and prestage_sync_software_metadata_required
vars:
ansible_become_pass: "{{ ansible_ssh_pass }}"
- role: prestage/prestage-sw-packages
when: prestage_os_type == 'debian'
become: yes
when: prestage_install == 'for_install' and prestage_os_type == 'debian'
become: true
- role: prestage/prestage-sw-packages-centos
when: prestage_os_type == 'centos'
become: yes
when: prestage_install == 'for_install' and prestage_os_type == 'centos'
become: true
- role: prestage/prestage-patches
when: host_patches_exist
become: yes
when: software_major_release is version('24.09', '<') and prestage_install == 'for_install' and host_patches_exist
become: true
- role: prestage/get-prestage-versions
become: yes
become: true

View File

@ -0,0 +1,141 @@
---
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# ROLE DESCRIPTION:
# This role is for install only. It synchronizes the system controller
# /opt/software/metadata directory to the subcloud. This is only done if the
# system controller and subcloud are at the same ostree commit.
#
- name: Retrieve software version number
shell: source /etc/build.info; echo $SW_VERSION
register: sw_version
- name: Set software metadata synchronization facts
set_fact:
software_sync_required: false
ostree_feed_repo_dir: /var/www/pages/feed/rel-{{ sw_version.stdout }}/ostree_repo
ostree_sysroot_repo_dir: /sysroot/ostree/repo
ostree_rev: starlingx
software_dir: /opt/software
- block:
- name: Gather system controller ostree commit
command: ostree --repo={{ ostree_feed_repo_dir }} rev-parse {{ ostree_rev }}
register: ostree_commit_system_controller
delegate_to: localhost
- name: Gather subcloud ostree commit
command: ostree --repo={{ ostree_feed_repo_dir }} rev-parse {{ ostree_rev }}
register: ostree_commit_subcloud
- name: Gather system controller updates list
shell: software list | grep {{ sw_version.stdout }} | grep -E '(available|deployed|committed)'
register: system_controller_software_list
failed_when: false
delegate_to: localhost
- name: Gather subcloud software list
shell: software list | grep {{ sw_version.stdout }} | grep -E '(available|deployed|committed)'
register: subcloud_software_list
failed_when: false
- name: Decide if software metadata synchronization is required
# Conditions:
# 1) the system controller and subcloud have different results from 'software list'
# or
# 2) The subcloud is not be at the same ostree commit level as the system controller.
# This can happen if the subcloud is installed from ISO (release or prestage).
set_fact:
software_sync_required: true
when: (system_controller_software_list.stdout != subcloud_software_list.stdout)
and (ostree_commit_system_controller.stdout == ostree_commit_subcloud.stdout)
- debug:
msg:
- "Software sync required: {{ software_sync_required }}"
- "ostree revision from {{ ostree_feed_repo_dir }}:"
- "system controller: {{ ostree_commit_system_controller.stdout }}"
- "subcloud: {{ ostree_commit_subcloud.stdout }}"
- "Software list:"
- "system controller:"
- "{{ system_controller_software_list.stdout }}"
- "subcloud:"
- "{{ subcloud_software_list.stdout }}"
- debug:
msg:
- "Skipping software metadata synchronization."
when: not software_sync_required
- block:
- debug:
msg: "Synchronizing {{ software_dir }} metadata to subcloud..."
- name: Ensure subcloud {{ software_dir }} exists (sysadmin)
file:
path: "{{ software_dir }}"
owner: sysadmin
group: root
state: directory
mode: 0755
recurse: yes
become: true
- name: Synchronize software dir from system controller to subcloud
synchronize:
mode: push
src: "{{ software_dir }}/"
dest: "{{ software_dir }}/"
rsync_opts: "--delete"
register: software_transfer
retries: 2
delay: 5
until: software_transfer.rc == 0
- name: Restore root ownership to subcloud {{ software_dir }}
file:
path: "{{ software_dir }}"
state: directory
owner: root
recurse: yes
become: true
# TODO(kmacleod) This has to change. We'll have different versions for
# the upgrades in USM
# - name: Delete patch metadata files not belonging to the current software version
# ({{ sw_version.stdout }})
# shell: >
# grep -L "<sw_version>{{ sw_version.stdout }}</sw_version>" {{ item }}
# | xargs -I {} sh -c 'echo "$1"; rm -f "$1"' sh {}
# loop:
# - "{{ software_dir }}/metadata/available/*"
# - "{{ software_dir }}/metadata/deployed/*"
# - "{{ software_dir }}/metadata/committed/*"
# register: deleted_metadata_files
# become: true
#
# - name: Print deleted software metadata files
# debug:
# msg: "{{ deleted_metadata_files.results | map(attribute='stdout_lines')
# | flatten | join('\n') }}"
# when: deleted_metadata_files.results | map(attribute='stdout_lines')
# | flatten | length > 0
# Restart the software controller and agent to pickup the changes
- name: Restart the software controller and agent
systemd:
name: "{{ item }}"
state: restarted
with_items:
- software-controller-daemon
- software-agent
become: true
when: software_sync_required
when: sync_software_metadata and sw_version.stdout is version('24.03', '>=')

View File

@ -29,8 +29,8 @@
- name: Extract and sort version numbers
set_fact:
prestege_versions: "{{ parent_dirs | sort | join(',') }}"
prestage_versions: "{{ parent_dirs | sort | join(',') }}"
- name: Print prestage versions
debug:
msg: "prestage_versions: {{ prestege_versions }}"
msg: "prestage_versions: {{ prestage_versions }}"

View File

@ -9,36 +9,42 @@
# for packages/images prestaging.
#
- name: Fail if software version for images prestaging is not specified
- name: Fail if software version for prestaging is not specified
fail:
msg: "Please specify software version for prestaging."
when: software_version is not defined
- name: Fail if software major release for prestaging is not specified
fail:
msg: "Please specify software major version for prestaging."
when: software_major_release is not defined
- name: Get minimum supported release version
import_role:
name: common/minimum-upgradable-release
- name: Fail if software version is not supported for prestaging
- name: Fail if software major release is not supported for prestaging
fail:
msg: >-
The specified software version {{ software_version }} is not supported.
The specified software major release {{ software_major_release }} is not supported.
Minimum supported release versions for prestage: {{ minimum_supported_release_version }}
when: software_version is version(minimum_supported_release_version, "<")
when: software_major_release is version(minimum_supported_release_version, "<")
- name: Set prestage software version and cleanup directores fact
set_fact:
prestage_software_version: "{{ software_version }}"
cleanup_dirs: "{{ cleanup_dirs|default([]) + [software_version] }}"
cleanup_dirs: "{{ cleanup_dirs|default([]) + [software_major_release] }}"
- name: Set prestage_os_type
set_fact:
prestage_os_type: "{{ 'debian' if prestage_software_version is version('22.12', '>=') else 'centos' }}"
prestage_os_type: "{{ 'debian' if software_major_release is version('22.12', '>=') else 'centos' }}"
- name: Set prestage dir and subdirectory facts
set_fact:
prestage_source: remote
prestage_dir: "{{ platform_backup_dir }}/{{ prestage_software_version }}"
patches_prestage_dir: "{{ platform_backup_dir }}/{{ prestage_software_version }}/patches"
prestage_dir: "{{ platform_backup_dir }}/{{ software_major_release }}"
host_software_metadata_dir: "/opt/software/metadata"
prestage_sync_software_metadata_required: false
patches_prestage_dir: "{{ platform_backup_dir }}/{{ software_major_release }}/patches"
host_patch_metadata_dir: "/opt/patching/metadata"
- name: Retrieve current software version of the host
@ -53,7 +59,7 @@
- name: Change prestage_source to local
set_fact:
prestage_source: local
when: prestage_software_version == host_software_version
when: software_major_release == host_software_version
- name: Prepare prestaging packages
include_tasks: prepare-sw-packages-prestage-env.yml

View File

@ -1,6 +1,6 @@
---
#
# Copyright (c) 2023 Wind River Systems, Inc.
# Copyright (c) 2023-2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
@ -9,23 +9,27 @@
# packages/ostree_repo prestaging.
#
# When is prestage_source local vs. remote?
# - local: if we are prestaging the same software version as currently running on system controller
# - remote: if we are prestaging a different software version than currently running on system controller
- block:
- name: Set feed and patch directories to /www/pages/... (Centos and local source)
set_fact:
release_feed: "/www/pages/feed/rel-{{ prestage_software_version }}"
host_patch_repo: "/www/pages/updates/rel-{{ prestage_software_version }}"
release_feed: "/www/pages/feed/rel-{{ software_major_release }}"
host_patch_repo: "/www/pages/updates/rel-{{ software_major_release }}"
when: prestage_source == 'local'
- name: Set feed and patch directories to /var/www/pages/... (Centos and remote source)
set_fact:
release_feed: "/var/www/pages/feed/rel-{{ prestage_software_version }}"
host_patch_repo: "/var/www/pages/updates/rel-{{ prestage_software_version }}"
release_feed: "/var/www/pages/feed/rel-{{ software_major_release }}"
host_patch_repo: "/var/www/pages/updates/rel-{{ software_major_release }}"
when: prestage_source == 'remote'
when: prestage_os_type == 'centos'
- name: Set the feed directory to /var/www/pages/feed (Debian)
set_fact:
release_feed: "/var/www/pages/feed/rel-{{ prestage_software_version }}"
release_feed: "/var/www/pages/feed/rel-{{ software_major_release }}"
when: prestage_os_type == 'debian'
- name: Prepare prestage packages for Centos
@ -101,6 +105,30 @@
ostree_repo_release_feed: "{{ release_feed }}/ostree_repo"
- block:
- name: Check if {{ host_software_metadata_dir }} exists on {{ inventory_hostname }}
stat:
path: "{{ host_software_metadata_dir }}"
register: host_software_dir_result
- name: Set flag to indicate if the host software dir exist
set_fact:
host_software_exist: "{{ host_software_dir_result.stat.exists }}"
when: host_software_dir_result is not skipped
- name: Check if {{ host_software_metadata_dir }} exists on {{ inventory_hostname }}
stat:
path: "{{ host_software_metadata_dir }}"
register: host_software_dir_result
- name: Check if any minor upgrades have been applied
shell: software list --release {{ software_major_release }} | tail +4 | grep -c -E '^\| '
register: system_controller_software_num_releases
delegate_to: localhost
- name: Set flag to indicate if minor upgrades have been applied
set_fact:
prestage_sync_software_metadata_required: >-
"{{ true if system_controller_software_num_releases.stdout != '1' else false }}"
- name: Check if {{ host_patch_metadata_dir }} exists on {{ inventory_hostname }}
stat:
path: "{{ host_patch_metadata_dir }}"
@ -124,6 +152,17 @@
msg: "Directory {{ ostree_repo_release_feed }} does not exist on the system controller."
when: not ostree_repo_release_feed_remote.stat.exists
- name: Check if {{ host_software_metadata_dir }} exists on the system controller
stat:
path: "{{ host_software_metadata_dir }}"
register: host_software_dir_result
delegate_to: localhost
- name: Set flag to indicate if the host software dir exist
set_fact:
host_software_dir_exist: "{{ host_software_dir_result.stat.exists }}"
when: host_software_dir_result is not skipped
- name: Check if {{ host_patch_metadata_dir }} exists on the system controller
stat:
path: "{{ host_patch_metadata_dir }}"
@ -140,6 +179,8 @@
- debug:
msg: |
Prestaging type: {{ prestage_type }}
Prestaging install: {{ prestage_install }}
Prestaging source: {{ prestage_source }}
Prestaging os type: {{ prestage_os_type }}
Release feed directory (on target): {{ release_feed|default('N/A') }}
prestage_sync_software_metadata_required: {{ prestage_sync_software_metadata_required }}

View File

@ -49,7 +49,7 @@
- name: Delete those patch metadata files not belonging to the prestage software version
shell: >
grep -L "<sw_version>{{ prestage_software_version }}</sw_version>"
grep -L "<sw_version>{{ software_major_release }}</sw_version>"
{{ item }} 2>/dev/null | xargs rm -f
loop:
- "{{ patches_prestage_dir }}/metadata/applied/*"

View File

@ -0,0 +1,694 @@
#!/bin/bash
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: filetype=sh shiftwidth=4 softtabstop=4 expandtab
set -o nounset; # Do not allow use of undefined vars. Use ${VAR:-} to use an undefined VAR. Same as 'set -u'
set -o pipefail; # Catch the error in case a piped command fails
# set -o xtrace; # Turn on traces, useful while debugging (short form: on: 'set -x' off: 'set +x')
################################################################################
#
# Testing:
# This script has unit tests. Run the unit tests manually via: ./test/run-bats.sh
#
################################################################################
#
# Structure:
# This script has two top-level modes of operation, based on the subcommands:
# - get-commits (implementation: find_all_ostree_commits_for_release)
# - sync-subcloud (implementation: sync_subcloud_metadata)
#
################################################################################
################################################################################
# Helpers
#
# shellcheck disable=SC2155
readonly SCRIPTNAME=$(basename "$0")
# shellcheck disable=SC2155,SC2034
#readonly SCRIPTDIR=$(readlink -m "$(dirname "$0")")
SW_VERSION=${SW_VERSION:-}
DEBUG=${DEBUG:-}
DRY_RUN=${DRY_RUN:-}
help() {
cat<<EOF
ostree metadata synchronization utilities.
This script is invoked via ansible.
USAGE:
$SCRIPTNAME <options> [ get-commits | sync-subcloud ]
The script behaves differently depending on the 'get-commits' or 'sync-subcloud' subcommand:
get-commits : For the given major software version, get a list of all metadata file + ostree commit hash
Returns a list of: <metadata_file>:<ostree_commit_hash>
If the ostree_commit_hash is not known then this field is set to '-'
sync-subcloud : Synchronize /opt/software/metadata directory on the subcloud.
This subcommand *must be run as root*, and is executed on the subcloud
via ansible, after setting up proper contents of the \$METADATA_SYNC_DIR
See documentation in sync_metadata_on_subcloud() for algorithm details.
OPTIONS:
-o|--output <file> : Save script output to file.
-v|--sw-version <version> : Software version being synchronized.
-D|--debug: Show extra debug information.
--dry-run: Dry run. Makes no changes.
-h|--help: print this help
EXAMPLES:
$SCRIPTNAME --sw-version 24.09 get-commits
sudo $SCRIPTNAME --sw-version 24.09 sync-subcloud
EOF
exit 1
}
# Logging: these all log to stderr
die() { >&2 colorecho red "FATAL: $*"; exit 1; }
die_with_rc() { local rc=$1; shift; >&2 colorecho red "FATAL: $*, rc=$rc"; exit "$rc"; }
check_rc_die() { local rc=$1; shift; [ "$rc" != "0" ] && die_with_rc "$rc" "$@"; return 0; }
check_rc_err() { local rc=$1; shift; [ "$rc" != "0" ] && log_error "$*, rc=$rc"; return 0; }
log_error() { >&2 colorecho red "ERROR: $*"; }
log_warn() { >&2 colorecho orange "WARN: $*"; }
log_info() { >&2 echo "$*"; }
log_info_l() {
local line spacer=''
for line in "$@"; do
[ -n "${line}" ] && >&2 echo "${spacer}${line}"
spacer=' '
done
}
log_debug() { if [ -n "$DEBUG" ]; then >&2 echo "DEBUG: $*"; fi; }
log_debug_l() {
[ -z "$DEBUG" ] && return
local line spacer=''
for line in "$@"; do
[ -n "${line}" ] && >&2 echo "${spacer}${line}"
spacer=' '
done
}
log_progress() { >&2 colorecho green "$*"; }
get_logdate() { date '+%Y-%m-%d %H:%M:%S'; } # eg: log_info "$(get_logdate) My log message"
# Optionals to log output to file (see http://mywiki.wooledge.org/BashFAQ/106)
_init_log() { LOG_FILE="${LOG_FILE:-$(pwd)/${SCRIPTNAME%.*}.log}"; log_progress "$(get_logdate) Logging output to $LOG_FILE"; }
# output to file only:
redirect_output_to_file() { _init_log; exec &> "$LOG_FILE"; }
# output to console and file:
tee_output_to_file_single_process() { _init_log; exec &> >(exec tee "$LOG_FILE"); } # see https://superuser.com/a/1534702
tee_output_to_file() { _init_log; exec &> >(tee "$LOG_FILE"); }
colorecho() { # usage: colorecho <colour> <text> or colorecho -n <colour> <text>
local echo_arg=
if [ "$1" = "-n" ]; then
echo_arg="-n"; shift
fi
local colour="$1"; shift
case "${colour}" in
red) echo $echo_arg -e "$(tput setaf 1)$*$(tput sgr0)"; ;;
green) echo $echo_arg -e "$(tput setaf 2)$*$(tput sgr0)"; ;;
green-bold) echo $echo_arg -e "$(tput setaf 2; tput bold)$*$(tput sgr0)"; ;;
yellow) echo $echo_arg -e "$(tput setaf 3; tput bold)$*$(tput sgr0)"; ;;
orange) echo $echo_arg -e "$(tput setaf 3)$*$(tput sgr0)"; ;;
blue) echo $echo_arg -e "$(tput setaf 4)$*$(tput sgr0)"; ;;
purple) echo $echo_arg -e "$(tput setaf 5)$*$(tput sgr0)"; ;;
cyan) echo $echo_arg -e "$(tput setaf 6)$*$(tput sgr0)"; ;;
bold) echo $echo_arg -e "$(tput bold)$*$(tput sgr0)"; ;;
normal|*) echo $echo_arg -e "$*"; ;;
esac
}
################################################################################
#
# Utilities
#
################################################################################
initialize_env() {
if [ -f /etc/platform/openrc ]; then
# shellcheck disable=SC1091
source /etc/platform/openrc
else
# unit testing
log_warn "not found: /etc/platform/openrc"
fi
export METADATA_DIR=${METADATA_DIR:-/opt/software/metadata}
export METADATA_SYNC_DIR=${METADATA_SYNC_DIR:-/opt/software/tmp/metadata-sync}
export METADATA_SYNC_METADATA_DIR=${METADATA_SYNC_DIR}/metadata
# shellcheck disable=SC1091
if [ -z "${SW_VERSION}" ]; then
source /etc/build.info
fi
export SW_VERSION
local version_array
IFS='.' read -ra version_array <<< "${SW_VERSION}"
MAJOR_SW_VERSION=$(get_major_release_version "${SW_VERSION}")
export MAJOR_SW_VERSION
export OSTREE_REPO="/var/www/pages/feed/rel-${MAJOR_SW_VERSION}/ostree_repo"
export OSTREE_REMOTE=starlingx
export OSTREE_BRANCH=starlingx
export OSTREE_LOCAL_REF="${OSTREE_REMOTE}"
export OSTREE_REMOTE_REF="${OSTREE_REMOTE}:${OSTREE_BRANCH}"
export OSTREE_HTTP_PORT=8080
export OSTREE_HTTPS_PORT=8443
log_debug_l "SW_VERSION: ${SW_VERSION}"\
"MAJOR_SW_VERSION: ${MAJOR_SW_VERSION}"\
"OSTREE_REPO: ${OSTREE_REPO}"\
"OSTREE_LOCAL_REF: ${OSTREE_LOCAL_REF}"\
"OSTREE_REMOTE_REF: ${OSTREE_REMOTE_REF}"\
"METADATA_DIR: ${METADATA_DIR}"\
"METADATA_SYNC_DIR: ${METADATA_SYNC_DIR}"
}
trim() {
# Trim whitespace from string
# see https://stackoverflow.com/a/3352015
local var="$*"
# remove leading whitespace characters
var="${var#"${var%%[![:space:]]*}"}"
# remove trailing whitespace characters
var="${var%"${var##*[![:space:]]}"}"
printf '%s' "$var"
}
get_major_release_version() {
# The given sw_version may be in form YY.MM.nn or just YY.MM
# Get the major release (YY.MM) by splitting on '.' into an
# array then constructing MAJOR_SW_VERSION from it
local sw_version=$1
local version_array
IFS='.' read -ra version_array <<< "${sw_version}"
echo "${version_array[0]}.${version_array[1]}"
}
find_metadata_files_for_release_sorted() {
# Find all metadata files for given software release (major, e.g YY.MM or minor YY.MM.nn)
# The files are sorted in order of minor release version, ascending
# For minor release we should only find one metadata file
#
local sw_version=${1:-$SW_VERSION}
local metadata_dir=${2:-$METADATA_DIR}
# 1) Get all the sw_version metadata files matching the major/minor software version we're given
# Storing in a associative array
local meta_file
local -A metadata_files_map # key: sw_version, value: metadata file
local found_sw_version
while IFS= read -r meta_file; do
found_sw_version=$(get_simple_xml_attrib_from_metadata "${meta_file}" "sw_version")
metadata_files_map[${found_sw_version}]=${meta_file}
done < <(grep --recursive --files-with-matches --fixed-strings "<sw_version>${sw_version}" "${metadata_dir}")
if [ ${#metadata_files_map[@]} -eq 0 ]; then
return
fi
# 2) Sort by sw_version tag (regardless of path)
local sorted_versions=()
while IFS= read -rd '' found_sw_version; do
sorted_versions+=("${found_sw_version}")
done < <(printf '%s\0' "${!metadata_files_map[@]}" | sort --zero-terminated --version-sort)
# 3) Return the list of files in sorted order
local sorted_version
for sorted_version in "${sorted_versions[@]}"; do
echo "${metadata_files_map[${sorted_version}]}"
done
}
find_metadata_file_for_attrib_val() {
local attrib_name=$1
local attrib_val=$2
local metadata_dir=${3:-$METADATA_DIR}
local meta_file
local -a metadata_files=()
while IFS= read -r meta_file; do
metadata_files+=( "${meta_file}" )
done < <(grep --recursive --files-with-matches --fixed-strings "<${attrib_name}>${attrib_val}</${attrib_name}>" "${metadata_dir}")
if [ ${#metadata_files[*]} -eq 1 ]; then
log_debug "find_metadata_file_for_attrib_val: attrib: ${attrib_name}, value: ${attrib_val}, file: ${metadata_files[0]}"
echo "${metadata_files[0]}"
elif [ ${#metadata_files[*]} -gt 1 ]; then
die "find_metadata_file_for_attrib_val unexpected: found multiple metadata files for ${attrib_name} ${attrib_val} in ${metadata_dir}: ${metadata_files[*]}"
fi
}
get_simple_xml_attrib_from_metadata() {
# Retrieve the value of given attribute.
# WARNING: This function performs very basic parsing:
# It only works if the opening and closing
# <attrib> </attrib> are on the same line.
local meta_file=$1
local attrib=$2
local val
val=$(sed -n 's|<'"${attrib}"'>\(.*\)</'"${attrib}"'>|\1|p' "${meta_file}")
val=$(trim "${val}")
log_debug "metadata GET ${attrib}: ${val}"
echo "${val}"
}
get_commit_hashes_from_metadata() {
# Using a nameref to update the passed-in array,
# see https://mywiki.wooledge.org/BashProgramming?highlight=%28nameref%29#Functions
local -n from_metadata_commit_hashes=$1
local meta_file=$2
local commit
while IFS= read -r commit; do
commit=$(trim "${commit}")
from_metadata_commit_hashes+=( "${commit}" )
done < <(sed --quiet 's|<commit[0-9]*>\(.*\)</commit[0-9]*>|\1|p' "${meta_file}")
}
get_usm_state_from_path() {
local path=$1
local state
case "${path}" in
*/available/*)
state=available
;;
*/committed/*)
state=committed
;;
*/deployed/*)
state=deployed
;;
*/deploying/*)
state=deploying
;;
*/removing/*)
state=removing
;;
*/unavailable/*)
state=unavailable
;;
*)
log_error "get_usm_state_from_path: parse failure: path='${path}'"
state=unavailable
;;
esac
log_debug "get_usm_state_from_path: path=${path}, state: ${state}"
echo "${state}"
}
ostree_commit_exists() {
# Does given commit exist in ostree? i.e. has it been pulled into our repo yet?
# Note: this only works for locally defined ostree repositories.
# i.e. it can't get status from a remote server
local commit_hash=$1
local ref=${2:-${OSTREE_LOCAL_REF}}
ostree --repo="${OSTREE_REPO}" log "${ref}" | grep '^commit ' | grep --quiet "${commit_hash}"
}
translate_central_metadata_path() {
# translate the /opt/software/metadata/... path to /opt/software/tmp/metadata-sync/metadata...
local metadata_file=$1
echo "${metadata_file/#"${METADATA_DIR}"/"${METADATA_SYNC_METADATA_DIR}"}"
}
get_metadata_files_unique_to_central() {
# TODO ISSUE:
# This gets flagged for removal which it shouldn't - it's just a stage change:
#
# [sysadmin@controller-0 ~(keystone_admin)]$ diff -s /opt/software/tmp/metadata-sync/ostree-metadata-commits.*
# 1d0
# < /opt/software/metadata/deployed/starlingx-24.09.1-metadata.xml:db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b
# 2a2
# > /opt/software/metadata/available/starlingx-24.09.1-metadata.xml:db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b
diff "${METADATA_SYNC_DIR}"/ostree-metadata-commits.{central,subcloud} | awk '/^</ {print $2;}' | awk -F ':' '{print $1;}'
}
get_metadata_files_unique_to_subcloud() {
diff "${METADATA_SYNC_DIR}"/ostree-metadata-commits.{central,subcloud} | awk '/^>/ {print $2;}' | awk -F ':' '{print $1;}'
}
pull_ostree_commit_to_subcloud() {
# Pulls given commit into subcloud feed repo
#
local commit_hash=$1
if ostree_commit_exists "${commit_hash}"; then
log_info "ostree commit ${commit_hash}: already exists in ${OSTREE_LOCAL_REF}"
else
log_info "Pulling ostree commit from system controller: ${commit_hash}"
run_cmd ostree --repo="${OSTREE_REPO}" pull --mirror "${OSTREE_REMOTE_REF}" "${OSTREE_BRANCH}@${commit_hash}"
check_rc_die $? "ostree pull failed"
fi
}
configure_ostree_repo_for_central_pull() {
# Ensures the $OSTREE_REPO is configured to pull from the system controller
[ -n "${DRY_RUN}" ] && return
# Get system controller management IP (run from system controller):
local system_controller_ip
system_controller_ip=$(system addrpool-list | awk '/system-controller-subnet/ { print $14; }')
local is_https_enabled
is_https_enabled=$(system show | awk '/https_enabled/ { print $4; }')
log_info_l "Configuring ostree repo: "\
"system_controller_ip: ${system_controller_ip}"\
"is_https_enabled: ${is_https_enabled}"\
"OSTREE_REPO: ${OSTREE_REPO}"
if [ "${is_https_enabled}" = True ]; then
sed -i.bak 's|^url=.*|url=https://'"${system_controller_ip}:${OSTREE_HTTPS_PORT}/iso/${MAJOR_SW_VERSION}/ostree_repo"'|' "${OSTREE_REPO}/config"
if ! grep --quiet 'tls-permissive=true' "${OSTREE_REPO}/config"; then
echo "tls-permissive=true" >> "${OSTREE_REPO}/config"
fi
else
sed -i.bak 's|^url=.*|url=http://'"${system_controller_ip}:${OSTREE_HTTP_PORT}/iso/${MAJOR_SW_VERSION}/ostree_repo"'|' "${OSTREE_REPO}/config"
fi
}
run_cmd() {
if [ -n "${DRY_RUN}" ]; then
log_info "DRY_RUN: $*"
else
log_info "Running: $*"
"$@"
fi
}
################################################################################
#
# Top-level command implementation
#
################################################################################
find_all_ostree_commits_for_release() {
#
# Find all ostree commits for the given sw version.
#
# Returns a list of: <metadata_file>:<ostree_commit_hash>
# for the local metadata tree.
#
# If the metadata does not specify a commit-id then we use '-'
# The list is sorted by version, from lowest to highest.
# This ensures that versions can be processed in the correct numerical order.
#
local sw_version=${1:-$SW_VERSION}
local metadata_dir=${2:-$METADATA_DIR}
local number_of_commits metadata_file
for metadata_file in $(find_metadata_files_for_release_sorted "${sw_version}" "${metadata_dir}"); do
if [ ! -f "${metadata_file}" ]; then
return
fi
number_of_commits=$(get_simple_xml_attrib_from_metadata "${metadata_file}" "number_of_commits")
# TODO Testing with multiple commits in an update is incomplete
# remove this when fully tested:
if [ -n "${number_of_commits}" ] && [ "${number_of_commits}" -gt 1 ]; then
log_warn "Update has ${number_of_commits} commits: not tested yet"
fi
local commit_hashes=()
get_commit_hashes_from_metadata commit_hashes "${metadata_file}"
if [ "${#commit_hashes[@]}" -eq 0 ]; then
echo "${metadata_file}:-"
else
if [ "${number_of_commits}" -ne "${#commit_hashes[@]}" ]; then
# Unexpected, and we should fail here
die "Update has number_of_commits=${number_of_commits} but only found ${#commit_hashes[@]} commits"
fi
# We only need to supply the first commit here.
# See how the sync_subcloud_metadata algorithm works - it only uses the first commit
# TODO: do we actually need to supply the commit at all?
echo "${metadata_file}:${commit_hashes[0]}"
fi
done
}
sync_metadata_on_subcloud() {
#
# This function peforms metadata / ostree commit synchronizaton on the subcloud
#
# The algorithm for syncing the /opt/software/metadata/<STATE>/<RELEASE>-metadata.xml
# is as follows:
#
# For each RELEASE being synchronized from systemController:
#
# COPY metadata.xml from systemController
# - this will include the 'ostree-commit-id' and 'committed' ATTRIBUTES from systemController
# * this has already been done by ansible
#
# IF RELEASE does NOT EXIST on subcloud
# IF 'ostree-commit-id' == NULL
# Create it with STATE = unavailable
# ELSE
# Create it with STATE = available
# ELSE // RELEASE exists on subcloud
# IF subcloud STATE == deployed
# Leave it as deployed
# ELSE IF subcloud STATE == available or unavailable
# IF ostree-commit-id == NULL
# Set STATE = unavailable
# ELSE
# Set STATE = available
#
# For each RELEASE on SUBCLOUD but NOT synchronized from systemController
# REMOVE RELEASE FROM SUBCLOUD
#
local metadata_file=$1
local central_metadata_file=$2
# Using a namedref: use different name to avoid name collision
# See https://mywiki.wooledge.org/BashProgramming?highlight=%28nameref%29#Functions
local -n sync_subcloud_commit_hashes=$3
# We already have the metadata file from the system controller
if [ -z "${central_metadata_file}" ]; then
# unexpected
die "no metadata file found for ostree commit(s): ${sync_subcloud_commit_hashes[*]}"
fi
# Get current subcloud state from metadata; it may or may not exist
local id sw_release central_usm_state subcloud_metadata_file
id=$(get_simple_xml_attrib_from_metadata "${central_metadata_file}" "id")
sw_release=$(get_simple_xml_attrib_from_metadata "${central_metadata_file}" "sw_release")
central_usm_state=$(get_usm_state_from_path "$central_metadata_file")
subcloud_metadata_file=$(find_metadata_file_for_attrib_val "id" "${id}" "${METADATA_DIR}")
local log_hdr="sync_metadata_on_subcloud: id: ${id}"
log_info_l "${log_hdr}" "sw_release: ${sw_release}"\
"commit_hashes: ${sync_subcloud_commit_hashes[*]}"\
"central_metadata_file: ${central_metadata_file}"\
"central_usm_state: ${central_usm_state}"\
"subcloud_metadata_file: ${subcloud_metadata_file}"
local new_state
# It is sufficient to check against only one commit hash here - they are all part of the same metadata file
local commit_hash=${sync_subcloud_commit_hashes[0]}
if [ -z "${subcloud_metadata_file}" ]; then
# Not found: RELEASE does NOT EXIST on subcloud
if ostree_commit_exists "${commit_hash}"; then
# Create it with STATE = available
log_debug_l "sync_metadata_on_subcloud: commit exists in local ${OSTREE_LOCAL_REF}"\
"ref: ${commit_hash}"
new_state="available"
else
# Create it with STATE = unavailable
new_state="unavailable"
fi
log_info "${log_hdr} does not exist on subcloud, setting to ${new_state}"
run_cmd cp "${central_metadata_file}" "${METADATA_DIR}/${new_state}"
else
# RELEASE exists on subcloud
local subcloud_state
subcloud_state=$(get_usm_state_from_path "${subcloud_metadata_file}")
case "${subcloud_state}" in
'deployed')
# Leave it as deployed
log_info "${log_hdr} is in sync (subcloud state: deployed)"
;;
'available'|'unavailable')
# Not found: RELEASE does NOT EXIST on subcloud
if ostree_commit_exists "${commit_hash}"; then
# Set STATE = available
log_debug_l "sync_metadata_on_subcloud: commit exists in local ${OSTREE_LOCAL_REF}"\
"ref: ${commit_hash}"
new_state=available
else
# Set STATE = unavailable
new_state=unavailable
fi
log_info "${log_hdr} exists on subcloud, setting subcloud state: ${new_state}"
run_cmd rm "${subcloud_metadata_file}"
run_cmd cp "${central_metadata_file}" "${METADATA_DIR}/${new_state}"
;;
'committed')
log_info "${log_hdr} subcloud state is ${subcloud_state} - ignoring"
;;
'deploying'|'removing')
log_info "${log_hdr} subcloud state is ${subcloud_state} - transitional, ignoring"
;;
*)
log_error "${log_hdr} subcloud state is unexpected: ${subcloud_state} - ignoring"
;;
esac
fi
}
# Context: INVOKED ON SUBCLOUD
sync_subcloud_metadata() {
#
# Top-level function to synchronize the subcloud software metadata.
#
# When this is invoked, we have the following in place (via ansible):
#
# - "${METADATA_SYNC_DIR}"/ostree-metadata-commits.{central,subcloud}
# - these files summarizing the metadata files / ostree commits matching our given release
# - "${METADATA_SYNC_DIR}/metadata
# - is a direct copy of the system controller /opt/software/medatada directory
# - we use this to calculate the new subcloud state of the release
# and to pull the missing ostree commits to the subcloud
#
# Synchronization is done on a per-major-release basis.
# For given major release:
# 1) Get a list of all update metadata files needing to be synchronized
# (this is done by comparing (using diff) the central and subcloud file in
# "${METADATA_SYNC_DIR}"/ostree-metadata-commits.{central,subcloud}).
# 2) Ensure any ostree commit(s) for the update are pulled from central
# controller if necessary.
# 3) Synchronize the update metadata file into the proper state-based location
# on the subcloud
#
local metadata_file commit_hash central_metadata_file
configure_ostree_repo_for_central_pull
local commit_hashes=()
local commit_hash
# 1) Get list of metadata files requiring sync
for metadata_file in $(get_metadata_files_unique_to_central); do
log_info "sync_subcloud_metadata: processing ${metadata_file} from central (sync)"
central_metadata_file=$(translate_central_metadata_path "${metadata_file}")
get_commit_hashes_from_metadata commit_hashes "${central_metadata_file}"
log_debug_l "sync_subcloud_metadata from central: "\
"metadata_file: ${metadata_file}"\
"central_metadata_file: ${central_metadata_file}"\
"commit_hashes: ${commit_hashes[*]}"
if [ "${#commit_hashes[@]}" -gt 0 ]; then
for commit_hash in "${commit_hashes[@]}"; do
# 2) Pull from central controller if necessary
# TODO(kmacleod): check if previous_commit exists from metadata, fail
pull_ostree_commit_to_subcloud "${commit_hash}"
done
# 3) Synchronize the metadata file
sync_metadata_on_subcloud "${metadata_file}" "${central_metadata_file}" commit_hashes
fi
done
for metadata_file in $(get_metadata_files_unique_to_subcloud); do
log_info "sync_subcloud_metadata: processing ${metadata_file} from subcloud (check remove)"
commit_hashes=()
get_commit_hashes_from_metadata commit_hashes "${central_metadata_file}"
log_debug_l "sync_subcloud_metadata from subcloud (check remove): "\
"metadata_file: ${metadata_file}"\
"commit_hashes: ${commit_hashes[*]}"
local removed=
if [ "${#commit_hashes[@]}" -gt 0 ]; then
for commit_hash in "${commit_hashes[@]}"; do
if ! ostree_commit_exists "${commit_hash}"; then
log_info "sync_subcloud_metadata from subcloud: commit '${commit_hash}' does not exist, removing '${metadata_file}'"
removed=1
fi
done
if [ -n "${removed}" ]; then
rm "${metadata_file}"
fi
fi
if [ -n "${removed}" ]; then
log_info_l "sync_subcloud_metadata from subcloud, removed file for non-existing commit(s): "\
"metadata_file: ${metadata_file}"\
"commit_hashes: ${commit_hashes[*]}"
else
log_info_l "sync_subcloud_metadata from subcloud, commit is in use, not removing: "\
"metadata_file: ${metadata_file}"\
"commit_hashes: ${commit_hashes[*]}"
fi
done
}
################################################################################
# Main
#
main() {
local arg_outputfile=
local -a cmd
while [ $# -gt 0 ] ; do
case "${1:-""}" in
-h|--help)
help
;;
-D|--debug)
DEBUG=1
;;
--dry-run)
DRY_RUN=1
;;
-o|--output)
shift
arg_outputfile=$1
;;
-v|--sw-version)
shift
SW_VERSION=$1
export SW_VERSION
;;
get-commits)
shift
cmd=('find_all_ostree_commits_for_release')
break
;;
sync-subcloud)
if [ "$(id -u)" != 0 ]; then
die "you must be root to run sync-commits"
fi
shift
cmd=('sync_subcloud_metadata')
break
;;
*)
die "Invalid command '$1' [use -h/--help for help]"
;;
esac
shift
done
initialize_env
# execute our command
if [ -z "${arg_outputfile}" ]; then
"${cmd[@]}"
else
[ -f "${arg_outputfile}" ] && rm -f "${arg_outputfile}"
"${cmd[@]}" | tee "${arg_outputfile}"
fi
}
if [[ "${BASH_SOURCE[0]}" = "$0" ]]; then
main "$@"
fi

View File

@ -0,0 +1,9 @@
FROM bats/bats
RUN \
apk --no-cache --update add \
coreutils \
diffutils \
grep \
gawk \
sed

View File

@ -0,0 +1,40 @@
This is a unit test suite for the ostree-metadata-sync.sh bash script.
Bats is the Bash Automated Testing System. See https://bats-core.readthedocs.io/
Usage:
The tests are executed via the bats docker container. Use the run-bats.sh wrapper script to run the tests via bats inside the docker
container:
cd $MY_REPO/stx/ansible-playbooks/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test
# Run all tests
./run-bats.sh
# Run tests in interactive mode:
./run-bats.sh --interactive
See ./run-bats.sh --help for information.
TODO (as suggested by Yuxing):
> I would suggest to try to run the bat test with tox/zuul rather than in another container:
> Something like:
> [testenv:bats]
> basepython = python3
> allowlist_externals =
> bats
> git
> commands = bats <>
> [tox]
> envlist = linters,pep8,bats
Note: this would require ensuring that bats in installed in the tox/zuul environment, which
is probably not trivial, and is likely the bulk of the effort required.
Once this is dont, then other bash scripts could be tested using bats, which would be a valuable
addition to the test suite.

View File

@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<patch>
<id>starlingx-24.03.0</id>
<sw_version>24.03.0</sw_version>
<component>starlingx</component>
<summary>STX 24.03 GA release</summary>
<description>STX 24.03 major GA release</description>
<install_instructions/>
<warnings/>
<status>REL</status>
<unremovable>Y</unremovable>
<reboot_required>Y</reboot_required>
<apply_active_release_only/>
<contents>
</contents>
<requires>
</requires>
<semantics/>
</patch>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<patch>
<id>starlingx-24.03.0</id>
<sw_version>24.03.0</sw_version>
<component>starlingx</component>
<summary>STX 24.03 GA release</summary>
<description>STX 24.03 major GA release</description>
<install_instructions/>
<warnings/>
<status>REL</status>
<unremovable>Y</unremovable>
<reboot_required>Y</reboot_required>
<apply_active_release_only/>
<contents>
</contents>
<requires>
</requires>
<semantics/>
</patch>

View File

@ -0,0 +1,27 @@
<patch>
<id>starlingx-24.03.1</id>
<sw_version>24.03.1</sw_version>
<component>starlingx</component>
<summary>Sample inservice test patch</summary>
<description>This patch DOESN'T require reboot.
This patch should include 1 packages:
- logmgmt</description>
<install_instructions>Sample instructions</install_instructions>
<warnings>Sample warning</warnings>
<status>DEV</status>
<unremovable>N</unremovable>
<reboot_required>N</reboot_required>
<semantics />
<requires>
<req_patch_id>starlingx-24.03.0</req_patch_id>
</requires>
<pre_install>pre-install.sh</pre_install>
<post_install>post-install.sh</post_install>
<deploy_precheck>deploy-precheck.sh</deploy_precheck>
<packages>
<deb>logmgmt_1.0-1.stx.9_all.deb</deb>
</packages>
<number_of_commits>1</number_of_commits>
<previous_commit>895ea56dd33882a91a872871f6989c7978ff1413404e2574693380186dbb09a7</previous_commit>
<commit>db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b</commit>
</patch>

View File

@ -0,0 +1,2 @@
/opt/software/metadata/deployed/starlingx-24.03.0-metadata.xml:-
/opt/software/metadata/deployed/starlingx-24.03.1-metadata.xml:db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b

View File

@ -0,0 +1 @@
/opt/software/metadata/deployed/starlingx-24.03.0-metadata.xml:-

View File

@ -0,0 +1,25 @@
<patch>
<id>starlingx-24.03.3</id>
<sw_version>24.03.3</sw_version>
<component>starlingx</component>
<summary>Sample inservice test patch</summary>
<description>Test</description>
<install_instructions>Sample instructions</install_instructions>
<warnings>Sample warning</warnings>
<status>DEV</status>
<unremovable>N</unremovable>
<reboot_required>N</reboot_required>
<semantics />
<requires>
<req_patch_id>starlingx-24.03.0</req_patch_id>
</requires>
<pre_install>pre-install.sh</pre_install>
<post_install>post-install.sh</post_install>
<deploy_precheck>deploy-precheck.sh</deploy_precheck>
<packages>
<deb>logmgmt_1.0-1.stx.9_all.deb</deb>
</packages>
<number_of_commits>1</number_of_commits>
<previous_commit>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</previous_commit>
<commit>bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb</commit>
</patch>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<patch>
<id>starlingx-24.03.0</id>
<sw_version>24.03.0</sw_version>
<component>starlingx</component>
<summary>STX 24.03 GA release</summary>
<description>STX 24.03 major GA release</description>
<install_instructions/>
<warnings/>
<status>REL</status>
<unremovable>Y</unremovable>
<reboot_required>Y</reboot_required>
<apply_active_release_only/>
<contents>
</contents>
<requires>
</requires>
<semantics/>
</patch>

View File

@ -0,0 +1,27 @@
<patch>
<id>starlingx-24.03.1</id>
<sw_version>24.03.1</sw_version>
<component>starlingx</component>
<summary>Sample inservice test patch</summary>
<description>This patch DOESN'T require reboot.
This patch should include 1 packages:
- logmgmt</description>
<install_instructions>Sample instructions</install_instructions>
<warnings>Sample warning</warnings>
<status>DEV</status>
<unremovable>N</unremovable>
<reboot_required>N</reboot_required>
<semantics />
<requires>
<req_patch_id>starlingx-24.03.0</req_patch_id>
</requires>
<pre_install>pre-install.sh</pre_install>
<post_install>post-install.sh</post_install>
<deploy_precheck>deploy-precheck.sh</deploy_precheck>
<packages>
<deb>logmgmt_1.0-1.stx.9_all.deb</deb>
</packages>
<number_of_commits>1</number_of_commits>
<previous_commit>895ea56dd33882a91a872871f6989c7978ff1413404e2574693380186dbb09a7</previous_commit>
<commit>db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b</commit>
</patch>

View File

@ -0,0 +1,25 @@
<patch>
<id>starlingx-24.03.2</id>
<sw_version>24.03.2</sw_version>
<component>starlingx</component>
<summary>Sample inservice test patch</summary>
<description>Test</description>
<install_instructions>Sample instructions</install_instructions>
<warnings>Sample warning</warnings>
<status>DEV</status>
<unremovable>N</unremovable>
<reboot_required>N</reboot_required>
<semantics />
<requires>
<req_patch_id>starlingx-24.03.0</req_patch_id>
</requires>
<pre_install>pre-install.sh</pre_install>
<post_install>post-install.sh</post_install>
<deploy_precheck>deploy-precheck.sh</deploy_precheck>
<packages>
<deb>logmgmt_1.0-1.stx.9_all.deb</deb>
</packages>
<number_of_commits>1</number_of_commits>
<previous_commit>db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b</previous_commit>
<commit>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</commit>
</patch>

View File

@ -0,0 +1,25 @@
<patch>
<id>starlingx-24.03.3</id>
<sw_version>24.03.3</sw_version>
<component>starlingx</component>
<summary>Sample inservice test patch</summary>
<description>Test</description>
<install_instructions>Sample instructions</install_instructions>
<warnings>Sample warning</warnings>
<status>DEV</status>
<unremovable>N</unremovable>
<reboot_required>N</reboot_required>
<semantics />
<requires>
<req_patch_id>starlingx-24.03.0</req_patch_id>
</requires>
<pre_install>pre-install.sh</pre_install>
<post_install>post-install.sh</post_install>
<deploy_precheck>deploy-precheck.sh</deploy_precheck>
<packages>
<deb>logmgmt_1.0-1.stx.9_all.deb</deb>
</packages>
<number_of_commits>1</number_of_commits>
<previous_commit>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</previous_commit>
<commit>bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb</commit>
</patch>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" ?>
<patch>
<id>starlingx-24.03.0</id>
<sw_version>24.03.0</sw_version>
<component>starlingx</component>
<summary>STX 24.03 GA release</summary>
<description>STX 24.03 major GA release</description>
<install_instructions/>
<warnings/>
<status>REL</status>
<unremovable>Y</unremovable>
<reboot_required>Y</reboot_required>
<apply_active_release_only/>
<contents>
</contents>
<requires>
</requires>
<semantics/>
</patch>

View File

@ -0,0 +1,27 @@
<patch>
<id>starlingx-24.03.1</id>
<sw_version>24.03.1</sw_version>
<component>starlingx</component>
<summary>Sample inservice test patch</summary>
<description>This patch DOESN'T require reboot.
This patch should include 1 packages:
- logmgmt</description>
<install_instructions>Sample instructions</install_instructions>
<warnings>Sample warning</warnings>
<status>DEV</status>
<unremovable>N</unremovable>
<reboot_required>N</reboot_required>
<semantics />
<requires>
<req_patch_id>starlingx-24.03.0</req_patch_id>
</requires>
<pre_install>pre-install.sh</pre_install>
<post_install>post-install.sh</post_install>
<deploy_precheck>deploy-precheck.sh</deploy_precheck>
<packages>
<deb>logmgmt_1.0-1.stx.9_all.deb</deb>
</packages>
<number_of_commits>1</number_of_commits>
<previous_commit>895ea56dd33882a91a872871f6989c7978ff1413404e2574693380186dbb09a7</previous_commit>
<commit>db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b</commit>
</patch>

View File

@ -0,0 +1,25 @@
<patch>
<id>starlingx-24.03.2</id>
<sw_version>24.03.2</sw_version>
<component>starlingx</component>
<summary>Sample inservice test patch</summary>
<description>Test</description>
<install_instructions>Sample instructions</install_instructions>
<warnings>Sample warning</warnings>
<status>DEV</status>
<unremovable>N</unremovable>
<reboot_required>N</reboot_required>
<semantics />
<requires>
<req_patch_id>starlingx-24.03.0</req_patch_id>
</requires>
<pre_install>pre-install.sh</pre_install>
<post_install>post-install.sh</post_install>
<deploy_precheck>deploy-precheck.sh</deploy_precheck>
<packages>
<deb>logmgmt_1.0-1.stx.9_all.deb</deb>
</packages>
<number_of_commits>1</number_of_commits>
<previous_commit>db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b</previous_commit>
<commit>aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa</commit>
</patch>

View File

@ -0,0 +1,3 @@
/opt/software/metadata/deployed/starlingx-24.03.0-metadata.xml:-
/opt/software/metadata/deployed/starlingx-24.03.1-metadata.xml:db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b
/opt/software/metadata/deployed/starlingx-24.03.2-metadata.xml:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

View File

@ -0,0 +1 @@
/opt/software/metadata/deployed/starlingx-24.03.0-metadata.xml:-

View File

@ -0,0 +1,177 @@
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# vim: set filetype=bash :
# bats unit tests: https://github.com/bats-core
bats_require_minimum_version 1.5.0
# source the script under test
. /code/ostree-metadata-sync.sh
TEST_METADATA_BASE=/tmp/ostree-metadata-sync-test
# The setup function is automatically called before each test
setup() {
echo "Running setup"
bats_load_library bats-support
bats_load_library bats-assert
bats_load_library bats-file
mkdir "${TEST_METADATA_BASE}" || fail 'mkdir failed'
cp -r /code/test/metadata/test1 "${TEST_METADATA_BASE}" || fail 'cp failed'
cp -r /code/test/metadata/test2 "${TEST_METADATA_BASE}" || fail 'cp failed'
}
init_metadata_dir() {
local test_dir_base=$1
export METADATA_DIR="${test_dir_base}"/metadata
export METADATA_SYNC_DIR="${test_dir_base}"/tmp/metadata-sync
export MAJOR_SW_VERSION="24.03"
export MINOR_SW_VERSION="24.03.1"
export SW_VERSION=$MAJOR_SW_VERSION
export DRY_RUN=1
initialize_env
}
mock_command() {
# usage: mock_command <command> <mock output>
local command=$1
shift
eval "export MOCK_OUTPUT_${command}=\"$*\""
eval "${command}() { echo \"\${MOCK_OUTPUT_${command}}\"; }"
}
mock_command_exit_code() {
# mocks a command which only returns 0/1
# usage: mock_command_exit_code <command> <exit_code>
local command=$1
local exit_code=$2
eval "${command}() { return ${exit_code}; }"
}
unmock_command() {
local command=$1
eval "unset -f ${command}"
}
# The teardown function runs after each individual test in a file, regardless of test success or failure
teardown() {
if [ -n "${TEST_METADATA_BASE}" ]; then
rm -rf "${TEST_METADATA_BASE}" || fail 'rmdir failed'
fi
}
@test "test infrastructure and mocking" {
# this is a test of functions defined in bash-template.sh
run log_info "Testing log_info"
assert_output --partial "Testing log_info"
run log_warn "Testing log_warn"
assert_output --partial "Testing log_warn"
run log_progress "Testing log_progress"
assert_output --partial "Testing log_progress"
run log_error "Testing log_error (ignore)"
assert_output --partial "Testing log_error (ignore)"
mock_command testmock "testing mock"
run testmock
assert_output "testing mock"
unmock_command testmock
run -127 testmock
mock_command ostree "ostree output"
run ostree
assert_output "ostree output"
unmock_command ostree
run -127 ostree
mock_command_exit_code testexit 0
run -0 testexit
unmock_command textexit
run -127 textexit
mock_command_exit_code testexit 1
run -1 testexit
unmock_command textexit
run -127 textexit
}
@test "test1 utilities" {
init_metadata_dir "${TEST_METADATA_BASE}"/test1
# Test standalone utilities
local id="starlingx-24.03.0"
local sw_version="24.03.0"
local test_metadata_file="${METADATA_DIR}/deployed/${id}-metadata.xml"
run find_metadata_files_for_release_sorted "${SW_VERSION}"
assert_output "${test_metadata_file}"
run get_simple_xml_attrib_from_metadata "${test_metadata_file}" "id"
assert_output "${id}"
run get_simple_xml_attrib_from_metadata "${test_metadata_file}" "sw_version"
assert_output "${sw_version}"
run get_simple_xml_attrib_from_metadata "${test_metadata_file}" "commit"
assert_output ""
run get_usm_state_from_path "${test_metadata_file}"
assert_output deployed
run find_metadata_file_for_attrib_val "id" "starlingx-24.03.0" "${METADATA_DIR}"
assert_output "${test_metadata_file}"
local test_central_metadata_file="${METADATA_SYNC_METADATA_DIR}/deployed/${id}-metadata.xml"
run translate_central_metadata_path "${test_metadata_file}"
assert_output "${test_central_metadata_file}"
run find_all_ostree_commits_for_release "${SW_VERSION}"
assert_output "${METADATA_DIR}/deployed/starlingx-24.03.0-metadata.xml:-"
}
@test "test1 data: sync subcloud metadata" {
init_metadata_dir "${TEST_METADATA_BASE}"/test1
# mock
mock_command_exit_code pull_ostree_commit_on_subcloud 0
mock_command_exit_code ostree_commit_exists 0
run sync_subcloud_metadata
assert_success
unmock_command pull_ostree_commit_on_subcloud
unmock_command ostree_commit_exists
}
@test "test2 data: find operations on major release" {
init_metadata_dir "${TEST_METADATA_BASE}"/test2
run find_metadata_files_for_release_sorted "${SW_VERSION}"
assert_output "/tmp/ostree-metadata-sync-test/test2/metadata/deployed/starlingx-24.03.0-metadata.xml
/tmp/ostree-metadata-sync-test/test2/metadata/deployed/starlingx-24.03.1-metadata.xml
/tmp/ostree-metadata-sync-test/test2/metadata/deploying/starlingx-24.03.2-metadata.xml
/tmp/ostree-metadata-sync-test/test2/metadata/available/starlingx-24.03.3-metadata.xml"
run find_all_ostree_commits_for_release "${SW_VERSION}"
assert_output "${METADATA_DIR}/deployed/starlingx-24.03.0-metadata.xml:-
${METADATA_DIR}/deployed/starlingx-24.03.1-metadata.xml:db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b
${METADATA_DIR}/deploying/starlingx-24.03.2-metadata.xml:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
${METADATA_DIR}/available/starlingx-24.03.3-metadata.xml:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
}
@test "test2 data: find operations on minor release" {
init_metadata_dir "${TEST_METADATA_BASE}"/test2
export SW_VERSION=$MINOR_SW_VERSION
run find_metadata_files_for_release_sorted "${SW_VERSION}"
assert_output "/tmp/ostree-metadata-sync-test/test2/metadata/deployed/starlingx-24.03.1-metadata.xml"
run find_all_ostree_commits_for_release "${SW_VERSION}"
assert_output "${METADATA_DIR}/deployed/starlingx-24.03.1-metadata.xml:db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b"
}

View File

@ -0,0 +1,31 @@
#!/bin/bash
#
# Runs bats (Bash Automated Testing System) https://bats-core.readthedocs.io/ via docker
#
# shellcheck disable=SC2155
readonly SCRIPTNAME=$(basename "$0")
# shellcheck disable=SC2155,SC2034
readonly SCRIPTDIR=$(readlink -m "$(dirname "$0")")
cd "${SCRIPTDIR}" || { echo "cd failed"; exit 1; }
# build if necessary
docker images | grep -q 'starlingx/bats' || docker build -t starlingx/bats:latest .
#echo "Running bats: $(docker run -it bats/bats:latest --help)"
case "$1" in
--help)
"Usage: ${SCRIPTNAME} ?-i|--interactive?"
echo ""
echo "bats --help:"
docker run -it bats/bats:latest --help
;;
-i|--interactive|--bash|bash|shell)
echo "Running in interactive mode. Run tests in 'test' via: bats test"
docker run -it --rm -v "${PWD}/..:/code" --entrypoint bash starlingx/bats:latest
;;
*)
docker run -it --rm -v "${PWD}/..:/code" starlingx/bats:latest --verbose-run test/ostree-metadata-sync.bats
;;
esac

View File

@ -0,0 +1,209 @@
---
#
# Copyright (c) 2024 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# ROLE DESCRIPTION:
#
# This role synchronizes the system controller /opt/software/metadata
# directory to the subcloud.
#
# Note: much of the work is done by the ostree-metadata-sync.sh script, which
# has two targets: get-commits and sync-subcloud
#
# Procedure:
# - On system controller, run ostree-metadata-sync.sh --sw-version 24.09 get-commits
# - Creates /opt/software/tmp/metadata-sync/ostree-metadata-commits.central
# which contains list of sw_version:ostree commit for all
# software updates matching prestaging software release
# - this file is transferred to subcloud for use during the sync-subcloud stage
# - On subcloud, run ostree-metadata-sync.sh --sw-version 24.09 get-commits
# - Creates /opt/software/tmp/metadata-sync/ostree-metadata-commits.subcloud
# which contains list of sw_version:ostree commit for all software
# updates matching prestaging software release
# - Set software_sync_required:
# - The above two get-commits script calls output its list to stdout
# - Based on the stdout diff of the above two script calls, we determine
# if a sync is required
# - Is software_sync_required?
# - Transfer the following from system controller to subcloud at /opt/software/tmp/metadata-sync:
# - /opt/software/tmp/metadata-sync/ostree-metadata-commits.central
# - the full /opt/software/metadata hierarchy
# - The above data is used on the subcloud to synchronize the commits on the subcloud
# by invoking: ostree-metadata-sync.sh --sw-version 24.09 sync-subcloud
# - operates on the data in /opt/software/tmp/metadata-sync
# plus the state on subcloud to run the sync algorithm for each commit
#
- name: Set software metadata synchronization facts for {{ software_version }}
set_fact:
software_sync_required: false
ostree_feed_repo_dir: /var/www/pages/feed/rel-{{ software_major_release }}/ostree_repo
ostree_sysroot_repo_dir: /sysroot/ostree/repo
ostree_rev: starlingx
usm_software_dir: /opt/software
usm_metadata_dir: /opt/software/metadata
tmp_metadata_sync_dir: /opt/software/tmp/metadata-sync
ostree_metadata_commits_central: /opt/software/tmp/metadata-sync/ostree-metadata-commits.central
ostree_metadata_commits_subcloud: /opt/software/tmp/metadata-sync/ostree-metadata-commits.subcloud
- name: Gather system controller ostree commit
command: ostree --repo={{ ostree_feed_repo_dir }} rev-parse {{ ostree_rev }}
register: ostree_commit_system_controller
delegate_to: localhost
- name: Gather subcloud ostree commit
command: ostree --repo={{ ostree_feed_repo_dir }} rev-parse {{ ostree_rev }}
register: ostree_commit_subcloud
- name: Gather system controller software list
shell: software list --release {{ software_major_release }} | grep {{ software_version }} | grep -E '(available|deploy)'
register: system_controller_software_list
failed_when: false
delegate_to: localhost
- name: Show system controller software list for release {{ software_version }}
debug:
var: system_controller_software_list.stdout
- name: Gather subcloud software list
shell: software list --release {{ software_major_release }} | grep {{ software_version }} | grep -E '(available|deploy)'
register: subcloud_software_list
failed_when: false
become: true
- name: Show subcloud software list for release {{ software_version }}
debug:
var: subcloud_software_list.stdout
- name: Ensure system controller {{ tmp_metadata_sync_dir }} exists (sysadmin)
file:
path: "{{ tmp_metadata_sync_dir }}"
owner: sysadmin
group: root
state: directory
mode: 0755
recurse: yes
become: true
delegate_to: localhost
- name: Ensure subcloud {{ tmp_metadata_sync_dir }} exists (sysadmin)
file:
path: "{{ tmp_metadata_sync_dir }}"
owner: sysadmin
group: root
state: directory
mode: 0755
recurse: yes
become: true
- name: Gather system controller metadata commits
#
# Use the existing ostree_metadata_commits_central file if:
# 1) It exists, and
# 2) There are no new commits. Compare last line of the ostree_metadata_commits_central
# file against current ostree repo commit.
# Otherwise, we generate a new ostree_metadata_commits_central file using the
# get-commits target to our script.
#
# Parallel operations (for orchestration):
# We use flock here because there may be many prestaging operations running
# in parallel on system controller. flock behaviour:
# - acquire lock on /tmp/ostree_metadata_commits_central.lock
# - the timeout is long just to ensure we never deadlock for any reason
shell: |
exec 3>/tmp/ostree_metadata_commits_central.lock
flock --exclusive --timeout 180 3 || echo "ERROR: flock failed: $?"
if [ ! -f "{{ ostree_metadata_commits_central }}" ] \
|| diff -q <(ostree --repo="{{ ostree_feed_repo_dir }}" rev-parse "{{ ostree_rev }}") \
<(tail --lines=1 "{{ ostree_metadata_commits_central }}" | awk -F':' '{ print $2; }'); then
/usr/share/ansible/stx-ansible/playbooks/roles/prestage/sync-software-metadata/files/ostree-metadata-sync.sh \
--sw-version "{{ software_version }}" --output "{{ ostree_metadata_commits_central }}" get-commits
else
cat "{{ ostree_metadata_commits_central }}"
fi
exec 3>&- # release the lock
register: system_controller_software_metadata_commits
delegate_to: localhost
- name: Show ostree metadata commits on system controller
debug:
var: system_controller_software_metadata_commits
- name: Gather subcloud metadata commits
command: >-
/usr/share/ansible/stx-ansible/playbooks/roles/prestage/sync-software-metadata/files/ostree-metadata-sync.sh
--sw-version "{{ software_version }}" --output "{{ ostree_metadata_commits_subcloud }}" get-commits
register: subcloud_software_metadata_commits
- name: Show ostree metadata commits on subcloud
debug:
var: subcloud_software_metadata_commits
- name: Decide if software metadata synchronization is required
set_fact:
software_sync_required: true
when: (system_controller_software_metadata_commits.stdout != subcloud_software_metadata_commits.stdout)
or (system_controller_software_list.stdout != subcloud_software_list.stdout)
- debug:
msg:
- "Software sync required: {{ software_sync_required }}"
- "ostree revision from {{ ostree_feed_repo_dir }}:"
- "system controller: {{ ostree_commit_system_controller.stdout }}"
- "subcloud: {{ ostree_commit_subcloud.stdout }}"
- "Software list:"
- "system controller:"
- "{{ system_controller_software_list.stdout }}"
- "{{ system_controller_software_metadata_commits.stdout }}"
- "subcloud:"
- "{{ subcloud_software_list.stdout }}"
- "{{ subcloud_software_metadata_commits.stdout }}"
- block:
- name: Copy system controller {{ usm_metadata_dir }} to subcloud {{ tmp_metadata_sync_dir }}
copy:
src: "{{ usm_metadata_dir }}"
dest: "{{ tmp_metadata_sync_dir }}"
force: true
- name: Copy system controller {{ ostree_metadata_commits_central }} to subcloud {{ tmp_metadata_sync_dir }}
copy:
src: "{{ ostree_metadata_commits_central }}"
dest: "{{ tmp_metadata_sync_dir }}"
force: true
- name: Synchronizing system controller ostree commits on subcloud
command: >-
/usr/share/ansible/stx-ansible/playbooks/roles/prestage/sync-software-metadata/files/ostree-metadata-sync.sh
--sw-version "{{ software_version }}" sync-subcloud 2>&1 | tee /tmp/sync-ostree-commits.log
register: sync_software_commits
become: true
- name: Show sync output
debug:
msg:
- "sync_software_commits:"
- "rc: {{ sync_software_commits.rc }}"
- "start: {{ sync_software_commits.start }}"
- "end: {{ sync_software_commits.end }}"
- "stderr: {{ sync_software_commits.stderr }}"
- "stdout: {{ sync_software_commits.stdout }}"
# Restart the software controller and agent to pickup the changes
- name: Restart the software controller and agent
systemd:
name: "{{ item }}"
state: restarted
with_items:
- software-controller-daemon
- software-agent
become: true
when: software_sync_required
- debug:
msg:
- "Skipping software metadata synchronization."
when: not software_sync_required

View File

@ -7,7 +7,7 @@
- hosts: all
# If gathering facts is really necessary, run setup task AFTER host connectivity
# check block in prepare-env role.
gather_facts: no
gather_facts: false
vars_files:
- vars/common/main.yml
@ -19,6 +19,7 @@
check_bootstrap_address: "{{ check_bootstrap_address | default(true) }}"
check_patches: "{{ check_patches | default(true) }}"
sync_patch_metadata: "{{ sync_patch_metadata | default(false) }}"
sync_software_metadata: "{{ sync_software_metadata | default(false) }}"
password_change: "{{ password_change | default(true) }}"
password_change_responses:
yes/no: 'yes'
@ -32,7 +33,12 @@
roles:
- common/prepare-env
- role: common/sync-patch-metadata
# TODO turn this off for >= 24.09 targets:
when: sync_patch_metadata and os_release == "debian"
vars:
ansible_become_pass: "{{ ansible_ssh_pass }}"
- role: common/install-sync-software-metadata
when: sync_software_metadata
vars:
ansible_become_pass: "{{ ansible_ssh_pass }}"
- common/validate-target

View File

@ -25,7 +25,7 @@ commands =
-not -name \*~ \
-not -name \*.md \
-name \*.sh \
-print0 | xargs -r -n 1 -0 bashate -v"
-print0 | xargs -r -n 1 -0 bashate -iE006 -v"
bash -c "find {toxinidir} \
-path '{toxinidir}/.tox' -a -prune \
-o -name '*.yaml' \