blob: 01037daa56c30da624e6b84f1026fb8ffe2f1388 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
super::*,
crate::{merkle_str, pinned_pkg_url},
fidl_fuchsia_update_installer_ext::{
FetchFailureReason, PrepareFailureReason, Progress, State, UpdateInfo,
UpdateInfoAndProgress,
},
pretty_assertions::assert_eq,
};
#[fasync::run_singlethreaded(test)]
async fn fails_on_package_resolver_connect_error() {
let env = TestEnvBuilder::new().unregister_protocol(Protocol::PackageResolver).build().await;
let result = env.run_update().await;
assert!(result.is_err(), "system updater succeeded when it should fail");
assert_eq!(
env.take_interactions(),
vec![
Paver(PaverEvent::QueryCurrentConfiguration),
Paver(PaverEvent::ReadAsset {
configuration: paver::Configuration::A,
asset: paver::Asset::VerifiedBootMetadata
}),
Paver(PaverEvent::ReadAsset {
configuration: paver::Configuration::A,
asset: paver::Asset::Kernel
}),
Paver(PaverEvent::QueryCurrentConfiguration),
Paver(PaverEvent::QueryConfigurationStatus { configuration: paver::Configuration::A }),
Paver(PaverEvent::SetConfigurationUnbootable {
configuration: paver::Configuration::B
}),
Paver(PaverEvent::BootManagerFlush),
// The connect succeeds, so the system updater only notices the resolver is not there when
// it tries to resolve a package
Gc
]
);
}
#[fasync::run_singlethreaded(test)]
async fn fails_on_update_package_fetch_error() {
let env = TestEnv::builder().build().await;
env.resolver
.register_package("update", "upd4t3")
.add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
.add_file("epoch.json", make_epoch_json(SOURCE_EPOCH));
let system_image_url = SYSTEM_IMAGE_URL;
env.resolver
.mock_resolve_failure(system_image_url, fidl_fuchsia_pkg::ResolveError::PackageNotFound);
let result = env.run_update().await;
assert!(result.is_err(), "system updater succeeded when it should fail");
assert_eq!(
env.get_ota_metrics().await,
OtaMetrics {
initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
as u32,
phase: metrics::OtaResultAttemptsMetricDimensionPhase::PackageDownload as u32,
status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Error as u32,
target: "".into(),
}
);
assert_eq!(
env.take_interactions(),
vec![
Paver(PaverEvent::QueryCurrentConfiguration),
Paver(PaverEvent::ReadAsset {
configuration: paver::Configuration::A,
asset: paver::Asset::VerifiedBootMetadata
}),
Paver(PaverEvent::ReadAsset {
configuration: paver::Configuration::A,
asset: paver::Asset::Kernel
}),
Paver(PaverEvent::QueryCurrentConfiguration),
Paver(PaverEvent::QueryConfigurationStatus { configuration: paver::Configuration::A }),
Paver(PaverEvent::SetConfigurationUnbootable {
configuration: paver::Configuration::B
}),
Paver(PaverEvent::BootManagerFlush),
Gc,
PackageResolve(UPDATE_PKG_URL.to_string()),
Gc,
PackageResolve(system_image_url.to_string()),
]
);
}
#[fasync::run_singlethreaded(test)]
async fn fails_on_content_package_fetch_error() {
let env = TestEnv::builder().build().await;
let pkg1_url = pinned_pkg_url!("package1/0", "aa");
let pkg2_url = pinned_pkg_url!("package2/0", "00");
let pkg3_url = pinned_pkg_url!("package3/0", "bb");
let pkg4_url = pinned_pkg_url!("package4/0", "cc");
let pkg5_url = pinned_pkg_url!("package5/0", "dd");
let pkg6_url = pinned_pkg_url!("package6/0", "ee");
let pkg1 = env.resolver.package("package1", merkle_str!("aa"));
let pkg3 = env.resolver.package("package3", merkle_str!("bb"));
let pkg4 = env.resolver.package("package4", merkle_str!("cc"));
let pkg5 = env.resolver.package("package5", merkle_str!("dd"));
env.resolver.url("fuchsia-pkg://fuchsia.com/update").resolve(
&env.resolver
.package("update", UPDATE_HASH)
.add_file(
"packages.json",
make_packages_json([
SYSTEM_IMAGE_URL,
pkg1_url,
pkg2_url,
pkg3_url,
pkg4_url,
pkg5_url,
pkg6_url,
]),
)
.add_file("epoch.json", make_epoch_json(1)),
);
env.resolver
.url(SYSTEM_IMAGE_URL)
.resolve(&env.resolver.package("system_image", SYSTEM_IMAGE_HASH));
let mut handle_pkg1 = env.resolver.url(pkg1_url).block_once();
let mut handle_pkg2 = env.resolver.url(pkg2_url).block_once();
let mut handle_pkg3 = env.resolver.url(pkg3_url).block_once();
let mut handle_pkg4 = env.resolver.url(pkg4_url).block_once();
let mut handle_pkg5 = env.resolver.url(pkg5_url).block_once();
env.resolver.url(pkg6_url).resolve(&env.resolver.package("package6", merkle_str!("ee")));
let ((), ()) = future::join(
async {
let result = env.run_update().await;
assert!(result.is_err(), "system updater succeeded when it should fail");
},
async move {
// The system updater will try to resolve 5 packages concurrently. Wait for the
// requests to come in.
handle_pkg1.wait().await;
handle_pkg2.wait().await;
handle_pkg3.wait().await;
handle_pkg4.wait().await;
handle_pkg5.wait().await;
// Return a failure for pkg2, and success for the other blocked packages.
handle_pkg2.fail(fidl_fuchsia_pkg::ResolveError::PackageNotFound).await;
handle_pkg1.resolve(&pkg1).await;
handle_pkg3.resolve(&pkg3).await;
handle_pkg4.resolve(&pkg4).await;
handle_pkg5.resolve(&pkg5).await;
},
)
.await;
assert_eq!(
env.get_ota_metrics().await,
OtaMetrics {
initiator: metrics::OtaResultAttemptsMetricDimensionInitiator::UserInitiatedCheck
as u32,
phase: metrics::OtaResultAttemptsMetricDimensionPhase::PackageDownload as u32,
status_code: metrics::OtaResultAttemptsMetricDimensionStatusCode::Error as u32,
target: "".into(),
}
);
assert_eq!(
env.take_interactions(),
vec![
Paver(PaverEvent::QueryCurrentConfiguration),
Paver(PaverEvent::ReadAsset {
configuration: paver::Configuration::A,
asset: paver::Asset::VerifiedBootMetadata
}),
Paver(PaverEvent::ReadAsset {
configuration: paver::Configuration::A,
asset: paver::Asset::Kernel
}),
Paver(PaverEvent::QueryCurrentConfiguration),
Paver(PaverEvent::QueryConfigurationStatus { configuration: paver::Configuration::A }),
Paver(PaverEvent::SetConfigurationUnbootable {
configuration: paver::Configuration::B
}),
Paver(PaverEvent::BootManagerFlush),
Gc,
PackageResolve(UPDATE_PKG_URL.to_string()),
Gc,
PackageResolve(SYSTEM_IMAGE_URL.to_string()),
PackageResolve(pkg1_url.to_string()),
PackageResolve(pkg2_url.to_string()),
PackageResolve(pkg3_url.to_string()),
PackageResolve(pkg4_url.to_string()),
PackageResolve(pkg5_url.to_string()),
// pkg6_url is never resolved, as pkg2's resolve finishes first with an error.
]
);
}
#[fasync::run_singlethreaded(test)]
async fn fails_when_package_cache_sync_fails() {
let env = TestEnv::builder().build().await;
env.cache_service.set_sync_response(Err(Status::INTERNAL));
env.resolver
.register_package("update", "upd4t3")
.add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
.add_file("epoch.json", make_epoch_json(SOURCE_EPOCH));
env.resolver
.url(SYSTEM_IMAGE_URL)
.resolve(&env.resolver.package("system_image/0", SYSTEM_IMAGE_HASH));
let result = env.run_update().await;
assert!(result.is_err(), "system updater succeeded when it should fail");
assert_eq!(
env.take_interactions(),
vec![
Paver(PaverEvent::QueryCurrentConfiguration),
Paver(PaverEvent::ReadAsset {
configuration: paver::Configuration::A,
asset: paver::Asset::VerifiedBootMetadata
}),
Paver(PaverEvent::ReadAsset {
configuration: paver::Configuration::A,
asset: paver::Asset::Kernel
}),
Paver(PaverEvent::QueryCurrentConfiguration),
Paver(PaverEvent::QueryConfigurationStatus { configuration: paver::Configuration::A }),
Paver(PaverEvent::SetConfigurationUnbootable {
configuration: paver::Configuration::B
}),
Paver(PaverEvent::BootManagerFlush),
Gc,
PackageResolve(UPDATE_PKG_URL.to_string()),
Gc,
PackageResolve(SYSTEM_IMAGE_URL.to_string()),
BlobfsSync,
]
);
}
/// Verifies that when we fail to resolve the update package, we get a Prepare failure with the
/// expected `PrepareFailureReason`.
async fn assert_prepare_failure_reason(
resolve_error: fidl_fuchsia_pkg::ResolveError,
expected_reason: PrepareFailureReason,
) {
let env = TestEnv::builder().build().await;
env.resolver.mock_resolve_failure(UPDATE_PKG_URL, resolve_error);
let mut attempt = env.start_update().await.unwrap();
assert_eq!(attempt.next().await.unwrap().unwrap(), State::Prepare);
assert_eq!(attempt.next().await.unwrap().unwrap(), State::FailPrepare(expected_reason));
}
#[fasync::run_singlethreaded(test)]
async fn prepare_failure_reason_out_of_space() {
assert_prepare_failure_reason(
fidl_fuchsia_pkg::ResolveError::NoSpace,
PrepareFailureReason::OutOfSpace,
)
.await;
}
#[fasync::run_singlethreaded(test)]
async fn prepare_failure_reason_internal() {
assert_prepare_failure_reason(
fidl_fuchsia_pkg::ResolveError::AccessDenied,
PrepareFailureReason::Internal,
)
.await;
assert_prepare_failure_reason(
fidl_fuchsia_pkg::ResolveError::RepoNotFound,
PrepareFailureReason::Internal,
)
.await;
assert_prepare_failure_reason(
fidl_fuchsia_pkg::ResolveError::Internal,
PrepareFailureReason::Internal,
)
.await;
assert_prepare_failure_reason(
fidl_fuchsia_pkg::ResolveError::Io,
PrepareFailureReason::Internal,
)
.await;
assert_prepare_failure_reason(
fidl_fuchsia_pkg::ResolveError::PackageNotFound,
PrepareFailureReason::Internal,
)
.await;
assert_prepare_failure_reason(
fidl_fuchsia_pkg::ResolveError::UnavailableBlob,
PrepareFailureReason::Internal,
)
.await;
}
/// Verifies that when we fail to resolve a non-update package, we get a Fetch failure with the
/// expected `FetchFailureReason`.
async fn assert_fetch_failure_reason(
resolve_error: fidl_fuchsia_pkg::ResolveError,
expected_reason: FetchFailureReason,
) {
let env = TestEnv::builder().build().await;
env.resolver
.register_package("update", "upd4t3")
.add_file("packages.json", make_packages_json([SYSTEM_IMAGE_URL]))
.add_file("epoch.json", make_epoch_json(SOURCE_EPOCH));
env.resolver.mock_resolve_failure(SYSTEM_IMAGE_URL, resolve_error);
let mut attempt = env.start_update().await.unwrap();
let info = UpdateInfo::builder().download_size(0).build();
let progress = Progress::builder().fraction_completed(0.0).bytes_downloaded(0).build();
assert_eq!(attempt.next().await.unwrap().unwrap(), State::Prepare);
assert_eq!(
attempt.next().await.unwrap().unwrap(),
State::Fetch(
UpdateInfoAndProgress::builder().info(info.clone()).progress(progress.clone()).build()
)
);
assert_eq!(
attempt.next().await.unwrap().unwrap(),
State::FailFetch(
UpdateInfoAndProgress::builder()
.info(info)
.progress(progress)
.build()
.with_reason(expected_reason)
)
);
}
#[fasync::run_singlethreaded(test)]
async fn fetch_failure_reason_out_of_space() {
assert_fetch_failure_reason(
fidl_fuchsia_pkg::ResolveError::NoSpace,
FetchFailureReason::OutOfSpace,
)
.await;
}
#[fasync::run_singlethreaded(test)]
async fn fetch_failure_reason_internal() {
assert_fetch_failure_reason(
fidl_fuchsia_pkg::ResolveError::AccessDenied,
FetchFailureReason::Internal,
)
.await;
assert_fetch_failure_reason(
fidl_fuchsia_pkg::ResolveError::RepoNotFound,
FetchFailureReason::Internal,
)
.await;
assert_fetch_failure_reason(
fidl_fuchsia_pkg::ResolveError::Internal,
FetchFailureReason::Internal,
)
.await;
assert_fetch_failure_reason(fidl_fuchsia_pkg::ResolveError::Io, FetchFailureReason::Internal)
.await;
assert_fetch_failure_reason(
fidl_fuchsia_pkg::ResolveError::PackageNotFound,
FetchFailureReason::Internal,
)
.await;
assert_fetch_failure_reason(
fidl_fuchsia_pkg::ResolveError::UnavailableBlob,
FetchFailureReason::Internal,
)
.await;
}