mirror of
https://github.com/containers/bootc.git
synced 2026-02-05 15:45:53 +01:00
Implementation of adoption
So far we've supported updating systems that we installed, but we also need to handle updating at least older CoreOS systems. This shares a lot of similarity with `update`; the biggest difference is that we aren't sure which files we should be managing. So given a pending update, we only replace files that exist in that update. Closes: https://github.com/coreos/bootupd/issues/38
This commit is contained in:
committed by
OpenShift Merge Robot
parent
e3c36fb4cb
commit
6d9d2e36a4
@@ -47,10 +47,16 @@ cosaPod(buildroot: true, runAsUser: 0, memory: "3072Mi", cpu: "4") {
|
||||
mkdir -p overrides/rootfs
|
||||
mv insttree/* overrides/rootfs/
|
||||
rmdir insttree
|
||||
coreos-assembler fetch
|
||||
cosa fetch
|
||||
cosa build
|
||||
""")
|
||||
}
|
||||
// The e2e-update test does a build, so we just end at fetch above
|
||||
// The e2e-adopt test will use the ostree commit we just generated above
|
||||
// but a static qemu base image.
|
||||
stage("e2e adopt test") {
|
||||
shwrap("env COSA_DIR=${env.WORKSPACE} ./tests/e2e-adopt/e2e-adopt.sh")
|
||||
}
|
||||
// Now a test that upgrades using bootupd
|
||||
stage("e2e upgrade test") {
|
||||
shwrap("env COSA_DIR=${env.WORKSPACE} ./tests/e2e-update/e2e-update.sh")
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ pub(crate) const WRITE_LOCK_PATH: &str = "run/bootupd-lock";
|
||||
pub(crate) enum ClientRequest {
|
||||
/// Update a component
|
||||
Update { component: String },
|
||||
/// Update a component via adoption
|
||||
AdoptAndUpdate { component: String },
|
||||
/// Validate a component
|
||||
Validate { component: String },
|
||||
/// Print the current state
|
||||
@@ -148,6 +150,28 @@ pub(crate) fn update(name: &str) -> Result<ComponentUpdateResult> {
|
||||
})
|
||||
}
|
||||
|
||||
/// daemon implementation of component adoption
|
||||
pub(crate) fn adopt_and_update(name: &str) -> Result<ContentMetadata> {
|
||||
let sysroot = openat::Dir::open("/")?;
|
||||
let _lock = acquire_write_lock("/").context("Failed to acquire write lock")?;
|
||||
let mut state = get_saved_state("/")?.unwrap_or_default();
|
||||
let component = component::new_from_name(name)?;
|
||||
if state.installed.get(name).is_some() {
|
||||
anyhow::bail!("Component {} is already installed", name);
|
||||
};
|
||||
let update = if let Some(update) = component.query_update()? {
|
||||
update
|
||||
} else {
|
||||
anyhow::bail!("Component {} has no available update", name);
|
||||
};
|
||||
let inst = component
|
||||
.adopt_update(&update)
|
||||
.context("Failed adopt and update")?;
|
||||
state.installed.insert(component.name().into(), inst);
|
||||
update_state(&sysroot, &state)?;
|
||||
Ok(update)
|
||||
}
|
||||
|
||||
/// daemon implementation of component validate
|
||||
pub(crate) fn validate(name: &str) -> Result<ValidationResult> {
|
||||
let state = get_saved_state("/")?.unwrap_or_default();
|
||||
@@ -228,6 +252,7 @@ pub(crate) fn status() -> Result<Status> {
|
||||
.flatten();
|
||||
let update = component.query_update()?;
|
||||
let updatable = ComponentUpdatable::from_metadata(&ic.meta, update.as_ref());
|
||||
let adopted_from = ic.adopted_from.clone();
|
||||
ret.components.insert(
|
||||
name.to_string(),
|
||||
ComponentStatus {
|
||||
@@ -235,6 +260,7 @@ pub(crate) fn status() -> Result<Status> {
|
||||
interrupted: interrupted.cloned(),
|
||||
update,
|
||||
updatable,
|
||||
adopted_from,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -242,7 +268,15 @@ pub(crate) fn status() -> Result<Status> {
|
||||
log::trace!("No saved state");
|
||||
}
|
||||
|
||||
// Process the remaining components not installed
|
||||
log::trace!("Remaining known components: {}", known_components.len());
|
||||
for (name, component) in known_components {
|
||||
if let Some(adopt_ver) = component.query_adopt()? {
|
||||
ret.adoptable.insert(name.to_string(), adopt_ver);
|
||||
} else {
|
||||
log::trace!("Not adoptable: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
@@ -273,6 +307,13 @@ pub(crate) fn print_status(status: &Status) -> Result<()> {
|
||||
println!(" Update: {}", msg);
|
||||
}
|
||||
|
||||
if status.adoptable.is_empty() {
|
||||
println!("No components are adoptable.");
|
||||
}
|
||||
for (name, version) in status.adoptable.iter() {
|
||||
println!("Adoptable: {}: {}", name, version.version);
|
||||
}
|
||||
|
||||
if let Some(coreos_aleph) = coreos::get_aleph_version()? {
|
||||
println!("CoreOS aleph image ID: {}", coreos_aleph.aleph.imgid);
|
||||
}
|
||||
@@ -350,6 +391,22 @@ pub(crate) fn client_run_update(c: &mut ipc::ClientToDaemonConnection) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn client_run_adopt_and_update(c: &mut ipc::ClientToDaemonConnection) -> Result<()> {
|
||||
validate_preview_env()?;
|
||||
let status: Status = c.send(&ClientRequest::Status)?;
|
||||
if status.adoptable.is_empty() {
|
||||
println!("No components are adoptable.");
|
||||
} else {
|
||||
for (name, _) in status.adoptable.iter() {
|
||||
let r: ContentMetadata = c.send(&ClientRequest::AdoptAndUpdate {
|
||||
component: name.to_string(),
|
||||
})?;
|
||||
println!("Adopted and updated: {}: {}", name, r.version);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn client_run_validate(c: &mut ipc::ClientToDaemonConnection) -> Result<()> {
|
||||
let status: Status = c.send(&ClientRequest::Status)?;
|
||||
if status.components.is_empty() {
|
||||
|
||||
@@ -42,6 +42,8 @@ pub enum CtlVerb {
|
||||
Status(StatusOpts),
|
||||
#[structopt(name = "update", about = "Update all components")]
|
||||
Update,
|
||||
#[structopt(name = "adopt-and-update", about = "Update all adoptable components")]
|
||||
AdoptAndUpdate,
|
||||
#[structopt(name = "validate", about = "Validate system state")]
|
||||
Validate,
|
||||
}
|
||||
@@ -67,6 +69,7 @@ impl CtlCommand {
|
||||
match self.cmd {
|
||||
CtlVerb::Status(opts) => Self::run_status(opts),
|
||||
CtlVerb::Update => Self::run_update(),
|
||||
CtlVerb::AdoptAndUpdate => Self::run_adopt_and_update(),
|
||||
CtlVerb::Validate => Self::run_validate(),
|
||||
CtlVerb::Backend(CtlBackend::Generate(opts)) => {
|
||||
super::bootupd::DCommand::run_generate_meta(opts)
|
||||
@@ -106,6 +109,17 @@ impl CtlCommand {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runner for `update` verb.
|
||||
fn run_adopt_and_update() -> Result<()> {
|
||||
let mut client = ClientToDaemonConnection::new();
|
||||
client.connect()?;
|
||||
|
||||
bootupd::client_run_adopt_and_update(&mut client)?;
|
||||
|
||||
client.shutdown()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Runner for `validate` verb.
|
||||
fn run_validate() -> Result<()> {
|
||||
let mut client = ClientToDaemonConnection::new();
|
||||
|
||||
@@ -25,6 +25,14 @@ pub(crate) trait Component {
|
||||
/// and should remain stable.
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
/// In an operating system whose initially booted disk image is not
|
||||
/// using bootupd, detect whether it looks like the component exists
|
||||
/// and "synthesize" content metadata from it.
|
||||
fn query_adopt(&self) -> Result<Option<ContentMetadata>>;
|
||||
|
||||
/// Given an adoptable system and an update, perform the update.
|
||||
fn adopt_update(&self, update: &ContentMetadata) -> Result<InstalledContent>;
|
||||
|
||||
/// Implementation of `bootupd install` for a given component. This should
|
||||
/// gather data (or run binaries) from the source root, and install them
|
||||
/// into the target root. It is expected that sub-partitions (e.g. the ESP)
|
||||
|
||||
@@ -24,7 +24,6 @@ pub(crate) struct Aleph {
|
||||
|
||||
pub(crate) struct AlephWithTimestamp {
|
||||
pub(crate) aleph: Aleph,
|
||||
#[allow(dead_code)]
|
||||
pub(crate) ts: chrono::DateTime<Utc>,
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,12 @@ fn process_client_requests(client: ipc::AuthenticatedClient) -> Result<()> {
|
||||
Err(e) => ipc::DaemonToClientReply::Failure(format!("{:#}", e)),
|
||||
})?
|
||||
}
|
||||
ClientRequest::AdoptAndUpdate { component } => {
|
||||
bincode::serialize(&match bootupd::adopt_and_update(component.as_str()) {
|
||||
Ok(v) => ipc::DaemonToClientReply::Success::<crate::model::ContentMetadata>(v),
|
||||
Err(e) => ipc::DaemonToClientReply::Failure(format!("{:#}", e)),
|
||||
})?
|
||||
}
|
||||
ClientRequest::Validate { component } => {
|
||||
bincode::serialize(&match bootupd::validate(component.as_str()) {
|
||||
Ok(v) => ipc::DaemonToClientReply::Success::<ValidationResult>(v),
|
||||
|
||||
69
src/efi.rs
69
src/efi.rs
@@ -6,10 +6,11 @@
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::io::prelude::*;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use openat_ext::OpenatDirExt;
|
||||
|
||||
use chrono::prelude::*;
|
||||
|
||||
@@ -26,11 +27,73 @@ pub(crate) const MOUNT_PATH: &str = "boot/efi";
|
||||
#[derive(Default)]
|
||||
pub(crate) struct EFI {}
|
||||
|
||||
impl EFI {
|
||||
fn esp_path(&self) -> PathBuf {
|
||||
Path::new(MOUNT_PATH).join("EFI")
|
||||
}
|
||||
|
||||
fn open_esp_optional(&self) -> Result<Option<openat::Dir>> {
|
||||
let sysroot = openat::Dir::open("/")?;
|
||||
let esp = sysroot.sub_dir_optional(&self.esp_path())?;
|
||||
Ok(esp)
|
||||
}
|
||||
fn open_esp(&self) -> Result<openat::Dir> {
|
||||
let sysroot = openat::Dir::open("/")?;
|
||||
let esp = sysroot.sub_dir(&self.esp_path())?;
|
||||
Ok(esp)
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for EFI {
|
||||
fn name(&self) -> &'static str {
|
||||
"EFI"
|
||||
}
|
||||
|
||||
fn query_adopt(&self) -> Result<Option<ContentMetadata>> {
|
||||
let esp = self.open_esp_optional()?;
|
||||
if esp.is_none() {
|
||||
log::trace!("No ESP detected");
|
||||
return Ok(None);
|
||||
};
|
||||
// This would be extended with support for other operating systems later
|
||||
let coreos_aleph = if let Some(a) = crate::coreos::get_aleph_version()? {
|
||||
a
|
||||
} else {
|
||||
log::trace!("No CoreOS aleph detected");
|
||||
return Ok(None);
|
||||
};
|
||||
let meta = ContentMetadata {
|
||||
timestamp: coreos_aleph.ts,
|
||||
version: coreos_aleph.aleph.imgid,
|
||||
};
|
||||
log::trace!("EFI adoptable: {:?}", &meta);
|
||||
Ok(Some(meta))
|
||||
}
|
||||
|
||||
/// Given an adoptable system and an update, perform the update.
|
||||
fn adopt_update(&self, updatemeta: &ContentMetadata) -> Result<InstalledContent> {
|
||||
let meta = if let Some(meta) = self.query_adopt()? {
|
||||
meta
|
||||
} else {
|
||||
anyhow::bail!("Failed to find adoptable system");
|
||||
};
|
||||
|
||||
let esp = self.open_esp()?;
|
||||
validate_esp(&esp)?;
|
||||
let updated =
|
||||
openat::Dir::open(&component_updatedir("/", self)).context("opening update dir")?;
|
||||
let updatef = filetree::FileTree::new_from_dir(&updated).context("reading update dir")?;
|
||||
// For adoption, we should only touch files that we know about.
|
||||
let diff = updatef.relative_diff_to(&esp)?;
|
||||
log::trace!("applying adoption diff: {}", &diff);
|
||||
filetree::apply_diff(&updated, &esp, &diff, None).context("applying filesystem changes")?;
|
||||
Ok(InstalledContent {
|
||||
meta: updatemeta.clone(),
|
||||
filetree: Some(updatef),
|
||||
adopted_from: Some(meta),
|
||||
})
|
||||
}
|
||||
|
||||
fn install(&self, src_root: &str, dest_root: &str) -> Result<InstalledContent> {
|
||||
let meta = if let Some(meta) = get_component_update(src_root, self)? {
|
||||
meta
|
||||
@@ -56,6 +119,7 @@ impl Component for EFI {
|
||||
Ok(InstalledContent {
|
||||
meta,
|
||||
filetree: Some(ft),
|
||||
adopted_from: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,11 +136,14 @@ impl Component for EFI {
|
||||
let destdir = openat::Dir::open(&Path::new("/").join(MOUNT_PATH).join("EFI"))
|
||||
.context("opening EFI dir")?;
|
||||
validate_esp(&destdir)?;
|
||||
log::trace!("applying diff: {}", &diff);
|
||||
filetree::apply_diff(&updated, &destdir, &diff, None)
|
||||
.context("applying filesystem changes")?;
|
||||
let adopted_from = None;
|
||||
Ok(InstalledContent {
|
||||
meta: updatemeta,
|
||||
filetree: Some(updatef),
|
||||
adopted_from: adopted_from,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ use openat_ext::OpenatDirExt;
|
||||
use openssl::hash::{Hasher, MessageDigest};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::fmt::Display;
|
||||
use std::os::linux::fs::MetadataExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::process::CommandExt;
|
||||
@@ -49,6 +50,18 @@ pub(crate) struct FileTreeDiff {
|
||||
pub(crate) changes: HashSet<String>,
|
||||
}
|
||||
|
||||
impl Display for FileTreeDiff {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
f,
|
||||
"additions: {} removals: {} changes: {}",
|
||||
self.additions.len(),
|
||||
self.removals.len(),
|
||||
self.changes.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl FileTreeDiff {
|
||||
pub(crate) fn count(&self) -> usize {
|
||||
|
||||
@@ -37,6 +37,8 @@ pub(crate) struct InstalledContent {
|
||||
pub(crate) meta: ContentMetadata,
|
||||
/// Human readable version number, like ostree it is not ever parsed, just displayed
|
||||
pub(crate) filetree: Option<crate::filetree::FileTree>,
|
||||
/// The version this was originally adopted from
|
||||
pub(crate) adopted_from: Option<ContentMetadata>,
|
||||
}
|
||||
|
||||
/// Will be serialized into /boot/bootupd-state.json
|
||||
@@ -89,6 +91,8 @@ pub(crate) struct ComponentStatus {
|
||||
pub(crate) update: Option<ContentMetadata>,
|
||||
/// Is true if the version in `update` is different from `installed`
|
||||
pub(crate) updatable: ComponentUpdatable,
|
||||
/// Originally adopted version
|
||||
pub(crate) adopted_from: Option<ContentMetadata>,
|
||||
}
|
||||
|
||||
/// Representation of bootupd's worldview at a point in time.
|
||||
@@ -101,6 +105,8 @@ pub(crate) struct ComponentStatus {
|
||||
pub(crate) struct Status {
|
||||
/// Maps a component name to status
|
||||
pub(crate) components: BTreeMap<String, ComponentStatus>,
|
||||
/// Components that appear to be installed, not via bootupd
|
||||
pub(crate) adoptable: BTreeMap<String, ContentMetadata>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
133
tests/e2e-adopt/e2e-adopt-in-vm.sh
Executable file
133
tests/e2e-adopt/e2e-adopt-in-vm.sh
Executable file
@@ -0,0 +1,133 @@
|
||||
#!/bin/bash
|
||||
# Run inside the vm spawned from e2e.sh
|
||||
set -euo pipefail
|
||||
|
||||
dn=$(cd $(dirname $0) && pwd)
|
||||
bn=$(basename $0)
|
||||
. ${dn}/../kola/data/libtest.sh
|
||||
|
||||
cd $(mktemp -d)
|
||||
|
||||
echo "Starting $0"
|
||||
|
||||
enable_bootupd() {
|
||||
systemctl start bootupd.socket
|
||||
# For now
|
||||
export BOOTUPD_ACCEPT_PREVIEW=1
|
||||
}
|
||||
|
||||
current_commit=$(rpm-ostree status --json | jq -r .deployments[0].checksum)
|
||||
|
||||
reboot_with_mark() {
|
||||
mark=$1; shift
|
||||
runv echo ${mark} > ${reboot_mark_path}
|
||||
sync ${reboot_mark_path}
|
||||
runv systemd-run -- systemctl reboot
|
||||
touch /run/rebooting
|
||||
sleep infinity
|
||||
}
|
||||
|
||||
status_ok_no_update() {
|
||||
bootupctl status | tee out.txt
|
||||
assert_file_has_content_literal out.txt 'Component EFI'
|
||||
assert_file_has_content_literal out.txt ' Installed: grub2-efi-x64-'
|
||||
assert_file_has_content_literal out.txt 'Update: At latest version'
|
||||
assert_file_has_content out.txt 'CoreOS aleph image ID: .*coreos.*-qemu'
|
||||
bootupctl validate
|
||||
ok status and validate
|
||||
}
|
||||
|
||||
reboot_mark_path=/etc/${bn}.rebootstamp
|
||||
reboot_mark=
|
||||
if test -f "${reboot_mark_path}"; then
|
||||
reboot_mark=$(cat ${reboot_mark_path})
|
||||
fi
|
||||
case "${reboot_mark}" in
|
||||
"")
|
||||
if test "${current_commit}" = ${TARGET_COMMIT}; then
|
||||
fatal "already at ${TARGET_COMMIT}"
|
||||
fi
|
||||
|
||||
# This system wasn't built via bootupd
|
||||
assert_not_has_file /boot/bootupd-state.json
|
||||
|
||||
# FIXME
|
||||
# https://github.com/coreos/rpm-ostree/issues/2210
|
||||
runv setenforce 0
|
||||
runv rpm-ostree rebase /run/cosadir/tmp/repo:${TARGET_COMMIT}
|
||||
reboot_with_mark first
|
||||
;;
|
||||
first)
|
||||
if test "${current_commit}" != ${TARGET_COMMIT}; then
|
||||
fatal "not at ${TARGET_COMMIT}"
|
||||
fi
|
||||
# NOTE Fall through NOTE
|
||||
;;
|
||||
second)
|
||||
enable_bootupd
|
||||
status_ok_no_update
|
||||
touch /run/testtmp/success
|
||||
sync
|
||||
# TODO maybe try to make this use more of the exttest infrastructure?
|
||||
exec poweroff -ff
|
||||
;;
|
||||
esac
|
||||
|
||||
enable_bootupd
|
||||
|
||||
# We did setenforce 0 above for https://github.com/coreos/rpm-ostree/issues/2210
|
||||
# Validate that on reboot we're still enforcing.
|
||||
semode=$(getenforce)
|
||||
if test "$semode" != Enforcing; then
|
||||
fatal "SELinux mode is ${semode}"
|
||||
fi
|
||||
|
||||
source_grub_cfg=$(find /boot/efi/EFI -name grub.cfg)
|
||||
test -f "${source_grub_cfg}"
|
||||
|
||||
source_grub=$(find /boot/efi/EFI -name grubx64.efi)
|
||||
test -f ${source_grub}
|
||||
source_grub_sha256=$(sha256sum ${source_grub} | cut -f 1 -d ' ')
|
||||
|
||||
update_grub=$(find /usr/lib/bootupd/updates/EFI/ -name grubx64.efi)
|
||||
test -f ${update_grub}
|
||||
update_grub_sha256=$(sha256sum ${update_grub} | cut -f 1 -d ' ')
|
||||
if test "${source_grub_sha256}" = "${update_grub_sha256}"; then
|
||||
fatal "Already have target grubx64.efi"
|
||||
fi
|
||||
|
||||
bootupctl status | tee out.txt
|
||||
assert_file_has_content_literal out.txt 'No components installed.'
|
||||
assert_file_has_content out.txt 'Adoptable: EFI: .*coreos.*-qemu.*'
|
||||
|
||||
bootupctl validate | tee out.txt
|
||||
assert_file_has_content_literal out.txt 'No components installed.'
|
||||
assert_not_file_has_content_literal out.txt "Validated"
|
||||
# Shouldn't write state just starting and validating
|
||||
assert_not_has_file /boot/bootupd-state.json
|
||||
ok validate
|
||||
|
||||
bootupctl adopt-and-update | tee out.txt
|
||||
assert_file_has_content out.txt 'Adopted and updated: EFI: grub2-efi-x64'
|
||||
ok adoption
|
||||
|
||||
status_ok_no_update
|
||||
|
||||
bootupctl validate | tee out.txt
|
||||
assert_not_file_has_content_literal out.txt "Validated EFI"
|
||||
|
||||
new_grub_sha256=$(sha256sum ${source_grub} | cut -f 1 -d ' ')
|
||||
if test "${new_grub_sha256}" != "${update_grub_sha256}"; then
|
||||
fatal "Failed to update grub"
|
||||
fi
|
||||
ok updated grub
|
||||
|
||||
# We shouldn't have deleted the config file which was unmanaged
|
||||
test -f "${source_grub_cfg}"
|
||||
ok still have grub.cfg
|
||||
|
||||
tap_finish
|
||||
|
||||
# And now do another reboot to validate that things are good
|
||||
reboot_with_mark second
|
||||
|
||||
92
tests/e2e-adopt/e2e-adopt.sh
Executable file
92
tests/e2e-adopt/e2e-adopt.sh
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/bin/bash
|
||||
# Given an old FCOS build (pre-bootupd), upgrade
|
||||
# to the latest build in ${COSA_DIR} and run through
|
||||
# the adoption procedure to update the ESP.
|
||||
set -euo pipefail
|
||||
|
||||
# There was a grub2-efi-x64 change after this
|
||||
PRE_BOOTUPD_FCOS=https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/32.20200907.3.0/x86_64/meta.json
|
||||
|
||||
dn=$(cd $(dirname $0) && pwd)
|
||||
testprefix=$(cd ${dn} && git rev-parse --show-prefix)
|
||||
. ${dn}/../kola/data/libtest.sh
|
||||
|
||||
if test -z "${COSA_DIR:-}"; then
|
||||
fatal "COSA_DIR must be set"
|
||||
fi
|
||||
# Validate source directory
|
||||
bootupd_git=$(cd ${dn} && git rev-parse --show-toplevel)
|
||||
test -f ${bootupd_git}/systemd/bootupd.service
|
||||
|
||||
testtmp=$(mktemp -d -p /var/tmp bootupd-e2e.XXXXXXX)
|
||||
export test_tmpdir=${testtmp}
|
||||
cd ${test_tmpdir}
|
||||
runv curl -sSL -o meta.json ${PRE_BOOTUPD_FCOS}
|
||||
jq .images.qemu < meta.json > qemu.json
|
||||
qemu_image_xz=$(jq -r .path < qemu.json)
|
||||
qemu_image=${qemu_image_xz%%.xz}
|
||||
if test -f "${COSA_DIR}/tmp/${qemu_image}"; then
|
||||
qemu_image="${COSA_DIR}/tmp/${qemu_image}"
|
||||
else
|
||||
runv curl -sSL $(dirname ${PRE_BOOTUPD_FCOS})/${qemu_image_xz} | xz -d > ${COSA_DIR}/tmp/${qemu_image}.tmp
|
||||
mv ${COSA_DIR}/tmp/${qemu_image}{.tmp,}
|
||||
qemu_image=${COSA_DIR}/tmp/${qemu_image}
|
||||
fi
|
||||
|
||||
# Start in cosa dir
|
||||
cd ${COSA_DIR}
|
||||
test -d builds
|
||||
|
||||
echo "Preparing test"
|
||||
target_commit=$(cosa meta --get-value ostree-commit)
|
||||
echo "Target commit: ${target_commit}"
|
||||
|
||||
execstop='test -f /run/rebooting || poweroff -ff'
|
||||
if test -n "${e2e_debug:-}"; then
|
||||
execstop=
|
||||
fi
|
||||
cat >${testtmp}/test.fcct << EOF
|
||||
variant: fcos
|
||||
version: 1.0.0
|
||||
systemd:
|
||||
units:
|
||||
- name: zincati.service
|
||||
dropins:
|
||||
- name: disabled.conf
|
||||
contents: |
|
||||
[Unit]
|
||||
# Disable zincati, we're going to do our own updates
|
||||
ConditionPathExists=/nosuchfile
|
||||
- name: bootupd-test.service
|
||||
enabled: true
|
||||
contents: |
|
||||
[Unit]
|
||||
RequiresMountsFor=/run/testtmp
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
Environment=TARGET_COMMIT=${target_commit}
|
||||
Environment=SRCDIR=/run/bootupd-source
|
||||
# Run via shell because selinux denies systemd writing to 9p apparently
|
||||
ExecStart=/bin/sh -c '/run/bootupd-source/${testprefix}/e2e-adopt-in-vm.sh &>>/run/testtmp/out.txt; ${execstop}'
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
runv fcct -o ${testtmp}/test.ign ${testtmp}/test.fcct
|
||||
cd ${testtmp}
|
||||
qemuexec_args=(kola qemuexec --propagate-initramfs-failure --qemu-image "${qemu_image}" --qemu-firmware uefi \
|
||||
-i test.ign --bind-ro ${COSA_DIR},/run/cosadir --bind-ro ${bootupd_git},/run/bootupd-source --bind-rw .,/run/testtmp)
|
||||
if test -n "${e2e_debug:-}"; then
|
||||
runv ${qemuexec_args[@]} --devshell
|
||||
else
|
||||
runv timeout 5m "${qemuexec_args[@]}" --console-to-file $(pwd)/console.txt
|
||||
fi
|
||||
if ! test -f ${testtmp}/success; then
|
||||
if test -s ${testtmp}/out.txt; then
|
||||
sed -e 's,^,# ,' < ${testtmp}/out.txt
|
||||
else
|
||||
echo "No out.txt created, systemd unit failed to start"
|
||||
fi
|
||||
fatal "test failed"
|
||||
fi
|
||||
echo "ok bootupd e2e"
|
||||
3
tests/fixtures/example-status-v0.json
vendored
3
tests/fixtures/example-status-v0.json
vendored
@@ -13,5 +13,6 @@
|
||||
"updatable": "at-latest-version",
|
||||
"adopted-from": null
|
||||
}
|
||||
}
|
||||
},
|
||||
"adoptable": {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user