Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
95 commits
Select commit Hold shift + click to select a range
eec6785
implement manual rollback for abupdate
bfjelds Dec 5, 2025
7b9358a
comments; cleanup
bfjelds Dec 5, 2025
ed4a931
update cli docs
bfjelds Dec 5, 2025
1c55cc4
add trident version to host status
bfjelds Dec 5, 2025
86b88d8
add quick rollback test to e2e for combined
bfjelds Dec 8, 2025
73209ef
hardcode encryption
bfjelds Dec 8, 2025
3d96cbe
set volume
bfjelds Dec 8, 2025
04c4e2d
set volume
bfjelds Dec 8, 2025
78d6671
use variable
bfjelds Dec 9, 2025
8bd2525
use variable
bfjelds Dec 9, 2025
870da72
try
bfjelds Dec 9, 2025
8962558
fix
bfjelds Dec 9, 2025
7f3c4bf
adjust rollback show and test
bfjelds Dec 9, 2025
fefb405
simplify
bfjelds Dec 9, 2025
74436f6
fix
bfjelds Dec 9, 2025
10e0a44
adjust tests
bfjelds Dec 10, 2025
a3f33fc
output
bfjelds Dec 10, 2025
76c0928
netplan runtime
bfjelds Dec 10, 2025
1d3fa35
netplan complained about openness of netplan config file
bfjelds Dec 10, 2025
e7c9d6c
add netplan testing to rollback tests
bfjelds Dec 10, 2025
d508d85
new CLI api; enable skip to ab rollback
bfjelds Dec 11, 2025
5d3e287
fix netplan validation
bfjelds Dec 11, 2025
1416f41
pub fn at top
bfjelds Dec 11, 2025
99423c5
test last_error
bfjelds Dec 11, 2025
25ef1ba
fix logging/tracing for rollback
bfjelds Dec 11, 2025
bad3166
api adjustment
bfjelds Dec 11, 2025
a391f05
Started implementing handling of encryption on manual rollback
ndubchak Dec 5, 2025
5476bf7
Updated get_binary_paths_pcrlock and helpers
ndubchak Dec 6, 2025
7ae3e49
Implemented get_uki_paths
ndubchak Dec 6, 2025
4d5a6e1
IMplemented get_bootloader_paths
ndubchak Dec 6, 2025
e9647a1
Fix post rebase + formatting
ndubchak Dec 10, 2025
5e5c7ea
Added an error and FT
ndubchak Dec 10, 2025
02024cf
Moved PCR encryption logic to staging of manual rollback
ndubchak Dec 10, 2025
fd9d83f
lint
bfjelds Dec 12, 2025
98e831e
get log contents
bfjelds Dec 12, 2025
6217cd9
output before finalize
bfjelds Dec 12, 2025
ab6fbba
fix params
bfjelds Dec 12, 2025
c46ad94
Removed failing FTs and fixed UKI paths
ndubchak Dec 12, 2025
14e3425
save logs; --check w/o sudo; fix rollback e2e chain
bfjelds Dec 12, 2025
079570f
recreate connection
bfjelds Dec 13, 2025
e5a8c2c
get commit logs
bfjelds Dec 13, 2025
df51233
ref linux env var
bfjelds Dec 13, 2025
2df2ae2
fix
bfjelds Dec 13, 2025
d811298
fix qemu
bfjelds Dec 13, 2025
bc3db3d
missed file
bfjelds Dec 13, 2025
0099a9c
Predict all PCRs but generate policy only for PCR 7
ndubchak Dec 13, 2025
a3c9172
Gate on manual rollback
ndubchak Dec 13, 2025
0957c0f
Fix bug wherre rollback binaries are requested on commit
ndubchak Dec 13, 2025
5a935ad
Restore pcr, disable ab rollback with encryption
bfjelds Dec 16, 2025
34503ca
hook up runtime rollback
bfjelds Dec 17, 2025
539a232
remove disabled/unused encryption test code
bfjelds Dec 18, 2025
42392fd
remove unused code
bfjelds Dec 18, 2025
d605a95
explain copy
bfjelds Dec 18, 2025
d07662e
add rollback to harpoon
bfjelds Dec 18, 2025
dcd2060
need pub to use in rollback
bfjelds Dec 18, 2025
189dde1
fix up comment
bfjelds Dec 18, 2025
efb8f85
check 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNK…
bfjelds Dec 18, 2025
69b6ce3
refactor; remove unused deps
bfjelds Dec 18, 2025
6f9c0fd
refactor obseletes need for pub
bfjelds Dec 18, 2025
54e2ab9
fix up harpoon
bfjelds Dec 30, 2025
2f4ae95
simplify
bfjelds Jan 5, 2026
312191b
use actual uki files to find previous uki rather than efivar
bfjelds Jan 5, 2026
e4e09da
look at preexisting uki files as well
bfjelds Jan 5, 2026
10360a4
make reboot optional
bfjelds Jan 5, 2026
4bad703
lint
bfjelds Jan 5, 2026
c61335a
cr feedback
bfjelds Jan 7, 2026
c73fa6d
match check
bfjelds Jan 7, 2026
a7c76d3
cr feedback
bfjelds Jan 7, 2026
211032b
ensure rollback --allowed-operations works
bfjelds Jan 8, 2026
d5f2932
clean up
bfjelds Jan 8, 2026
b2bccb6
move manual_rollback code together in engine
bfjelds Jan 8, 2026
5318d05
println -> debug
bfjelds Jan 8, 2026
cb71643
watch for skipruntime
bfjelds Jan 8, 2026
829d239
handle errors
bfjelds Jan 9, 2026
3693ba9
use internalerror
bfjelds Jan 9, 2026
fe7f818
remove old path
bfjelds Jan 12, 2026
3459adc
typo
bfjelds Jan 12, 2026
a7077b3
typo
bfjelds Jan 12, 2026
eed9de5
use explicit ab & runtime manual rollback states/types
bfjelds Jan 12, 2026
026e32c
make hoststatus parsing more resilient for rollback
bfjelds Jan 12, 2026
d3a6b2a
uefi fallback should be optional
bfjelds Jan 12, 2026
4497243
separate this from MR pr
bfjelds Jan 12, 2026
25dd9f8
there is no manualrollbackruntimefinalize
bfjelds Jan 12, 2026
288cad0
fix test
bfjelds Jan 12, 2026
77e54e7
feedback
bfjelds Jan 14, 2026
95894ef
merge datastore changes; fix udpate
bfjelds Jan 14, 2026
54ef4d4
favor kind over type
bfjelds Jan 14, 2026
acc80b7
clean up match
bfjelds Jan 14, 2026
492a8d1
use enum rather than 2 bools
bfjelds Jan 14, 2026
66aab54
improve error message
bfjelds Jan 14, 2026
ef9aede
clarify messages
bfjelds Jan 15, 2026
27422c4
clarify comment
bfjelds Jan 15, 2026
f817407
improve comments
bfjelds Jan 15, 2026
0535da7
fix active_index mistake
bfjelds Jan 15, 2026
9c09016
add period to comment; add comments to structs
bfjelds Jan 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,18 @@ parameters:
- name: skipManualRollbackTesting
displayName: "Skip manual rollback testing"
type: string
default: "true"
default: "false"

- name: skipRuntimeUpdateTesting
displayName: "Skip runtime update testing"
type: string
default: "false"

- name: skipNetplanRuntimeTesting
displayName: "Skip netplan runtime testing"
type: string
default: "false"

jobs:
- job: RollbackTesting_${{ replace(parameters.flavor, '-', '_') }}
displayName: Rollback Testing - ${{ parameters.flavor }}
Expand Down Expand Up @@ -135,6 +140,10 @@ jobs:
# Allow testing for non-extension scenarios
STORM_DYNAMIC_FLAGS="$STORM_DYNAMIC_FLAGS --skip-extension-testing"
fi
if [ "${{ parameters.skipNetplanRuntimeTesting }}" == "true" ]; then
# skip netplan runtime testing
STORM_DYNAMIC_FLAGS="$STORM_DYNAMIC_FLAGS --skip-netplan-runtime-testing"
fi

sudo ./bin/storm-trident run rollback -a $STORM_DYNAMIC_FLAGS \
--artifacts-dir $(Build.ArtifactStagingDirectory) \
Expand Down
13 changes: 6 additions & 7 deletions .pipelines/templates/stages/testing_rollback/vm-testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,25 +118,24 @@ stages:
verboseLogging: ${{ parameters.verboseLogging }}
platform: qemu
flavor: qemu-grub
skipExtensionTesting: false
skipRuntimeUpdateTesting: false
- ${{ if eq(parameters.includeQemu, true) }}:
- template: testing-template.yml
parameters:
updateCheckTimeoutInMinutes: ${{ parameters.updateCheckTimeoutInMinutes }}
verboseLogging: ${{ parameters.verboseLogging }}
platform: qemu
flavor: qemu
# qemu image is grub + root verity. in this setup, Image Customizer cannot add
# an extension to the servicing qcow2
# For qemu w/ root-verity, only test rollback of A/B update because:
# * extensions cannot be added to qcow2; IC cannot modify verity volume
# * netplan runtime changes cannot be made to config file on verity rootfs
# * without extensions and netplan, runtime updates will service nothing
skipExtensionTesting: true
skipRuntimeUpdateTesting: false
skipRuntimeUpdateTesting: true
skipNetplanRuntimeTesting: true
- ${{ if eq(parameters.includeUKI, true) }}:
- template: testing-template.yml
parameters:
updateCheckTimeoutInMinutes: ${{ parameters.updateCheckTimeoutInMinutes }}
verboseLogging: ${{ parameters.verboseLogging }}
platform: qemu
flavor: uki
skipExtensionTesting: false
skipRuntimeUpdateTesting: false
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ reqwest = { version = "0.12.9", features = [
"default-tls",
], default-features = false } # "http2" is enabled by default but causes a (false positive) CG alert
schemars = { version = "0.8.21", features = ["url", "uuid1"] }
semver = "1.0.23"
semver = { version = "1.0.23", features = ["serde"] }
serde = { version = "1.0.215", features = ["derive"] }
serde_json = "1.0.133"
serde_variant = "0.1.3"
Expand Down
2 changes: 1 addition & 1 deletion crates/osutils/src/efivar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ pub fn read_current_var() -> Result<String, TridentError> {
Ok(decode_utf16le(&data))
}

/// Sets the LoaderEntryDefault EFI variable to the current boot entry
/// Sets the LoaderEntryDefault EFI variable to the current boot entry.
pub fn set_default_to_current() -> Result<(), TridentError> {
let current = read_efi_variable(BOOTLOADER_INTERFACE_GUID, LOADER_ENTRY_SELECTED)?;
debug!(
Expand Down
18 changes: 16 additions & 2 deletions crates/osutils/src/netplan.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
use std::{fs, path::Path};
use std::{
fs::{self, OpenOptions},
io::Write,
os::unix::fs::OpenOptionsExt,
path::Path,
};

use anyhow::{Context, Error};
use const_format::formatcp;
Expand All @@ -19,7 +24,16 @@ pub const NETPLAN_BACKUP_FILE: &str = formatcp!("{NETPLAN_BACKUP_DIR}{TRIDENT_NE
/// Writes the given network configuration to Trident's netplan config file.
pub fn write(value: &NetworkConfig) -> Result<(), Error> {
debug!("Writing netplan config to {}", TRIDENT_NETPLAN_FILE);
fs::write(TRIDENT_NETPLAN_FILE, render_netplan_yaml(value)?)
OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.mode(0o600)
.open(TRIDENT_NETPLAN_FILE)
.with_context(|| {
format!("Failed to open netplan configuration file {TRIDENT_NETPLAN_FILE}")
})?
.write_all(render_netplan_yaml(value)?.as_bytes())
.with_context(|| format!("Failed to write netplan config to {TRIDENT_NETPLAN_FILE}"))
}

Expand Down
1 change: 1 addition & 0 deletions crates/trident/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ procfs = { workspace = true }
rayon = { workspace = true }
regex = { workspace = true }
reqwest = { workspace = true }
semver = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
serde_yaml = { workspace = true }
Expand Down
34 changes: 34 additions & 0 deletions crates/trident/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,37 @@ pub enum Commands {
history_path: Option<PathBuf>,
},

/// Trigger a manual rollback to previous state
Rollback {
/// Check operation that would be performed
#[arg(long)]
check: bool,

/// Invoke rollback only if next available rollback is runtime rollback.
/// If allowed-operations is specified, this argument is only applicable for
/// stage operation and will be ignored for finalize.
#[arg(long, conflicts_with = "ab")]
runtime: bool,

/// Invoke next available A/B rollback.
/// If allowed-operations is specified, this argument is only applicable for
/// stage operation and will be ignored for finalize.
#[arg(long, conflicts_with = "runtime")]
ab: bool,

/// Comma-separated list of operations that Trident will be allowed to perform
#[clap(long, value_delimiter = ',', num_args = 0.., default_value = "stage,finalize")]
allowed_operations: Vec<AllowedOperation>,

/// Path to save the resulting Host Status
#[clap(short, long)]
status: Option<PathBuf>,

/// Path to save an eventual fatal error
#[clap(short, long)]
error: Option<PathBuf>,
},

#[cfg(feature = "dangerous-options")]
StreamImage {
/// URL of the image to stream
Expand Down Expand Up @@ -216,6 +247,7 @@ impl Commands {
#[cfg(feature = "dangerous-options")]
Commands::StreamImage { .. } => "stream-image",
Commands::Daemon { .. } => "daemon",
Commands::Rollback { .. } => "rollback",
}
}
}
Expand All @@ -231,4 +263,6 @@ pub enum GetKind {
Configuration,
Status,
LastError,
RollbackChain,
RollbackTarget,
}
52 changes: 50 additions & 2 deletions crates/trident/src/datastore.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
use std::{fs, path::Path};

use log::debug;
use sqlite::State;

use trident_api::{
error::{
DatastoreError, InternalError, ReportError, ServicingError, TridentError, TridentResultExt,
},
status::{decode_host_status, HostStatus},
status::{decode_host_status, HostStatus, TridentVersion},
};

use crate::TRIDENT_SEMVER_VERSION;

pub struct DataStore {
db: Option<sqlite::Connection>,
host_status: HostStatus,
Expand Down Expand Up @@ -75,6 +78,45 @@ impl DataStore {
})
}

/// Retrieve all HostStatus entries from the datastore, sorted from oldest to newest.
pub(crate) fn get_host_statuses(&self) -> Result<Vec<HostStatus>, TridentError> {
let mut all_rows_data: Vec<HostStatus> = Vec::new();

// Read all HostStatus entries from the datastore, parse them into
// HostStatus structs, and return a slice of them.
let mut query_statement = self
.db
.as_ref()
.structured(ServicingError::from(DatastoreError::OpenDatastore))?
.prepare("SELECT contents FROM hoststatus ORDER BY id ASC")
.structured(ServicingError::Datastore {
inner: DatastoreError::InitializeDatastore,
})
.message("Failed to read all database host statuses")?;

while let State::Row = query_statement
.next()
.structured(ServicingError::Datastore {
inner: DatastoreError::ReadDatastore,
})
.message("Failed to get next datastore row")?
{
let host_status_yaml = query_statement
.read::<String, _>(0)
.structured(ServicingError::Datastore {
inner: DatastoreError::ReadDatastore,
})
.message("Failed to read datastore row")?;
let host_status = serde_yaml::from_str(&host_status_yaml)
.structured(ServicingError::Datastore {
inner: DatastoreError::ReadDatastore,
})
.message("Failed to parse Host Status as YAML")?;
all_rows_data.push(host_status);
}
Ok(all_rows_data)
}

pub(crate) fn is_persistent(&self) -> bool {
!self.temporary
}
Expand All @@ -101,6 +143,8 @@ impl DataStore {
if self.temporary {
let persistent_db = Self::make_datastore(path)?;
self.host_status.is_management_os = false;
self.host_status.trident_version =
TridentVersion::SemVer(TRIDENT_SEMVER_VERSION.clone());
Self::write_host_status(&persistent_db, self.host_status())?;

self.db = Some(persistent_db);
Expand All @@ -114,13 +158,17 @@ impl DataStore {
db: &sqlite::Connection,
host_status: &HostStatus,
) -> Result<(), TridentError> {
// Create a mutable copy of the Host Status to add Trident version before writing.
let mut host_status_with_trident_version = host_status.clone();
host_status_with_trident_version.trident_version =
TridentVersion::SemVer(TRIDENT_SEMVER_VERSION.clone());
let mut statement = db
.prepare("INSERT INTO hoststatus (contents) VALUES (?)")
.structured(ServicingError::from(DatastoreError::WriteToDatastore))?;
statement
.bind((
1,
&*serde_yaml::to_string(host_status)
&*serde_yaml::to_string(&host_status_with_trident_version)
.structured(InternalError::SerializeHostStatus)?,
))
.structured(ServicingError::from(DatastoreError::WriteToDatastore))?;
Expand Down
1 change: 1 addition & 0 deletions crates/trident/src/engine/ab_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ pub(super) fn stage_update(
install_index: ctx.install_index,
last_error: None,
is_management_os: false,
trident_version: Default::default(),
};
})?;

Expand Down
Loading
Loading