From 311386a3530cb242f0ebdb361ea8f0644e9626a5 Mon Sep 17 00:00:00 2001 From: Kyle MacLeod Date: Sun, 9 Jun 2024 23:28:16 -0400 Subject: [PATCH] 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 Change-Id: I4c67f03d0cfcf60e1bf78fc4c80ec18271fc49c1 --- playbookconfig/src/playbooks/install.yml | 1 + .../src/playbooks/prestage_images.yml | 6 +- .../src/playbooks/prestage_sw_packages.yml | 24 +- .../tasks/main.yml | 141 ++++ .../get-prestage-versions/tasks/main.yml | 4 +- .../roles/prestage/prepare-env/tasks/main.yml | 26 +- .../prepare-sw-packages-prestage-env.yml | 53 +- .../tasks/prestage-patch-metadata.yml | 2 +- .../files/ostree-metadata-sync.sh | 694 ++++++++++++++++++ .../files/test/Dockerfile | 9 + .../files/test/README.md | 40 + .../deployed/starlingx-24.03.0-metadata.xml | 19 + .../deployed/starlingx-24.03.0-metadata.xml | 19 + .../deployed/starlingx-24.03.1-metadata.xml | 27 + .../ostree-metadata-commits.central | 2 + .../ostree-metadata-commits.subcloud | 1 + .../available/starlingx-24.03.3-metadata.xml | 25 + .../deployed/starlingx-24.03.0-metadata.xml | 19 + .../deployed/starlingx-24.03.1-metadata.xml | 27 + .../deploying/starlingx-24.03.2-metadata.xml | 25 + .../available/starlingx-24.03.3-metadata.xml | 25 + .../deployed/starlingx-24.03.0-metadata.xml | 19 + .../deployed/starlingx-24.03.1-metadata.xml | 27 + .../deploying/starlingx-24.03.2-metadata.xml | 25 + .../ostree-metadata-commits.central | 3 + .../ostree-metadata-commits.subcloud | 1 + .../files/test/ostree-metadata-sync.bats | 177 +++++ .../files/test/run-bats.sh | 31 + .../sync-software-metadata/tasks/main.yml | 209 ++++++ .../src/playbooks/validate_host.yml | 8 +- tox.ini | 2 +- 31 files changed, 1658 insertions(+), 33 deletions(-) create mode 100644 playbookconfig/src/playbooks/roles/common/install-sync-software-metadata/tasks/main.yml create mode 100755 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/ostree-metadata-sync.sh create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/Dockerfile create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/README.md create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/metadata/deployed/starlingx-24.03.0-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/metadata/deployed/starlingx-24.03.0-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/metadata/deployed/starlingx-24.03.1-metadata.xml create mode 100755 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/ostree-metadata-commits.central create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/ostree-metadata-commits.subcloud create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/available/starlingx-24.03.3-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deployed/starlingx-24.03.0-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deployed/starlingx-24.03.1-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deploying/starlingx-24.03.2-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/available/starlingx-24.03.3-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deployed/starlingx-24.03.0-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deployed/starlingx-24.03.1-metadata.xml create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deploying/starlingx-24.03.2-metadata.xml create mode 100755 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/ostree-metadata-commits.central create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/ostree-metadata-commits.subcloud create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/ostree-metadata-sync.bats create mode 100755 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/run-bats.sh create mode 100644 playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/tasks/main.yml diff --git a/playbookconfig/src/playbooks/install.yml b/playbookconfig/src/playbooks/install.yml index 1bef74006..40a19136f 100644 --- a/playbookconfig/src/playbooks/install.yml +++ b/playbookconfig/src/playbooks/install.yml @@ -119,4 +119,5 @@ check_bootstrap_address: false check_patches: false sync_patch_metadata: true + sync_software_metadata: true enforce_password_change: true diff --git a/playbookconfig/src/playbooks/prestage_images.yml b/playbookconfig/src/playbooks/prestage_images.yml index 3d831d9bc..0ccd7c8ed 100644 --- a/playbookconfig/src/playbooks/prestage_images.yml +++ b/playbookconfig/src/playbooks/prestage_images.yml @@ -11,13 +11,13 @@ # # Usage: # ansible-playbook prestage_images.yml -i \ -# -e "software_version= image_list_file=" +# -e "software_version= software_major_release= image_list_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 } diff --git a/playbookconfig/src/playbooks/prestage_sw_packages.yml b/playbookconfig/src/playbooks/prestage_sw_packages.yml index 4bc72c997..04206d0f0 100644 --- a/playbookconfig/src/playbooks/prestage_sw_packages.yml +++ b/playbookconfig/src/playbooks/prestage_sw_packages.yml @@ -13,29 +13,35 @@ # # Usage: # ansible-playbook prestage_sw_packages.yml -i \ -# -e "software_version=" +# -e "software_version= software_major_release= prestage_install=" - 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 diff --git a/playbookconfig/src/playbooks/roles/common/install-sync-software-metadata/tasks/main.yml b/playbookconfig/src/playbooks/roles/common/install-sync-software-metadata/tasks/main.yml new file mode 100644 index 000000000..00763a491 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/common/install-sync-software-metadata/tasks/main.yml @@ -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.stdout }}" {{ 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', '>=') diff --git a/playbookconfig/src/playbooks/roles/prestage/get-prestage-versions/tasks/main.yml b/playbookconfig/src/playbooks/roles/prestage/get-prestage-versions/tasks/main.yml index 871afda11..62ec62a50 100644 --- a/playbookconfig/src/playbooks/roles/prestage/get-prestage-versions/tasks/main.yml +++ b/playbookconfig/src/playbooks/roles/prestage/get-prestage-versions/tasks/main.yml @@ -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 }}" diff --git a/playbookconfig/src/playbooks/roles/prestage/prepare-env/tasks/main.yml b/playbookconfig/src/playbooks/roles/prestage/prepare-env/tasks/main.yml index 51fa497b6..d0a065285 100644 --- a/playbookconfig/src/playbooks/roles/prestage/prepare-env/tasks/main.yml +++ b/playbookconfig/src/playbooks/roles/prestage/prepare-env/tasks/main.yml @@ -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 diff --git a/playbookconfig/src/playbooks/roles/prestage/prepare-env/tasks/prepare-sw-packages-prestage-env.yml b/playbookconfig/src/playbooks/roles/prestage/prepare-env/tasks/prepare-sw-packages-prestage-env.yml index 7558871b0..1319823c0 100644 --- a/playbookconfig/src/playbooks/roles/prestage/prepare-env/tasks/prepare-sw-packages-prestage-env.yml +++ b/playbookconfig/src/playbooks/roles/prestage/prepare-env/tasks/prepare-sw-packages-prestage-env.yml @@ -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 }} diff --git a/playbookconfig/src/playbooks/roles/prestage/prestage-patches/tasks/prestage-patch-metadata.yml b/playbookconfig/src/playbooks/roles/prestage/prestage-patches/tasks/prestage-patch-metadata.yml index 30c32891f..8eb2a1073 100644 --- a/playbookconfig/src/playbooks/roles/prestage/prestage-patches/tasks/prestage-patch-metadata.yml +++ b/playbookconfig/src/playbooks/roles/prestage/prestage-patches/tasks/prestage-patch-metadata.yml @@ -49,7 +49,7 @@ - name: Delete those patch metadata files not belonging to the prestage software version shell: > - grep -L "{{ prestage_software_version }}" + grep -L "{{ software_major_release }}" {{ item }} 2>/dev/null | xargs rm -f loop: - "{{ patches_prestage_dir }}/metadata/applied/*" diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/ostree-metadata-sync.sh b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/ostree-metadata-sync.sh new file mode 100755 index 000000000..47dc6ca3b --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/ostree-metadata-sync.sh @@ -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< [ 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: : + 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 : Save script output to file. + -v|--sw-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 or colorecho -n + 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}" "${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}" "${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 + # are on the same line. + local meta_file=$1 + local attrib=$2 + local val + val=$(sed -n 's|<'"${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|\(.*\)|\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;}' +} + +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: : + # 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//-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 diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/Dockerfile b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/Dockerfile new file mode 100644 index 000000000..fb4e1c54a --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/Dockerfile @@ -0,0 +1,9 @@ +FROM bats/bats + +RUN \ + apk --no-cache --update add \ + coreutils \ + diffutils \ + grep \ + gawk \ + sed diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/README.md b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/README.md new file mode 100644 index 000000000..67fe54751 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/README.md @@ -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. \ No newline at end of file diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/metadata/deployed/starlingx-24.03.0-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/metadata/deployed/starlingx-24.03.0-metadata.xml new file mode 100644 index 000000000..eccd5327d --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/metadata/deployed/starlingx-24.03.0-metadata.xml @@ -0,0 +1,19 @@ + + + starlingx-24.03.0 + 24.03.0 + starlingx + STX 24.03 GA release + STX 24.03 major GA release + + + REL + Y + Y + + + + + + + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/metadata/deployed/starlingx-24.03.0-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/metadata/deployed/starlingx-24.03.0-metadata.xml new file mode 100644 index 000000000..eccd5327d --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/metadata/deployed/starlingx-24.03.0-metadata.xml @@ -0,0 +1,19 @@ + + + starlingx-24.03.0 + 24.03.0 + starlingx + STX 24.03 GA release + STX 24.03 major GA release + + + REL + Y + Y + + + + + + + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/metadata/deployed/starlingx-24.03.1-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/metadata/deployed/starlingx-24.03.1-metadata.xml new file mode 100644 index 000000000..7fa7b7b7d --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/metadata/deployed/starlingx-24.03.1-metadata.xml @@ -0,0 +1,27 @@ + + starlingx-24.03.1 + 24.03.1 + starlingx + Sample inservice test patch + This patch DOESN'T require reboot. + This patch should include 1 packages: + - logmgmt + Sample instructions + Sample warning + DEV + N + N + + + starlingx-24.03.0 + + pre-install.sh + post-install.sh + deploy-precheck.sh + + logmgmt_1.0-1.stx.9_all.deb + + 1 + 895ea56dd33882a91a872871f6989c7978ff1413404e2574693380186dbb09a7 + db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b + \ No newline at end of file diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/ostree-metadata-commits.central b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/ostree-metadata-commits.central new file mode 100755 index 000000000..05af9e222 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/ostree-metadata-commits.central @@ -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 diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/ostree-metadata-commits.subcloud b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/ostree-metadata-commits.subcloud new file mode 100644 index 000000000..a16fa4cf8 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test1/tmp/metadata-sync/ostree-metadata-commits.subcloud @@ -0,0 +1 @@ +/opt/software/metadata/deployed/starlingx-24.03.0-metadata.xml:- diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/available/starlingx-24.03.3-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/available/starlingx-24.03.3-metadata.xml new file mode 100644 index 000000000..7a699ac7a --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/available/starlingx-24.03.3-metadata.xml @@ -0,0 +1,25 @@ + + starlingx-24.03.3 + 24.03.3 + starlingx + Sample inservice test patch + Test + Sample instructions + Sample warning + DEV + N + N + + + starlingx-24.03.0 + + pre-install.sh + post-install.sh + deploy-precheck.sh + + logmgmt_1.0-1.stx.9_all.deb + + 1 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deployed/starlingx-24.03.0-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deployed/starlingx-24.03.0-metadata.xml new file mode 100644 index 000000000..eccd5327d --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deployed/starlingx-24.03.0-metadata.xml @@ -0,0 +1,19 @@ + + + starlingx-24.03.0 + 24.03.0 + starlingx + STX 24.03 GA release + STX 24.03 major GA release + + + REL + Y + Y + + + + + + + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deployed/starlingx-24.03.1-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deployed/starlingx-24.03.1-metadata.xml new file mode 100644 index 000000000..7fa7b7b7d --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deployed/starlingx-24.03.1-metadata.xml @@ -0,0 +1,27 @@ + + starlingx-24.03.1 + 24.03.1 + starlingx + Sample inservice test patch + This patch DOESN'T require reboot. + This patch should include 1 packages: + - logmgmt + Sample instructions + Sample warning + DEV + N + N + + + starlingx-24.03.0 + + pre-install.sh + post-install.sh + deploy-precheck.sh + + logmgmt_1.0-1.stx.9_all.deb + + 1 + 895ea56dd33882a91a872871f6989c7978ff1413404e2574693380186dbb09a7 + db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b + \ No newline at end of file diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deploying/starlingx-24.03.2-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deploying/starlingx-24.03.2-metadata.xml new file mode 100644 index 000000000..2374fca80 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/metadata/deploying/starlingx-24.03.2-metadata.xml @@ -0,0 +1,25 @@ + + starlingx-24.03.2 + 24.03.2 + starlingx + Sample inservice test patch + Test + Sample instructions + Sample warning + DEV + N + N + + + starlingx-24.03.0 + + pre-install.sh + post-install.sh + deploy-precheck.sh + + logmgmt_1.0-1.stx.9_all.deb + + 1 + db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/available/starlingx-24.03.3-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/available/starlingx-24.03.3-metadata.xml new file mode 100644 index 000000000..7a699ac7a --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/available/starlingx-24.03.3-metadata.xml @@ -0,0 +1,25 @@ + + starlingx-24.03.3 + 24.03.3 + starlingx + Sample inservice test patch + Test + Sample instructions + Sample warning + DEV + N + N + + + starlingx-24.03.0 + + pre-install.sh + post-install.sh + deploy-precheck.sh + + logmgmt_1.0-1.stx.9_all.deb + + 1 + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deployed/starlingx-24.03.0-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deployed/starlingx-24.03.0-metadata.xml new file mode 100644 index 000000000..eccd5327d --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deployed/starlingx-24.03.0-metadata.xml @@ -0,0 +1,19 @@ + + + starlingx-24.03.0 + 24.03.0 + starlingx + STX 24.03 GA release + STX 24.03 major GA release + + + REL + Y + Y + + + + + + + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deployed/starlingx-24.03.1-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deployed/starlingx-24.03.1-metadata.xml new file mode 100644 index 000000000..7fa7b7b7d --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deployed/starlingx-24.03.1-metadata.xml @@ -0,0 +1,27 @@ + + starlingx-24.03.1 + 24.03.1 + starlingx + Sample inservice test patch + This patch DOESN'T require reboot. + This patch should include 1 packages: + - logmgmt + Sample instructions + Sample warning + DEV + N + N + + + starlingx-24.03.0 + + pre-install.sh + post-install.sh + deploy-precheck.sh + + logmgmt_1.0-1.stx.9_all.deb + + 1 + 895ea56dd33882a91a872871f6989c7978ff1413404e2574693380186dbb09a7 + db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b + \ No newline at end of file diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deploying/starlingx-24.03.2-metadata.xml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deploying/starlingx-24.03.2-metadata.xml new file mode 100644 index 000000000..2374fca80 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/metadata/deploying/starlingx-24.03.2-metadata.xml @@ -0,0 +1,25 @@ + + starlingx-24.03.2 + 24.03.2 + starlingx + Sample inservice test patch + Test + Sample instructions + Sample warning + DEV + N + N + + + starlingx-24.03.0 + + pre-install.sh + post-install.sh + deploy-precheck.sh + + logmgmt_1.0-1.stx.9_all.deb + + 1 + db313865837f9512b024a2356bd76106140ebcea783f8183e5fcc8d5cd28783b + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/ostree-metadata-commits.central b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/ostree-metadata-commits.central new file mode 100755 index 000000000..ec0747e47 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/ostree-metadata-commits.central @@ -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 diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/ostree-metadata-commits.subcloud b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/ostree-metadata-commits.subcloud new file mode 100644 index 000000000..a16fa4cf8 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/metadata/test2/tmp/metadata-sync/ostree-metadata-commits.subcloud @@ -0,0 +1 @@ +/opt/software/metadata/deployed/starlingx-24.03.0-metadata.xml:- diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/ostree-metadata-sync.bats b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/ostree-metadata-sync.bats new file mode 100644 index 000000000..9b0112156 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/ostree-metadata-sync.bats @@ -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 + 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 + 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" +} + diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/run-bats.sh b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/run-bats.sh new file mode 100755 index 000000000..19d8155d8 --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/files/test/run-bats.sh @@ -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 diff --git a/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/tasks/main.yml b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/tasks/main.yml new file mode 100644 index 000000000..81ffe120a --- /dev/null +++ b/playbookconfig/src/playbooks/roles/prestage/sync-software-metadata/tasks/main.yml @@ -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 diff --git a/playbookconfig/src/playbooks/validate_host.yml b/playbookconfig/src/playbooks/validate_host.yml index e8442011f..3f8dcd2dc 100644 --- a/playbookconfig/src/playbooks/validate_host.yml +++ b/playbookconfig/src/playbooks/validate_host.yml @@ -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 diff --git a/tox.ini b/tox.ini index 34b47e945..e46ad3eaa 100644 --- a/tox.ini +++ b/tox.ini @@ -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' \