blob: e91b1eff2b562f2c86a1ac661563018f801a89b5 [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 {
crate::model::{
component::StartReason,
error::{ActionError, CreateNamespaceError, ModelError, StartActionError},
routing::route_and_open_capability,
testing::routing_test_helpers::*,
},
::routing_test_helpers::{
component_id_index::make_index_file, storage::CommonStorageTest, RoutingTestModel,
},
assert_matches::assert_matches,
bedrock_error::{BedrockError, DowncastErrorForTest},
cm_moniker::InstancedMoniker,
cm_rust::*,
cm_rust_testing::*,
component_id_index::InstanceId,
fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio, fidl_fuchsia_sys2 as fsys,
fuchsia_zircon as zx,
moniker::{Moniker, MonikerBase},
routing::{error::RoutingError, RouteRequest},
std::path::Path,
vfs::{
directory::entry::OpenRequest, execution_scope::ExecutionScope, path::Path as VfsPath,
ToObjectRequest,
},
};
#[fuchsia::test]
async fn storage_dir_from_cm_namespace() {
CommonStorageTest::<RoutingTestBuilder>::new().test_storage_dir_from_cm_namespace().await
}
#[fuchsia::test]
async fn storage_and_dir_from_parent() {
CommonStorageTest::<RoutingTestBuilder>::new().test_storage_and_dir_from_parent().await
}
#[fuchsia::test]
async fn storage_and_dir_from_parent_with_subdir() {
CommonStorageTest::<RoutingTestBuilder>::new()
.test_storage_and_dir_from_parent_with_subdir()
.await
}
#[fuchsia::test]
async fn storage_and_dir_from_parent_rights_invalid() {
CommonStorageTest::<RoutingTestBuilder>::new()
.test_storage_and_dir_from_parent_rights_invalid()
.await
}
#[fuchsia::test]
async fn storage_from_parent_dir_from_grandparent() {
CommonStorageTest::<RoutingTestBuilder>::new()
.test_storage_from_parent_dir_from_grandparent()
.await
}
#[fuchsia::test]
async fn storage_from_parent_dir_from_grandparent_with_subdirs() {
CommonStorageTest::<RoutingTestBuilder>::new()
.test_storage_from_parent_dir_from_grandparent_with_subdirs()
.await
}
#[fuchsia::test]
async fn storage_from_parent_dir_from_grandparent_with_subdir() {
CommonStorageTest::<RoutingTestBuilder>::new()
.test_storage_from_parent_dir_from_grandparent_with_subdir()
.await
}
#[fuchsia::test]
async fn storage_and_dir_from_grandparent() {
CommonStorageTest::<RoutingTestBuilder>::new().test_storage_and_dir_from_grandparent().await
}
#[fuchsia::test]
async fn storage_from_parent_dir_from_sibling() {
CommonStorageTest::<RoutingTestBuilder>::new().test_storage_from_parent_dir_from_sibling().await
}
#[fuchsia::test]
async fn storage_from_parent_dir_from_sibling_with_subdir() {
CommonStorageTest::<RoutingTestBuilder>::new()
.test_storage_from_parent_dir_from_sibling_with_subdir()
.await
}
/// a
/// \
/// b
/// \
/// [c]
///
/// a: offers directory to b at path /minfs
/// b: has storage decl with name "mystorage" with a source of realm at path /data
/// b: offers storage to collection from "mystorage"
/// [c]: uses storage as /storage
/// [c]: destroyed and storage goes away
#[fuchsia::test]
async fn use_in_collection_from_parent() {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("data")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.offer(
OfferBuilder::directory()
.name("data")
.target_name("minfs")
.source(OfferSource::Self_)
.target_static_child("b")
.rights(fio::RW_STAR_DIR),
)
.child_default("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Self_)
.target(OfferTarget::Collection("coll".parse().unwrap())),
)
.offer(
OfferBuilder::storage()
.name("cache")
.source(OfferSource::Self_)
.target(OfferTarget::Collection("coll".parse().unwrap())),
)
.capability(
CapabilityBuilder::storage()
.name("data")
.backing_dir("minfs")
.source(StorageDirectorySource::Parent)
.subdir("data"),
)
.capability(
CapabilityBuilder::storage()
.name("cache")
.backing_dir("minfs")
.source(StorageDirectorySource::Parent)
.subdir("cache"),
)
.collection_default("coll")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("data").path("/data"))
.use_(UseBuilder::storage().name("cache").path("/cache"))
.build(),
),
];
let test = RoutingTest::new("a", components).await;
test.create_dynamic_child(
&vec!["b"].try_into().unwrap(),
"coll",
ChildDecl {
name: "c".parse().unwrap(),
url: "test:///c".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// Use storage and confirm its existence.
test.check_use(
vec!["b", "coll:c"].try_into().unwrap(),
CheckUse::Storage {
path: "/data".parse().unwrap(),
storage_relation: Some(InstancedMoniker::try_from(vec!["coll:c:1"]).unwrap()),
from_cm_namespace: false,
storage_subdir: Some("data".to_string()),
expected_res: ExpectedResult::Ok,
},
)
.await;
test.check_use(
vec!["b", "coll:c"].try_into().unwrap(),
CheckUse::Storage {
path: "/cache".parse().unwrap(),
storage_relation: Some(InstancedMoniker::try_from(vec!["coll:c:1"]).unwrap()),
from_cm_namespace: false,
storage_subdir: Some("cache".to_string()),
expected_res: ExpectedResult::Ok,
},
)
.await;
// Confirm storage directory exists for component in collection
assert_eq!(
test.list_directory_in_storage(Some("data"), InstancedMoniker::new(vec![]), None, "").await,
vec!["coll:c:1".to_string()],
);
assert_eq!(
test.list_directory_in_storage(Some("cache"), InstancedMoniker::new(vec![]), None, "")
.await,
vec!["coll:c:1".to_string()],
);
test.destroy_dynamic_child(vec!["b"].try_into().unwrap(), "coll", "c").await;
// Confirm storage no longer exists.
assert_eq!(
test.list_directory_in_storage(Some("data"), InstancedMoniker::new(vec![]), None, "").await,
Vec::<String>::new(),
);
assert_eq!(
test.list_directory_in_storage(Some("cache"), InstancedMoniker::new(vec![]), None, "")
.await,
Vec::<String>::new(),
);
}
/// a
/// \
/// b
/// \
/// [c]
///
/// a: has storage decl with name "mystorage" with a source of self at path /data
/// a: offers storage to b from "mystorage"
/// b: offers storage to collection from "mystorage"
/// [c]: uses storage as /storage
/// [c]: destroyed and storage goes away
#[fuchsia::test]
async fn use_in_collection_from_grandparent() {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("minfs")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Self_)
.target_static_child("b"),
)
.offer(
OfferBuilder::storage()
.name("cache")
.source(OfferSource::Self_)
.target_static_child("b"),
)
.child_default("b")
.capability(
CapabilityBuilder::storage()
.name("data")
.backing_dir("minfs")
.source(StorageDirectorySource::Self_)
.subdir("data"),
)
.capability(
CapabilityBuilder::storage()
.name("cache")
.backing_dir("minfs")
.source(StorageDirectorySource::Self_)
.subdir("cache"),
)
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target(OfferTarget::Collection("coll".parse().unwrap())),
)
.offer(
OfferBuilder::storage()
.name("cache")
.source(OfferSource::Parent)
.target(OfferTarget::Collection("coll".parse().unwrap())),
)
.collection_default("coll")
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("data").path("/data"))
.use_(UseBuilder::storage().name("cache").path("/cache"))
.build(),
),
];
let test = RoutingTest::new("a", components).await;
test.create_dynamic_child(
&vec!["b"].try_into().unwrap(),
"coll",
ChildDecl {
name: "c".parse().unwrap(),
url: "test:///c".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// Use storage and confirm its existence.
test.check_use(
vec!["b", "coll:c"].try_into().unwrap(),
CheckUse::Storage {
path: "/data".parse().unwrap(),
storage_relation: Some(InstancedMoniker::try_from(vec!["b:0", "coll:c:1"]).unwrap()),
from_cm_namespace: false,
storage_subdir: Some("data".to_string()),
expected_res: ExpectedResult::Ok,
},
)
.await;
test.check_use(
vec!["b", "coll:c"].try_into().unwrap(),
CheckUse::Storage {
path: "/cache".parse().unwrap(),
storage_relation: Some(InstancedMoniker::try_from(vec!["b:0", "coll:c:1"]).unwrap()),
from_cm_namespace: false,
storage_subdir: Some("cache".to_string()),
expected_res: ExpectedResult::Ok,
},
)
.await;
assert_eq!(
test.list_directory_in_storage(
Some("data"),
InstancedMoniker::try_from(vec!["b:0"]).unwrap(),
None,
"children",
)
.await,
vec!["coll:c:1".to_string()]
);
assert_eq!(
test.list_directory_in_storage(
Some("cache"),
InstancedMoniker::try_from(vec!["b:0"]).unwrap(),
None,
"children",
)
.await,
vec!["coll:c:1".to_string()]
);
test.destroy_dynamic_child(vec!["b"].try_into().unwrap(), "coll", "c").await;
// Confirm storage no longer exists.
assert_eq!(
test.list_directory_in_storage(
Some("data"),
InstancedMoniker::try_from(vec!["b:0"]).unwrap(),
None,
"children"
)
.await,
Vec::<String>::new(),
);
assert_eq!(
test.list_directory_in_storage(
Some("cache"),
InstancedMoniker::try_from(vec!["b:0"]).unwrap(),
None,
"children"
)
.await,
Vec::<String>::new(),
);
}
#[fuchsia::test]
async fn storage_multiple_types() {
CommonStorageTest::<RoutingTestBuilder>::new().test_storage_multiple_types().await
}
#[fuchsia::test]
async fn use_the_wrong_type_of_storage() {
CommonStorageTest::<RoutingTestBuilder>::new().test_use_the_wrong_type_of_storage().await
}
#[fuchsia::test]
async fn directories_are_not_storage() {
CommonStorageTest::<RoutingTestBuilder>::new().test_directories_are_not_storage().await
}
#[fuchsia::test]
async fn use_storage_when_not_offered() {
CommonStorageTest::<RoutingTestBuilder>::new().test_use_storage_when_not_offered().await
}
#[fuchsia::test]
async fn dir_offered_from_nonexecutable() {
CommonStorageTest::<RoutingTestBuilder>::new().test_dir_offered_from_nonexecutable().await
}
#[fuchsia::test]
async fn storage_dir_from_cm_namespace_prevented_by_policy() {
CommonStorageTest::<RoutingTestBuilder>::new()
.test_storage_dir_from_cm_namespace_prevented_by_policy()
.await
}
#[fuchsia::test]
async fn instance_id_from_index() {
CommonStorageTest::<RoutingTestBuilder>::new().test_instance_id_from_index().await
}
/// component manager's namespace
/// |
/// provider (provides storage capability, restricted to component ID index)
/// |
/// parent_consumer (in component ID index)
/// |
/// child_consumer (not in component ID index)
///
/// Test that a component cannot start if it uses restricted storage but isn't in the component ID
/// index.
#[fuchsia::test]
async fn use_restricted_storage_start_failure() {
let parent_consumer_instance_id = InstanceId::new_random(&mut rand::thread_rng());
let index = {
let mut index = component_id_index::Index::default();
index
.insert(
Moniker::parse_str("/parent_consumer").unwrap(),
parent_consumer_instance_id.clone(),
)
.unwrap();
index
};
let component_id_index_path = make_index_file(index).unwrap();
let components = vec![
(
"provider",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("data")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.capability(
CapabilityBuilder::storage()
.name("cache")
.backing_dir("data")
.source(StorageDirectorySource::Self_)
.storage_id(fdecl::StorageId::StaticInstanceId),
)
.offer(
OfferBuilder::storage()
.name("cache")
.source(OfferSource::Self_)
.target_static_child("parent_consumer"),
)
.child_default("parent_consumer")
.build(),
),
(
"parent_consumer",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("cache").path("/storage"))
.offer(
OfferBuilder::storage()
.name("cache")
.source(OfferSource::Parent)
.target_static_child("child_consumer"),
)
.child_default("child_consumer")
.build(),
),
(
"child_consumer",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("cache").path("/storage"))
.build(),
),
];
let test = RoutingTestBuilder::new("provider", components)
.set_component_id_index_path(component_id_index_path.path().to_owned().try_into().unwrap())
.build()
.await;
test.start_instance(&Moniker::parse_str("/parent_consumer").unwrap())
.await
.expect("start /parent_consumer failed");
let child_bind_result =
test.start_instance(&Moniker::parse_str("/parent_consumer/child_consumer").unwrap()).await;
assert_matches!(
child_bind_result,
Err(ModelError::ActionError {
err: ActionError::StartError {
err: StartActionError::CreateNamespaceError {
moniker,
err: CreateNamespaceError::InstanceNotInInstanceIdIndex(_),
}
}
}) if moniker == Moniker::try_from(vec!["parent_consumer", "child_consumer"]).unwrap()
);
}
/// component manager's namespace
/// |
/// provider (provides storage capability, restricted to component ID index)
/// |
/// parent_consumer (in component ID index)
/// |
/// child_consumer (not in component ID index)
///
/// Test that a component cannot open a restricted storage capability if the component isn't in
/// the component iindex.
#[fuchsia::test]
async fn use_restricted_storage_open_failure() {
let parent_consumer_instance_id = InstanceId::new_random(&mut rand::thread_rng());
let index = {
let mut index = component_id_index::Index::default();
index
.insert(
Moniker::parse_str("/parent_consumer/child_consumer").unwrap(),
parent_consumer_instance_id.clone(),
)
.unwrap();
index
};
let component_id_index_path = make_index_file(index).unwrap();
let components = vec![
(
"provider",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("data")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.capability(
CapabilityBuilder::storage()
.name("cache")
.backing_dir("data")
.source(StorageDirectorySource::Self_),
)
.offer(
OfferBuilder::storage()
.name("cache")
.source(OfferSource::Self_)
.target_static_child("parent_consumer"),
)
.child_default("parent_consumer")
.build(),
),
(
"parent_consumer",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("cache").path("/storage"))
.build(),
),
];
let test = RoutingTestBuilder::new("provider", components)
.set_component_id_index_path(component_id_index_path.path().to_owned().try_into().unwrap())
.build()
.await;
let parent_consumer_moniker = Moniker::parse_str("/parent_consumer").unwrap();
let (parent_consumer_instance, _) = test
.start_and_get_instance(&parent_consumer_moniker, StartReason::Eager, false)
.await
.expect("could not resolve state");
// `parent_consumer` should be able to open its storage because its not restricted
let (_client_end, server_end) = zx::Channel::create();
let scope = ExecutionScope::new();
let flags =
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::DIRECTORY;
let mut object_request = flags.to_object_request(server_end);
route_and_open_capability(
&RouteRequest::UseStorage(UseStorageDecl {
source_name: "cache".parse().unwrap(),
target_path: "/storage".parse().unwrap(),
availability: cm_rust::Availability::Required,
}),
&parent_consumer_instance,
OpenRequest::new(scope.clone(), flags, VfsPath::dot(), &mut object_request),
)
.await
.expect("Unable to route. oh no!!");
// now modify StorageDecl so that it restricts storage
let (provider_instance, _) = test
.start_and_get_instance(&Moniker::root(), StartReason::Eager, false)
.await
.expect("could not resolve state");
{
let mut resolved_state = provider_instance.lock_resolved_state().await.unwrap();
for cap in resolved_state.decl_as_mut().capabilities.iter_mut() {
match cap {
CapabilityDecl::Storage(storage_decl) => {
storage_decl.storage_id = fdecl::StorageId::StaticInstanceId;
}
_ => {}
}
}
}
// `parent_consumer` should NOT be able to open its storage because its IS restricted
let (_client_end, server_end) = zx::Channel::create();
let scope = ExecutionScope::new();
let flags =
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::DIRECTORY;
let mut object_request = flags.to_object_request(server_end);
let result = route_and_open_capability(
&RouteRequest::UseStorage(UseStorageDecl {
source_name: "cache".parse().unwrap(),
target_path: "/storage".parse().unwrap(),
availability: cm_rust::Availability::Required,
}),
&parent_consumer_instance,
OpenRequest::new(scope.clone(), flags, VfsPath::dot(), &mut object_request),
)
.await;
assert_matches!(
result,
Err(BedrockError::RoutingError(err))
if matches!(
err.downcast_for_test::<RoutingError>(),
RoutingError::ComponentNotInIdIndex { .. }
)
);
}
/// component manager's namespace
/// |
/// provider (provides storage capability, restricted to component ID index)
/// |
/// parent_consumer (in component ID index)
///
/// Test that a component can open a subdirectory of a storage successfully
#[fuchsia::test]
async fn open_storage_subdirectory() {
let parent_consumer_instance_id = InstanceId::new_random(&mut rand::thread_rng());
let index = {
let mut index = component_id_index::Index::default();
index
.insert(
Moniker::parse_str("/child_consumer").unwrap(),
parent_consumer_instance_id.clone(),
)
.unwrap();
index
};
let component_id_index_path = make_index_file(index).unwrap();
let components = vec![
(
"provider",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("data")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.capability(
CapabilityBuilder::storage()
.name("cache")
.backing_dir("data")
.source(StorageDirectorySource::Self_),
)
.offer(
OfferBuilder::storage()
.name("cache")
.source(OfferSource::Self_)
.target_static_child("consumer"),
)
.child_default("consumer")
.build(),
),
(
"consumer",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("cache").path("/storage"))
.build(),
),
];
let test = RoutingTestBuilder::new("provider", components)
.set_component_id_index_path(component_id_index_path.path().to_owned().try_into().unwrap())
.build()
.await;
let consumer_moniker = Moniker::parse_str("/consumer").unwrap();
let (consumer_instance, _) = test
.start_and_get_instance(&consumer_moniker, StartReason::Eager, false)
.await
.expect("could not resolve state");
// `consumer` should be able to open its storage at the root dir
let (root_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
let server_end = server_end.into_channel();
let scope = ExecutionScope::new();
let flags =
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::DIRECTORY;
let mut object_request = flags.to_object_request(server_end);
route_and_open_capability(
&RouteRequest::UseStorage(UseStorageDecl {
source_name: "cache".parse().unwrap(),
target_path: "/storage".parse().unwrap(),
availability: cm_rust::Availability::Required,
}),
&consumer_instance,
OpenRequest::new(scope.clone(), flags, VfsPath::dot(), &mut object_request),
)
.await
.expect("Unable to route. oh no!!");
// Create the subdirectories we will open later
let bar_dir = fuchsia_fs::directory::create_directory_recursive(
&root_dir,
"foo/bar",
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
)
.await
.unwrap();
let entries = fuchsia_fs::directory::readdir(&bar_dir).await.unwrap();
assert!(entries.is_empty());
// `consumer` should be able to open its storage at "foo/bar"
let (bar_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
let scope = ExecutionScope::new();
let flags =
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE | fio::OpenFlags::DIRECTORY;
let mut object_request = flags.to_object_request(server_end);
route_and_open_capability(
&RouteRequest::UseStorage(UseStorageDecl {
source_name: "cache".parse().unwrap(),
target_path: "/storage".parse().unwrap(),
availability: cm_rust::Availability::Required,
}),
&consumer_instance,
OpenRequest::new(scope.clone(), flags, "foo/bar".try_into().unwrap(), &mut object_request),
)
.await
.expect("Unable to route. oh no!!");
let entries = fuchsia_fs::directory::readdir(&bar_dir).await.unwrap();
assert!(entries.is_empty());
}
/// a
/// |
/// b
/// |
/// coll-persistent_storage: "true"
/// |
/// [c:1]
///
/// Test that storage data persists after destroy for a collection with a moniker-based storage
/// path. The persistent storage data can be deleted through a
/// StorageAdminRequest::DeleteComponentStorage request.
/// The following storage paths are used:
/// - moniker path with instance ids cleared
#[fuchsia::test]
async fn storage_persistence_moniker_path() {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("minfs")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.capability(
CapabilityBuilder::storage()
.name("data")
.backing_dir("minfs")
.source(StorageDirectorySource::Self_),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Self_)
.target_static_child("b"),
)
.offer(
OfferBuilder::protocol()
.name("fuchsia.sys2.StorageAdmin")
.source(OfferSource::Capability("data".parse().unwrap()))
.target_static_child("b"),
)
.child_default("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.use_(UseBuilder::protocol().name("fuchsia.sys2.StorageAdmin"))
.use_(UseBuilder::storage().name("data").path("/data"))
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target(OfferTarget::Collection("persistent_coll".parse().unwrap())),
)
.collection(
CollectionBuilder::new().name("persistent_coll").persistent_storage(true),
)
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("data").path("/data"))
.build(),
),
];
let test = RoutingTestBuilder::new("a", components).build().await;
// create [c:1] under the storage persistent collection
test.create_dynamic_child(
&vec!["b"].try_into().unwrap(),
"persistent_coll",
ChildDecl {
name: "c".parse().unwrap(),
url: "test:///c".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// write to [c:1] storage
test.create_static_file(&Path::new("b:0/children/persistent_coll:c:0/data/c1"), "hippos")
.await
.unwrap();
// destroy [c:1]
test.destroy_dynamic_child(vec!["b"].try_into().unwrap(), "persistent_coll", "c").await;
// expect the [c:1] storage and data to persist
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:0/data",
vec!["c1".to_string()],
)
.await;
// recreate dynamic child [c:2]
test.create_dynamic_child(
&vec!["b"].try_into().unwrap(),
"persistent_coll",
ChildDecl {
name: "c".parse().unwrap(),
url: "test:///c".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// write to [c:2] storage
test.create_static_file(&Path::new("b:0/children/persistent_coll:c:0/data/c2"), "sharks")
.await
.unwrap();
// destroy [c:2]
test.destroy_dynamic_child(vec!["b"].try_into().unwrap(), "persistent_coll", "c").await;
// expect the [c:1] and [c:2] storage and data to persist
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:0/data",
vec!["c1".to_string(), "c2".to_string()],
)
.await;
// check that the file can be destroyed by storage admin
let namespace = test.bind_and_get_namespace(vec!["b"].try_into().unwrap()).await;
let storage_admin_proxy = capability_util::connect_to_svc_in_namespace::<
fsys::StorageAdminMarker,
>(&namespace, &"/svc/fuchsia.sys2.StorageAdmin".parse().unwrap())
.await;
let _ = storage_admin_proxy
// StorageAdmin::DeleteComponentStorage tolerates both regular old monikers and instanced
// monikers ("b:0/persistent_col:c:0"). Use the regular moniker here since the IDs would be
// ignored anyway.
.delete_component_storage("b/persistent_coll:c")
.await
.unwrap()
.unwrap();
// expect persistent_coll storage to be destroyed
capability_util::confirm_storage_is_deleted_for_component(
None,
true,
InstancedMoniker::try_from(vec!["b:0", "persistent_coll:c:0"]).unwrap(),
None,
&test.test_dir_proxy,
)
.await;
}
/// a
/// |
/// b
/// |
/// coll-persistent_storage: "true" / instance_id
/// |
/// [c:1]
///
/// Test that storage data persists after destroy for a collection with an instance-id-based
/// storage path. The persistent storage data can be deleted through a
/// StorageAdminRequest::DeleteComponentStorage request.
/// The following storage paths are used:
/// - indexed path
#[fuchsia::test]
async fn storage_persistence_instance_id_path() {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("minfs")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.capability(
CapabilityBuilder::storage()
.name("data")
.backing_dir("minfs")
.source(StorageDirectorySource::Self_),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Self_)
.target_static_child("b"),
)
.offer(
OfferBuilder::protocol()
.name("fuchsia.sys2.StorageAdmin")
.source(OfferSource::Capability("data".parse().unwrap()))
.target_static_child("b"),
)
.child_default("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.use_(UseBuilder::protocol().name("fuchsia.sys2.StorageAdmin"))
.use_(UseBuilder::storage().name("data").path("/data"))
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target(OfferTarget::Collection("persistent_coll".parse().unwrap())),
)
.collection(
CollectionBuilder::new().name("persistent_coll").persistent_storage(true),
)
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("data").path("/data"))
.build(),
),
];
// set instance_id for "b/persistent_coll:c" components
let instance_id = InstanceId::new_random(&mut rand::thread_rng());
let index = {
let mut index = component_id_index::Index::default();
index
.insert(vec!["b", "persistent_coll:c"].try_into().unwrap(), instance_id.clone())
.unwrap();
index
};
let component_id_index_path = make_index_file(index).unwrap();
let test = RoutingTestBuilder::new("a", components)
.set_component_id_index_path(component_id_index_path.path().to_owned().try_into().unwrap())
.build()
.await;
// create [c:1] under the storage persistent collection
test.create_dynamic_child(
&vec!["b"].try_into().unwrap(),
"persistent_coll",
ChildDecl {
name: "c".parse().unwrap(),
url: "test:///c".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// write to [c:1] storage
test.create_static_file(&Path::new(&format!("{}/c1", instance_id)), "hippos").await.unwrap();
// destroy [c:1]
test.destroy_dynamic_child(vec!["b"].try_into().unwrap(), "persistent_coll", "c").await;
// expect the [c:1] storage and data to persist
test.check_test_subdir_contents(&instance_id.to_string(), vec!["c1".to_string()]).await;
// recreate dynamic child [c:2]
test.create_dynamic_child(
&vec!["b"].try_into().unwrap(),
"persistent_coll",
ChildDecl {
name: "c".parse().unwrap(),
url: "test:///c".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// write to [c:2] storage
test.create_static_file(&Path::new(&format!("{}/c2", instance_id)), "sharks").await.unwrap();
// destroy [c:2]
test.destroy_dynamic_child(vec!["b"].try_into().unwrap(), "persistent_coll", "c").await;
// expect the [c:1] and [c:2] storage and data to persist
test.check_test_subdir_contents(
&instance_id.to_string(),
vec!["c1".to_string(), "c2".to_string()],
)
.await;
// destroy the persistent storage with a storage admin request
let namespace = test.bind_and_get_namespace(vec!["b"].try_into().unwrap()).await;
let storage_admin_proxy = capability_util::connect_to_svc_in_namespace::<
fsys::StorageAdminMarker,
>(&namespace, &"/svc/fuchsia.sys2.StorageAdmin".parse().unwrap())
.await;
let _ =
storage_admin_proxy.delete_component_storage("./b:0/persistent_coll:c:0").await.unwrap();
// expect persistent_coll storage to be destroyed
capability_util::confirm_storage_is_deleted_for_component(
None,
true,
InstancedMoniker::try_from(vec!["b:0", "persistent_coll:c:0"]).unwrap(),
Some(&instance_id),
&test.test_dir_proxy,
)
.await;
}
/// a
/// |
/// b
/// |
/// coll-persistent_storage: "true" / instance_id
/// |
/// [c:1]
/// / \
/// d [coll]
/// |
/// [e:1]
///
/// Test that storage persistence behavior is inherited by descendents with a different storage
/// path.
/// The following storage paths are used:
/// - indexed path
/// - moniker path with instance ids cleared
#[fuchsia::test]
async fn storage_persistence_inheritance() {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("minfs")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.capability(
CapabilityBuilder::storage()
.name("data")
.backing_dir("minfs")
.source(StorageDirectorySource::Self_),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Self_)
.target_static_child("b"),
)
.child_default("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.use_(UseBuilder::storage().name("data").path("/data"))
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target(OfferTarget::Collection("persistent_coll".parse().unwrap())),
)
.collection(
CollectionBuilder::new().name("persistent_coll").persistent_storage(true),
)
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.use_(UseBuilder::storage().name("data").path("/data"))
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target_static_child("d"),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target(OfferTarget::Collection("lower_coll".parse().unwrap())),
)
.child_default("d")
.collection_default("lower_coll")
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("data").path("/data"))
.build(),
),
(
"e",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("data").path("/data"))
.build(),
),
];
// set instance_id for "b/persistent_coll:c" components
let instance_id = InstanceId::new_random(&mut rand::thread_rng());
let index = {
let mut index = component_id_index::Index::default();
index
.insert(vec!["b", "persistent_coll:c"].try_into().unwrap(), instance_id.clone())
.unwrap();
index
};
let component_id_index_path = make_index_file(index).unwrap();
let test = RoutingTestBuilder::new("a", components)
.set_component_id_index_path(component_id_index_path.path().to_owned().try_into().unwrap())
.build()
.await;
// create [c:1] under the storage persistent collection
test.create_dynamic_child(
&vec!["b"].try_into().unwrap(),
"persistent_coll",
ChildDecl {
name: "c".parse().unwrap(),
url: "test:///c".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// write to [c:1] storage
test.check_use(
vec!["b", "persistent_coll:c"].try_into().unwrap(),
CheckUse::Storage {
path: "/data".parse().unwrap(),
storage_relation: Some(
InstancedMoniker::try_from(vec!["b:0", "persistent_coll:c:1"]).unwrap(),
),
from_cm_namespace: false,
storage_subdir: None,
expected_res: ExpectedResult::Ok,
},
)
.await;
// start d:0 and write to storage
test.check_use(
vec!["b", "persistent_coll:c", "d"].try_into().unwrap(),
CheckUse::Storage {
path: "/data".parse().unwrap(),
storage_relation: Some(
InstancedMoniker::try_from(vec!["b:0", "persistent_coll:c:1", "d:0"]).unwrap(),
),
from_cm_namespace: false,
storage_subdir: None,
expected_res: ExpectedResult::Ok,
},
)
.await;
// create [e:1] under the lower collection
test.create_dynamic_child(
&vec!["b", "persistent_coll:c"].try_into().unwrap(),
"lower_coll",
ChildDecl {
name: "e".parse().unwrap(),
url: "test:///e".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// write to [e:1] storage
test.check_use(
vec!["b", "persistent_coll:c", "lower_coll:e"].try_into().unwrap(),
CheckUse::Storage {
path: "/data".parse().unwrap(),
storage_relation: Some(
InstancedMoniker::try_from(vec!["b:0", "persistent_coll:c:1", "lower_coll:e:1"])
.unwrap(),
),
from_cm_namespace: false,
storage_subdir: None,
expected_res: ExpectedResult::Ok,
},
)
.await;
// test that [c:1] wrote to instance id path
test.check_test_subdir_contents(&instance_id.to_string(), vec!["hippos".to_string()]).await;
// test that d:0 wrote to moniker based path with instance ids cleared
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:0/children/d:0/data",
vec!["hippos".to_string()],
)
.await;
// test that [e:1] wrote to moniker based path with instance ids cleared
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:0/children/lower_coll:e:0/data",
vec!["hippos".to_string()],
)
.await;
// destroy [c:1], which will also shutdown d:0 and lower_coll:e:1
test.destroy_dynamic_child(vec!["b"].try_into().unwrap(), "persistent_coll", "c").await;
// expect [c:1], d:0, and [e:1] storage and data to persist
test.check_test_subdir_contents(&instance_id.to_string(), vec!["hippos".to_string()]).await;
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:0/children/d:0/data",
vec!["hippos".to_string()],
)
.await;
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:0/children/lower_coll:e:0/data",
vec!["hippos".to_string()],
)
.await;
}
/// a
/// |
/// b
/// |
/// coll-persistent_storage: "true" / instance_id
/// |
/// [c:1]
/// / \
/// d coll-persistent_storage: "false"
/// |
/// [e:1]
///
/// Test that storage persistence can be disabled by a lower-level collection.
/// The following storage paths are used:
/// - indexed path
/// - moniker path with instance ids cleared
/// - moniker path with instance ids visible
#[fuchsia::test]
async fn storage_persistence_disablement() {
let components = vec![
(
"a",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::directory()
.name("minfs")
.path("/data")
.rights(fio::RW_STAR_DIR),
)
.capability(
CapabilityBuilder::storage()
.name("data")
.backing_dir("minfs")
.source(StorageDirectorySource::Self_),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Self_)
.target_static_child("b"),
)
.child_default("b")
.build(),
),
(
"b",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.use_(UseBuilder::storage().name("data").path("/data"))
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target(OfferTarget::Collection("persistent_coll".parse().unwrap())),
)
.collection(
CollectionBuilder::new().name("persistent_coll").persistent_storage(true),
)
.build(),
),
(
"c",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.use_(UseBuilder::storage().name("data").path("/data"))
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target_static_child("d"),
)
.offer(
OfferBuilder::storage()
.name("data")
.source(OfferSource::Parent)
.target(OfferTarget::Collection("non_persistent_coll".parse().unwrap())),
)
.child_default("d")
.collection(
CollectionBuilder::new().name("non_persistent_coll").persistent_storage(false),
)
.build(),
),
(
"d",
ComponentDeclBuilder::new()
.use_(
UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm"),
)
.use_(UseBuilder::storage().name("data").path("/data"))
.build(),
),
(
"e",
ComponentDeclBuilder::new()
.use_(UseBuilder::storage().name("data").path("/data"))
.build(),
),
];
// set instance_id for "b/persistent_coll:c" components
let instance_id = InstanceId::new_random(&mut rand::thread_rng());
let index = {
let mut index = component_id_index::Index::default();
index
.insert(vec!["b", "persistent_coll:c"].try_into().unwrap(), instance_id.clone())
.unwrap();
index
};
let component_id_index_path = make_index_file(index).unwrap();
let test = RoutingTestBuilder::new("a", components)
.set_component_id_index_path(component_id_index_path.path().to_owned().try_into().unwrap())
.build()
.await;
// create [c:1] under the storage persistent collection
test.create_dynamic_child(
&vec!["b"].try_into().unwrap(),
"persistent_coll",
ChildDecl {
name: "c".parse().unwrap(),
url: "test:///c".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// write to [c:1] storage
test.check_use(
vec!["b", "persistent_coll:c"].try_into().unwrap(),
CheckUse::Storage {
path: "/data".parse().unwrap(),
storage_relation: Some(
InstancedMoniker::try_from(vec!["b:0", "persistent_coll:c:1"]).unwrap(),
),
from_cm_namespace: false,
storage_subdir: None,
expected_res: ExpectedResult::Ok,
},
)
.await;
// start d:0 and write to storage
test.check_use(
vec!["b", "persistent_coll:c", "d"].try_into().unwrap(),
CheckUse::Storage {
path: "/data".parse().unwrap(),
storage_relation: Some(
InstancedMoniker::try_from(vec!["b:0", "persistent_coll:c:1", "d:0"]).unwrap(),
),
from_cm_namespace: false,
storage_subdir: None,
expected_res: ExpectedResult::Ok,
},
)
.await;
// create [e:1] under the non persistent collection
test.create_dynamic_child(
&vec!["b", "persistent_coll:c"].try_into().unwrap(),
"non_persistent_coll",
ChildDecl {
name: "e".parse().unwrap(),
url: "test:///e".to_string(),
startup: fdecl::StartupMode::Lazy,
environment: None,
on_terminate: None,
config_overrides: None,
},
)
.await;
// write to [e:1] storage
test.check_use(
vec!["b", "persistent_coll:c", "non_persistent_coll:e"].try_into().unwrap(),
CheckUse::Storage {
path: "/data".parse().unwrap(),
storage_relation: Some(
InstancedMoniker::try_from(vec![
"b:0",
"persistent_coll:c:1",
"non_persistent_coll:e:1",
])
.unwrap(),
),
from_cm_namespace: false,
storage_subdir: None,
expected_res: ExpectedResult::Ok,
},
)
.await;
// test that [c:1] wrote to instance id path
test.check_test_subdir_contents(&instance_id.to_string(), vec!["hippos".to_string()]).await;
// test that b:0 children includes:
// 1. persistent_coll:c:0 used by persistent storage
// 2. persistent_coll:c:1 used by non persistent storage
test.check_test_subdir_contents(
"b:0/children",
vec!["persistent_coll:c:0".to_string(), "persistent_coll:c:1".to_string()],
)
.await;
// test that d:0 wrote to moniker based path with instance ids cleared
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:0/children/d:0/data",
vec!["hippos".to_string()],
)
.await;
// test that [e:1] wrote to moniker based path with all instance ids visible
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:1/children/non_persistent_coll:e:1/data",
vec!["hippos".to_string()],
)
.await;
// destroy [c:1], which will shutdown d:0 and destroy [e:1]
test.destroy_dynamic_child(vec!["b"].try_into().unwrap(), "persistent_coll", "c").await;
// expect [c:1], d:0 storage and data to persist
test.check_test_subdir_contents(&instance_id.to_string(), vec!["hippos".to_string()]).await;
test.check_test_subdir_contents(
"b:0/children/persistent_coll:c:0/children/d:0/data",
vec!["hippos".to_string()],
)
.await;
// expect non_persistent_coll storage and data to be destroyed (only persistent_coll exists)
capability_util::confirm_storage_is_deleted_for_component(
None,
false,
InstancedMoniker::try_from(vec!["b:0", "persistent_coll:c:1", "non_persistent_coll:e:1"])
.unwrap(),
None,
&test.test_dir_proxy,
)
.await;
}