blob: 202c64245811e31ddf3fa07d3142917c2bf412ea [file] [log] [blame]
// Copyright 2019 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 {
anyhow::format_err,
assert_matches::assert_matches,
fidl_fuchsia_pkg_ext::RepositoryConfigBuilder,
fidl_fuchsia_pkg_rewrite_ext::{Rule, RuleConfig},
fuchsia_async as fasync,
fuchsia_inspect::{
assert_data_tree,
reader::Property,
testing::{AnyProperty, PropertyAssertion},
tree_assertion,
},
fuchsia_pkg_testing::{serve::responder, PackageBuilder, RepositoryBuilder},
futures::FutureExt as _,
lib::MountsBuilder,
lib::{TestEnvBuilder, EMPTY_REPO_PATH},
std::sync::Arc,
};
#[fasync::run_singlethreaded(test)]
async fn initial_inspect_state() {
let env = TestEnvBuilder::new().build().await;
// Wait for inspect to be created
env.wait_for_pkg_resolver_to_start().await;
// Obtain inspect hierarchy
let hierarchy = env.pkg_resolver_inspect_hierarchy().await;
assert_data_tree!(
hierarchy,
root: {
rewrite_manager: {
dynamic_rules: {},
dynamic_rules_path: format!("{:?}", Some(std::path::Path::new("rewrites.json"))),
static_rules: {},
generation: 0u64,
},
omaha_channel: {
tuf_config_name: OptionDebugStringProperty::<String>::None,
source: OptionDebugStringProperty::<String>::None,
},
experiments: {},
repository_manager: {
dynamic_configs_path: format!("{:?}", Some(std::path::Path::new("repositories.json"))),
dynamic_configs: {},
static_configs: {},
persisted_repos_dir: "None".to_string(),
repos: {},
stats: {
mirrors: {}
},
tuf_metadata_timeout_seconds: 240u64,
},
resolver_service: {
cache_fallbacks_due_to_not_found: 0u64,
active_package_resolves: {}
},
blob_fetcher: {
blob_header_timeout_seconds: 30u64,
blob_body_timeout_seconds: 30u64,
blob_download_resumption_attempts_limit: 50u64,
queue: {},
}
}
);
env.stop().await;
}
#[fasync::run_singlethreaded(test)]
async fn adding_repo_updates_inspect_state() {
let env = TestEnvBuilder::new().build().await;
let config = RepositoryConfigBuilder::new("fuchsia-pkg://example.com".parse().unwrap()).build();
let () = env.proxies.repo_manager.add(config.clone().into()).await.unwrap().unwrap();
// Obtain inspect service and convert into a node hierarchy.
let hierarchy = env.pkg_resolver_inspect_hierarchy().await;
assert_data_tree!(
hierarchy,
root: contains {
repository_manager: contains {
dynamic_configs: {
"example.com": {
root_keys: {},
mirrors: {},
},
},
},
}
);
env.stop().await;
}
#[fasync::run_singlethreaded(test)]
async fn resolving_package_updates_inspect_state() {
let env = TestEnvBuilder::new().build().await;
let pkg = PackageBuilder::new("just_meta_far").build().await.expect("created pkg");
let repo = Arc::new(
RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH)
.add_package(&pkg)
.build()
.await
.unwrap(),
);
let served_repository = repo.server().start().unwrap();
let repo_url = "fuchsia-pkg://example.com".parse().unwrap();
let config = served_repository.make_repo_config(repo_url);
let () = env.proxies.repo_manager.add(config.clone().into()).await.unwrap().unwrap();
env.resolve_package("fuchsia-pkg://example.com/just_meta_far")
.await
.expect("package to resolve");
assert_data_tree!(
env.pkg_resolver_inspect_hierarchy().await,
root: contains {
repository_manager: contains {
dynamic_configs: {
"example.com": {
root_keys: {
"0": format!("{:?}", config.root_keys()[0])
},
mirrors: {
"0": {
mirror_url: format!("{:?}", config.mirrors()[0].mirror_url()),
subscribe: format!("{:?}", config.mirrors()[0].subscribe()),
blob_mirror_url: format!("{:?}", config.mirrors()[0].blob_mirror_url())
}
},
}
},
repos: {
"example.com": {
merkles_successfully_resolved_count: 1u64,
last_merkle_successfully_resolved_time: OptionDebugStringProperty::Some(
AnyProperty
),
"updating_tuf_client": {
update_check_success_count: 1u64,
update_check_failure_count: 0u64,
last_update_successfully_checked_time: OptionDebugStringProperty::Some(
AnyProperty
),
updated_count: 1u64,
root_version: 1u64,
timestamp_version: 2u64,
snapshot_version: 2u64,
targets_version: 2u64,
}
},
},
stats: {
mirrors: {
format!("{}/blobs", served_repository.local_url()) => {
network_blips: 0u64,
network_rate_limits: 0u64,
},
},
},
},
}
);
env.stop().await;
}
#[fasync::run_singlethreaded(test)]
async fn package_and_blob_queues() {
// active_package_resolves exports the original and rewritten pkg urls, so we use a rewrite rule
// and verify that the two exported urls are different.
let env = TestEnvBuilder::new()
.mounts(
MountsBuilder::new()
.dynamic_rewrite_rules(RuleConfig::Version1(vec![Rule::new(
"original.example.com",
"rewritten.example.com",
"/",
"/",
)
.unwrap()]))
.enable_dynamic_config(lib::EnableDynamicConfig {
enable_dynamic_configuration: true,
})
.build(),
)
.build()
.await;
let pkg = PackageBuilder::new("just_meta_far").build().await.expect("created pkg");
let repo = Arc::new(
RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH)
.add_package(&pkg)
.build()
.await
.unwrap(),
);
let meta_far_blob_path = format!("/blobs/{}", pkg.meta_far_merkle_root());
let flake_first_attempt = responder::ForPath::new(
meta_far_blob_path.clone(),
responder::OverrideNth::new(1, responder::StaticResponseCode::server_error()),
);
let (blocking_responder, unblocking_closure_receiver) = responder::BlockResponseBodyOnce::new();
let block_second_attempt = responder::ForPath::new(
meta_far_blob_path.clone(),
responder::OverrideNth::new(2, blocking_responder),
);
let served_repository = repo
.server()
.response_overrider(flake_first_attempt)
.response_overrider(block_second_attempt)
.start()
.unwrap();
let () = env
.proxies
.repo_manager
.add(
served_repository
.make_repo_config("fuchsia-pkg://rewritten.example.com".parse().unwrap())
.into(),
)
.await
.unwrap()
.unwrap();
let resolve_fut =
env.resolve_package("fuchsia-pkg://original.example.com/just_meta_far").boxed_local();
let unblocker = unblocking_closure_receiver.await.unwrap();
let inspect_state_fut = env
.wait_for_pkg_resolver_inspect_state(tree_assertion!(
root: contains {
blob_fetcher: contains {
queue: contains {
pkg.meta_far_merkle_root().to_string() => contains {
attempts: {
"2": contains {
state: "read http body"
}
}
}
}
}
}
))
.boxed_local();
// The resolve should not fail, but if it does because of a bug somewhere, awaiting the
// inspect future would probably hang, and we want the test to fail instead of hang so that
// the test stdout is printed.
let resolve_fut = match futures::future::select(resolve_fut, inspect_state_fut).await {
futures::future::Either::Left((resolve_res, _)) => {
panic!("the resolve should not have completed: {:?}", resolve_res);
}
futures::future::Either::Right(((), resolve_fut)) => resolve_fut,
};
assert_data_tree!(
env.pkg_resolver_inspect_hierarchy().await,
root: contains {
blob_fetcher: contains {
queue: {
pkg.meta_far_merkle_root().to_string() => {
fetch_ts: AnyProperty,
source: "http",
mirror: format!("{}{}", served_repository.local_url(), meta_far_blob_path),
attempts: {
"2": {
state: "read http body",
state_ts: AnyProperty,
expected_size_bytes: 12288u64,
bytes_written: 0u64,
}
}
}
}
},
resolver_service: contains {
active_package_resolves: {
"fuchsia-pkg://original.example.com/just_meta_far": {
resolve_ts: AnyProperty,
rewritten_url: "fuchsia-pkg://rewritten.example.com/just_meta_far",
}
}
}
}
);
// After completing the resolve there should be no active blob fetches.
unblocker();
let _pkg = resolve_fut.await.unwrap();
assert_data_tree!(
env.pkg_resolver_inspect_hierarchy().await,
root: contains {
blob_fetcher: contains {
queue: {}
}
}
);
env.stop().await;
}
#[fasync::run_singlethreaded(test)]
async fn channel_in_vbmeta_appears_in_inspect_state() {
let repo =
Arc::new(RepositoryBuilder::from_template_dir(EMPTY_REPO_PATH).build().await.unwrap());
let served_repository = repo.server().start().unwrap();
let repo_url = "fuchsia-pkg://test-repo".parse().unwrap();
let config = served_repository.make_repo_config(repo_url);
let env = TestEnvBuilder::new()
.tuf_repo_config_boot_arg("test-repo".to_string())
.mounts(lib::MountsBuilder::new().static_repository(config.clone()).build())
.build()
.await;
env.wait_for_pkg_resolver_to_start().await;
let hierarchy = env.pkg_resolver_inspect_hierarchy().await;
assert_data_tree!(
hierarchy,
root: contains {
rewrite_manager: {
dynamic_rules: {
"0": {
path_prefix_replacement: "/",
host_match: "fuchsia.com",
path_prefix_match: "/",
host_replacement: "test-repo",
}
},
dynamic_rules_path: "None",
static_rules: {},
generation: 0u64,
},
omaha_channel: {
tuf_config_name: "test-repo",
source: "Some(VbMeta)"
},
}
);
env.stop().await;
}
enum OptionDebugStringProperty<I> {
Some(I),
None,
}
impl<I> PropertyAssertion for OptionDebugStringProperty<I>
where
I: PropertyAssertion,
{
fn run(&self, actual: &Property) -> Result<(), anyhow::Error> {
match actual {
Property::String(name, value) => match self {
OptionDebugStringProperty::Some(inner) => {
const PREFIX: &str = "Some(";
const SUFFIX: &str = ")";
if !value.starts_with(PREFIX) {
return Err(format_err!(
r#"expected property to be "Some(...", actual {:?}"#,
actual
));
}
if !value.ends_with(SUFFIX) {
return Err(format_err!(
r#"expected property to be "...)", actual {:?}"#,
actual
));
}
let inner_value = &value[PREFIX.len()..(value.len() - SUFFIX.len())];
inner.run(&Property::String(name.clone(), inner_value.to_owned()))
}
OptionDebugStringProperty::None => {
if value != "None" {
return Err(format_err!(
r#"expected property string to be "None", got {:?}"#,
actual
));
}
Ok(())
}
},
_wrong_type => Err(format_err!("expected string property, got {:?}", actual)),
}
}
}
#[test]
fn option_debug_string_property() {
fn make_string_property(value: &str) -> Property {
Property::String("name".to_owned(), value.to_owned())
}
fn dbg<D: std::fmt::Debug>(d: D) -> String {
format!("{:?}", d)
}
// trivial ok
assert_matches!(
OptionDebugStringProperty::Some(AnyProperty).run(&make_string_property("Some()")),
Ok(())
);
assert_matches!(
OptionDebugStringProperty::<AnyProperty>::None.run(&make_string_property("None")),
Ok(())
);
// trivial err
assert_matches!(
OptionDebugStringProperty::Some(AnyProperty).run(&make_string_property("None")),
Err(_)
);
assert_matches!(
OptionDebugStringProperty::Some(AnyProperty).run(&make_string_property("Some(foo")),
Err(_)
);
assert_matches!(
OptionDebugStringProperty::<AnyProperty>::None.run(&make_string_property("Some()")),
Err(_)
);
// non-empty inner ok
assert_matches!(
OptionDebugStringProperty::Some(AnyProperty).run(&make_string_property("Some(value)")),
Ok(())
);
assert_matches!(
OptionDebugStringProperty::Some(dbg("string"))
.run(&make_string_property("Some(\"string\")")),
Ok(())
);
// non-empty inner err
assert_matches!(
OptionDebugStringProperty::Some(dbg("a")).run(&make_string_property("Some(\"b\")")),
Err(_)
);
}