blob: 4fb92ee39cfa149dac57662900cac22bf54f91bd [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.cti
use {
crate::{
capability::{CapabilityProvider, CapabilitySource, FrameworkCapability},
channel,
model::{
error::ModelError,
hooks::{Event, EventPayload, EventType, Hook, HooksRegistration},
model::{ComponentManagerConfig, Model},
moniker::{AbsoluteMoniker, PartialMoniker},
realm::{BindReason, Realm},
},
},
anyhow::Error,
async_trait::async_trait,
cm_fidl_validator,
cm_rust::{CapabilityPath, FidlIntoNative},
fidl::endpoints::ServerEnd,
fidl_fuchsia_component as fcomponent,
fidl_fuchsia_io::DirectoryMarker,
fidl_fuchsia_sys2 as fsys, fuchsia_async as fasync, fuchsia_zircon as zx,
futures::prelude::*,
lazy_static::lazy_static,
log::*,
std::{
cmp,
convert::TryInto,
path::PathBuf,
sync::{Arc, Weak},
},
};
lazy_static! {
pub static ref REALM_SERVICE: CapabilityPath = "/svc/fuchsia.sys2.Realm".try_into().unwrap();
}
// The default implementation for framework services.
pub struct RealmCapabilityProvider {
scope_moniker: AbsoluteMoniker,
host: Arc<RealmCapabilityHost>,
}
impl RealmCapabilityProvider {
pub fn new(scope_moniker: AbsoluteMoniker, host: Arc<RealmCapabilityHost>) -> Self {
Self { scope_moniker, host }
}
}
#[async_trait]
impl CapabilityProvider for RealmCapabilityProvider {
async fn open(
self: Box<Self>,
_flags: u32,
_open_mode: u32,
_relative_path: PathBuf,
server_end: &mut zx::Channel,
) -> Result<(), ModelError> {
let server_end = channel::take_channel(server_end);
let stream = ServerEnd::<fsys::RealmMarker>::new(server_end)
.into_stream()
.expect("could not convert channel into stream");
let scope_moniker = self.scope_moniker.clone();
let host = self.host.clone();
fasync::spawn(async move {
if let Err(e) = host.serve(scope_moniker, stream).await {
// TODO: Set an epitaph to indicate this was an unexpected error.
warn!("serve_realm failed: {:?}", e);
}
});
Ok(())
}
}
#[derive(Clone)]
pub struct RealmCapabilityHost {
model: Arc<Model>,
config: ComponentManagerConfig,
}
// `RealmCapabilityHost` is a `Hook` that serves the `Realm` FIDL protocol.
impl RealmCapabilityHost {
pub fn new(model: Arc<Model>, config: ComponentManagerConfig) -> Self {
Self { model, config }
}
pub fn hooks(self: &Arc<Self>) -> Vec<HooksRegistration> {
vec![HooksRegistration::new(
"RealmCapabilityHost",
vec![EventType::CapabilityRouted],
Arc::downgrade(self) as Weak<dyn Hook>,
)]
}
pub async fn serve(
&self,
scope_moniker: AbsoluteMoniker,
mut stream: fsys::RealmRequestStream,
) -> Result<(), Error> {
// We only need to look up the realm matching this scope.
// These realm operations should all work, even if the scope realm is not running.
// A successful call to BindChild will cause the scope realm to start running.
let realm = Arc::downgrade(&self.model.look_up_realm(&scope_moniker).await?);
while let Some(request) = stream.try_next().await? {
let realm = match realm.upgrade() {
Some(r) => r,
None => {
break;
}
};
match request {
fsys::RealmRequest::CreateChild { responder, collection, decl } => {
let mut res = Self::create_child(realm, collection, decl).await;
responder.send(&mut res)?;
}
fsys::RealmRequest::BindChild { responder, child, exposed_dir } => {
let mut res = Self::bind_child(realm, child, exposed_dir).await;
responder.send(&mut res)?;
}
fsys::RealmRequest::DestroyChild { responder, child } => {
let mut res = Self::destroy_child(realm, child).await;
responder.send(&mut res)?;
}
fsys::RealmRequest::ListChildren { responder, collection, iter } => {
let mut res = Self::list_children(&self.config, realm, collection, iter).await;
responder.send(&mut res)?;
}
}
}
Ok(())
}
async fn create_child(
realm: Arc<Realm>,
collection: fsys::CollectionRef,
child_decl: fsys::ChildDecl,
) -> Result<(), fcomponent::Error> {
cm_fidl_validator::validate_child(&child_decl)
.map_err(|_| fcomponent::Error::InvalidArguments)?;
let child_decl = child_decl.fidl_into_native();
realm.add_dynamic_child(collection.name, &child_decl).await.map_err(|e| match e {
ModelError::InstanceAlreadyExists { .. } => fcomponent::Error::InstanceAlreadyExists,
ModelError::CollectionNotFound { .. } => fcomponent::Error::CollectionNotFound,
ModelError::Unsupported { .. } => fcomponent::Error::Unsupported,
e => {
error!("add_dynamic_child() failed: {:?}", e);
fcomponent::Error::Internal
}
})?;
Ok(())
}
async fn bind_child(
realm: Arc<Realm>,
child: fsys::ChildRef,
exposed_dir: ServerEnd<DirectoryMarker>,
) -> Result<(), fcomponent::Error> {
let partial_moniker = PartialMoniker::new(child.name, child.collection);
let child_realm = {
let realm_state = realm.lock_resolved_state().await.map_err(|e| match e {
ModelError::ResolverError { err } => {
debug!("failed to resolve: {:?}", err);
fcomponent::Error::InstanceCannotResolve
}
e => {
error!("failed to resolve RealmState: {}", e);
fcomponent::Error::Internal
}
})?;
realm_state.get_live_child_realm(&partial_moniker).map(|r| r.clone())
};
let mut exposed_dir = exposed_dir.into_channel();
if let Some(child_realm) = child_realm {
child_realm
.bind(&BindReason::BindChild { parent: realm.abs_moniker.clone() })
.await
.map_err(|e| match e {
ModelError::ResolverError { err } => {
debug!("failed to resolve child: {:?}", err);
fcomponent::Error::InstanceCannotResolve
}
ModelError::RunnerError { err } => {
debug!("failed to start child: {:?}", err);
fcomponent::Error::InstanceCannotStart
}
e => {
error!("bind() failed: {:?}", e);
fcomponent::Error::Internal
}
})?
.open_exposed(&mut exposed_dir)
.await
.map_err(|e| {
error!("open_exposed() failed: {:?}", e);
fcomponent::Error::Internal
})?;
} else {
return Err(fcomponent::Error::InstanceNotFound);
}
Ok(())
}
async fn destroy_child(
realm: Arc<Realm>,
child: fsys::ChildRef,
) -> Result<(), fcomponent::Error> {
child.collection.as_ref().ok_or(fcomponent::Error::InvalidArguments)?;
let partial_moniker = PartialMoniker::new(child.name, child.collection);
let _ = realm.remove_dynamic_child(&partial_moniker).await.map_err(|e| match e {
ModelError::InstanceNotFoundInRealm { .. } => fcomponent::Error::InstanceNotFound,
ModelError::Unsupported { .. } => fcomponent::Error::Unsupported,
e => {
error!("remove_dynamic_child() failed: {:?}", e);
fcomponent::Error::Internal
}
})?;
Ok(())
}
async fn list_children(
config: &ComponentManagerConfig,
realm: Arc<Realm>,
collection: fsys::CollectionRef,
iter: ServerEnd<fsys::ChildIteratorMarker>,
) -> Result<(), fcomponent::Error> {
let state = realm.lock_resolved_state().await.map_err(|e| {
error!("failed to resolve RealmState: {:?}", e);
fcomponent::Error::Internal
})?;
let decl = state.decl();
let _ = decl
.find_collection(&collection.name)
.ok_or_else(|| fcomponent::Error::CollectionNotFound)?;
let mut children: Vec<_> = state
.live_child_realms()
.filter_map(|(m, _)| match m.collection() {
Some(c) => {
if c == collection.name {
Some(fsys::ChildRef {
name: m.name().to_string(),
collection: m.collection().map(|s| s.to_string()),
})
} else {
None
}
}
_ => None,
})
.collect();
children.sort_unstable_by(|a, b| {
let a = &a.name;
let b = &b.name;
if a == b {
cmp::Ordering::Equal
} else if a < b {
cmp::Ordering::Less
} else {
cmp::Ordering::Greater
}
});
let stream = iter.into_stream().expect("could not convert iterator channel into stream");
let batch_size = config.list_children_batch_size;
fasync::spawn(async move {
if let Err(e) = Self::serve_child_iterator(children, stream, batch_size).await {
// TODO: Set an epitaph to indicate this was an unexpected error.
warn!("serve_child_iterator failed: {:?}", e);
}
});
Ok(())
}
async fn serve_child_iterator(
mut children: Vec<fsys::ChildRef>,
mut stream: fsys::ChildIteratorRequestStream,
batch_size: usize,
) -> Result<(), Error> {
while let Some(request) = stream.try_next().await? {
match request {
fsys::ChildIteratorRequest::Next { responder } => {
let n_to_send = std::cmp::min(children.len(), batch_size);
let mut res: Vec<_> = children.drain(..n_to_send).collect();
responder.send(&mut res.iter_mut())?;
}
}
}
Ok(())
}
async fn on_scoped_framework_capability_routed_async<'a>(
self: Arc<Self>,
scope_moniker: AbsoluteMoniker,
capability: &'a FrameworkCapability,
capability_provider: Option<Box<dyn CapabilityProvider>>,
) -> Result<Option<Box<dyn CapabilityProvider>>, ModelError> {
// If some other capability has already been installed, then there's nothing to
// do here.
match (&capability_provider, capability) {
(None, FrameworkCapability::Protocol(capability_path))
if *capability_path == *REALM_SERVICE =>
{
return Ok(Some(
Box::new(RealmCapabilityProvider::new(scope_moniker, self.clone()))
as Box<dyn CapabilityProvider>,
));
}
_ => return Ok(capability_provider),
}
}
}
#[async_trait]
impl Hook for RealmCapabilityHost {
async fn on(self: Arc<Self>, event: &Event) -> Result<(), ModelError> {
if let Ok(EventPayload::CapabilityRouted {
source: CapabilitySource::Framework { capability, scope_moniker: Some(scope_moniker) },
capability_provider,
}) = &event.result
{
let mut capability_provider = capability_provider.lock().await;
*capability_provider = self
.on_scoped_framework_capability_routed_async(
scope_moniker.clone(),
&capability,
capability_provider.take(),
)
.await?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use {
crate::{
builtin_environment::BuiltinEnvironment,
model::{
binding::Binder,
events::{event::SyncMode, source::EventSource, stream::EventStream},
model::ModelParams,
moniker::AbsoluteMoniker,
realm::BindReason,
resolver::ResolverRegistry,
testing::{mocks::*, out_dir::OutDir, test_helpers, test_helpers::*, test_hook::*},
},
startup,
},
cm_rust::{
self, CapabilityName, CapabilityPath, ChildDecl, ExposeDecl, ExposeProtocolDecl,
ExposeSource, ExposeTarget, NativeIntoFidl,
},
fidl::endpoints,
fidl_fidl_examples_echo as echo,
fidl_fuchsia_io::MODE_TYPE_SERVICE,
fuchsia_async as fasync,
io_util::OPEN_RIGHT_READABLE,
std::collections::HashSet,
std::convert::TryFrom,
std::path::PathBuf,
};
struct RealmCapabilityTest {
pub model: Arc<Model>,
pub builtin_environment: Arc<BuiltinEnvironment>,
realm: Arc<Realm>,
realm_proxy: fsys::RealmProxy,
hook: Arc<TestHook>,
events_data: Option<EventsData>,
}
struct EventsData {
_event_source: EventSource,
event_stream: EventStream,
}
impl RealmCapabilityTest {
async fn new(
mock_resolver: MockResolver,
mock_runner: Arc<MockRunner>,
realm_moniker: AbsoluteMoniker,
events: Vec<CapabilityName>,
) -> Self {
// Init model.
let mut resolver = ResolverRegistry::new();
resolver.register("test".to_string(), Box::new(mock_resolver));
let mut config = ComponentManagerConfig::default();
config.list_children_batch_size = 2;
let startup_args = startup::Arguments {
use_builtin_process_launcher: false,
root_component_url: "".to_string(),
debug: false,
};
let model = Arc::new(Model::new(ModelParams {
root_component_url: "test:///root".to_string(),
root_resolver_registry: resolver,
}));
let builtin_environment = Arc::new(
BuiltinEnvironment::new(
&startup_args,
&model,
config,
&vec![(test_helpers::TEST_RUNNER_NAME.into(), mock_runner as _)]
.into_iter()
.collect(),
)
.await
.expect("failed to set up builtin environment"),
);
let builtin_environment_inner = builtin_environment.clone();
let hook = Arc::new(TestHook::new());
let hooks = hook.hooks();
let events_data = if events.is_empty() {
None
} else {
let mut event_source = builtin_environment_inner
.event_source_factory
.create_for_debug(SyncMode::Sync)
.await
.expect("created event source");
let event_stream =
event_source.subscribe(events).await.expect("subscribe to event stream");
event_source.start_component_tree().await;
Some(EventsData { _event_source: event_source, event_stream })
};
model.root_realm.hooks.install(hooks).await;
// Look up and bind to realm.
let realm = model
.bind(&realm_moniker, &BindReason::Eager)
.await
.expect("failed to bind to realm");
// Host framework service.
let (realm_proxy, stream) =
endpoints::create_proxy_and_stream::<fsys::RealmMarker>().unwrap();
{
fasync::spawn(async move {
builtin_environment_inner
.realm_capability_host
.serve(realm_moniker, stream)
.await
.expect("failed serving realm service");
});
}
RealmCapabilityTest {
model,
builtin_environment,
realm,
realm_proxy,
hook,
events_data,
}
}
fn event_stream(&mut self) -> Option<&mut EventStream> {
self.events_data.as_mut().map(|data| &mut data.event_stream)
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn create_dynamic_child() {
// Set up model and realm service.
let mut mock_resolver = MockResolver::new();
let mock_runner = Arc::new(MockRunner::new());
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("system")
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component(
"system",
ComponentDeclBuilder::new()
.add_collection("coll", fsys::Durability::Transient)
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
let test = RealmCapabilityTest::new(
mock_resolver,
mock_runner.clone(),
vec!["system:0"].into(),
vec![],
)
.await;
// Create children "a" and "b" in collection.
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("a")).await;
let _ = res.expect("failed to create child a").expect("failed to create child a");
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("b")).await;
let _ = res.expect("failed to create child b").expect("failed to create child b");
// Verify that the component topology matches expectations.
let actual_children = get_live_children(&test.realm).await;
let mut expected_children: HashSet<PartialMoniker> = HashSet::new();
expected_children.insert("coll:a".into());
expected_children.insert("coll:b".into());
assert_eq!(actual_children, expected_children);
assert_eq!("(system(coll:a,coll:b))", test.hook.print());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn create_dynamic_child_errors() {
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("system")
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component(
"system",
ComponentDeclBuilder::new()
.add_collection("coll", fsys::Durability::Transient)
.add_collection("pcoll", fsys::Durability::Persistent)
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
let test = RealmCapabilityTest::new(
mock_resolver,
Arc::new(MockRunner::new()),
vec!["system:0"].into(),
vec![],
)
.await;
// Invalid arguments.
{
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let child_decl = fsys::ChildDecl {
name: Some("a".to_string()),
url: None,
startup: Some(fsys::StartupMode::Lazy),
environment: None,
};
let err = test
.realm_proxy
.create_child(&mut collection_ref, child_decl)
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::InvalidArguments);
}
// Instance already exists.
{
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("a")).await;
let _ = res.expect("failed to create child a");
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let err = test
.realm_proxy
.create_child(&mut collection_ref, child_decl("a"))
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::InstanceAlreadyExists);
}
// Collection not found.
{
let mut collection_ref = fsys::CollectionRef { name: "nonexistent".to_string() };
let err = test
.realm_proxy
.create_child(&mut collection_ref, child_decl("a"))
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::CollectionNotFound);
}
// Unsupported.
{
let mut collection_ref = fsys::CollectionRef { name: "pcoll".to_string() };
let err = test
.realm_proxy
.create_child(&mut collection_ref, child_decl("a"))
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::Unsupported);
}
{
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let child_decl = fsys::ChildDecl {
name: Some("b".to_string()),
url: Some("test:///b".to_string()),
startup: Some(fsys::StartupMode::Eager),
environment: None,
};
let err = test
.realm_proxy
.create_child(&mut collection_ref, child_decl)
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::Unsupported);
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn destroy_dynamic_child() {
// Set up model and realm service.
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("system")
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component(
"system",
ComponentDeclBuilder::new()
.add_collection("coll", fsys::Durability::Transient)
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component("a", component_decl_with_test_runner());
mock_resolver.add_component("b", component_decl_with_test_runner());
let events = vec![EventType::MarkedForDestruction.into(), EventType::Destroyed.into()];
let mut test = RealmCapabilityTest::new(
mock_resolver,
Arc::new(MockRunner::new()),
vec!["system:0"].into(),
events,
)
.await;
// Create children "a" and "b" in collection, and bind to them.
for name in &["a", "b"] {
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl(name)).await;
let _ = res
.unwrap_or_else(|_| panic!("failed to create child {}", name))
.unwrap_or_else(|_| panic!("failed to create child {}", name));
let mut child_ref =
fsys::ChildRef { name: name.to_string(), collection: Some("coll".to_string()) };
let (_dir_proxy, server_end) = endpoints::create_proxy::<DirectoryMarker>().unwrap();
let res = test.realm_proxy.bind_child(&mut child_ref, server_end).await;
let _ = res
.unwrap_or_else(|_| panic!("failed to bind to child {}", name))
.unwrap_or_else(|_| panic!("failed to bind to child {}", name));
}
let child_realm = get_live_child(&test.realm, "coll:a").await;
let instance_id = get_instance_id(&test.realm, "coll:a").await;
assert_eq!("(system(coll:a,coll:b))", test.hook.print());
assert_eq!(child_realm.component_url, "test:///a".to_string());
assert_eq!(instance_id, 1);
// Destroy "a". "a" is no longer live from the client's perspective, although it's still
// being destroyed.
let mut child_ref =
fsys::ChildRef { name: "a".to_string(), collection: Some("coll".to_string()) };
let (f, destroy_handle) = test.realm_proxy.destroy_child(&mut child_ref).remote_handle();
fasync::spawn(f);
let event = test
.event_stream()
.unwrap()
.wait_until(EventType::MarkedForDestruction, vec!["system:0", "coll:a:1"].into())
.await
.unwrap();
// Child is not marked deleted yet.
{
let actual_children = get_live_children(&test.realm).await;
let mut expected_children: HashSet<PartialMoniker> = HashSet::new();
expected_children.insert("coll:a".into());
expected_children.insert("coll:b".into());
assert_eq!(actual_children, expected_children);
}
// The destruction of "a" was arrested during `PreDestroy`. The old "a" should still exist,
// although it's not live.
assert!(has_child(&test.realm, "coll:a:1").await);
// Move past the 'PreDestroy' event for "a", and wait for destroy_child to return.
event.resume();
let res = destroy_handle.await;
let _ = res.expect("failed to destroy child a").expect("failed to destroy child a");
// Child is marked deleted now.
{
let actual_children = get_live_children(&test.realm).await;
let mut expected_children: HashSet<PartialMoniker> = HashSet::new();
expected_children.insert("coll:b".into());
assert_eq!(actual_children, expected_children);
assert_eq!("(system(coll:b))", test.hook.print());
}
// Wait until 'PostDestroy' event for "a"
let event = test
.event_stream()
.unwrap()
.wait_until(EventType::Destroyed, vec!["system:0", "coll:a:1"].into())
.await
.unwrap();
event.resume();
assert!(!has_child(&test.realm, "coll:a:1").await);
// Recreate "a" and verify "a" is back (but it's a different "a"). The old "a" is gone
// from the client's point of view, but it hasn't been cleaned up yet.
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let child_decl = fsys::ChildDecl {
name: Some("a".to_string()),
url: Some("test:///a_alt".to_string()),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
};
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl).await;
let _ = res.expect("failed to recreate child a").expect("failed to recreate child a");
assert_eq!("(system(coll:a,coll:b))", test.hook.print());
let child_realm = get_live_child(&test.realm, "coll:a").await;
let instance_id = get_instance_id(&test.realm, "coll:a").await;
assert_eq!(child_realm.component_url, "test:///a_alt".to_string());
assert_eq!(instance_id, 3);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn destroy_dynamic_child_errors() {
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("system")
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component(
"system",
ComponentDeclBuilder::new()
.add_collection("coll", fsys::Durability::Transient)
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
let test = RealmCapabilityTest::new(
mock_resolver,
Arc::new(MockRunner::new()),
vec!["system:0"].into(),
vec![],
)
.await;
// Create child "a" in collection.
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("a")).await;
let _ = res.expect("failed to create child a").expect("failed to create child a");
// Invalid arguments.
{
let mut child_ref = fsys::ChildRef { name: "a".to_string(), collection: None };
let err = test
.realm_proxy
.destroy_child(&mut child_ref)
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::InvalidArguments);
}
// Instance not found.
{
let mut child_ref =
fsys::ChildRef { name: "b".to_string(), collection: Some("coll".to_string()) };
let err = test
.realm_proxy
.destroy_child(&mut child_ref)
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::InstanceNotFound);
}
}
#[fuchsia_async::run_singlethreaded(test)]
async fn bind_static_child() {
// Create a hierarchy of three components, the last with eager startup. The middle
// component hosts and exposes the "/svc/hippo" service.
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("system")
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component(
"system",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_path: CapabilityPath::try_from("/svc/foo").unwrap(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
target: ExposeTarget::Realm,
}))
.add_eager_child("eager")
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component("eager", component_decl_with_test_runner());
let mock_runner = Arc::new(MockRunner::new());
let mut out_dir = OutDir::new();
out_dir.add_echo_service(CapabilityPath::try_from("/svc/foo").unwrap());
mock_runner.add_host_fn("test:///system_resolved", out_dir.host_fn());
let test =
RealmCapabilityTest::new(mock_resolver, mock_runner.clone(), vec![].into(), vec![])
.await;
// Bind to child and use exposed service.
let mut child_ref = fsys::ChildRef { name: "system".to_string(), collection: None };
let (dir_proxy, server_end) = endpoints::create_proxy::<DirectoryMarker>().unwrap();
let res = test.realm_proxy.bind_child(&mut child_ref, server_end).await;
let _ = res.expect("failed to bind to system").expect("failed to bind to system");
let node_proxy = io_util::open_node(
&dir_proxy,
&PathBuf::from("svc/hippo"),
OPEN_RIGHT_READABLE,
MODE_TYPE_SERVICE,
)
.expect("failed to open echo service");
let echo_proxy = echo::EchoProxy::new(node_proxy.into_channel().unwrap());
let res = echo_proxy.echo_string(Some("hippos")).await;
assert_eq!(res.expect("failed to use echo service"), Some("hippos".to_string()));
// Verify that the bindings happened (including the eager binding) and the component
// topology matches expectations.
let expected_urls = vec![
"test:///root_resolved".to_string(),
"test:///system_resolved".to_string(),
"test:///eager_resolved".to_string(),
];
assert_eq!(mock_runner.urls_run(), expected_urls);
assert_eq!("(system(eager))", test.hook.print());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn bind_dynamic_child() {
// Create a root component with a collection and define a component that exposes a service.
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_collection("coll", fsys::Durability::Transient)
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component(
"system",
ComponentDeclBuilder::new()
.expose(ExposeDecl::Protocol(ExposeProtocolDecl {
source: ExposeSource::Self_,
source_path: CapabilityPath::try_from("/svc/foo").unwrap(),
target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
target: ExposeTarget::Realm,
}))
.build(),
);
let mock_runner = Arc::new(MockRunner::new());
let mut out_dir = OutDir::new();
out_dir.add_echo_service(CapabilityPath::try_from("/svc/foo").unwrap());
mock_runner.add_host_fn("test:///system_resolved", out_dir.host_fn());
let test =
RealmCapabilityTest::new(mock_resolver, mock_runner.clone(), vec![].into(), vec![])
.await;
// Add "system" to collection.
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("system")).await;
let _ = res.expect("failed to create child system").expect("failed to create child system");
// Bind to child and use exposed service.
let mut child_ref =
fsys::ChildRef { name: "system".to_string(), collection: Some("coll".to_string()) };
let (dir_proxy, server_end) = endpoints::create_proxy::<DirectoryMarker>().unwrap();
let res = test.realm_proxy.bind_child(&mut child_ref, server_end).await;
let _ = res.expect("failed to bind to system").expect("failed to bind to system");
let node_proxy = io_util::open_node(
&dir_proxy,
&PathBuf::from("svc/hippo"),
OPEN_RIGHT_READABLE,
MODE_TYPE_SERVICE,
)
.expect("failed to open echo service");
let echo_proxy = echo::EchoProxy::new(node_proxy.into_channel().unwrap());
let res = echo_proxy.echo_string(Some("hippos")).await;
assert_eq!(res.expect("failed to use echo service"), Some("hippos".to_string()));
// Verify that the binding happened and the component topology matches expectations.
let expected_urls =
vec!["test:///root_resolved".to_string(), "test:///system_resolved".to_string()];
assert_eq!(mock_runner.urls_run(), expected_urls);
assert_eq!("(coll:system)", test.hook.print());
}
#[fuchsia_async::run_singlethreaded(test)]
async fn bind_child_errors() {
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("system")
.add_lazy_child("unresolvable")
.add_lazy_child("unrunnable")
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component("system", component_decl_with_test_runner());
mock_resolver.add_component("unrunnable", component_decl_with_test_runner());
let mock_runner = Arc::new(MockRunner::new());
mock_runner.cause_failure("unrunnable");
let test =
RealmCapabilityTest::new(mock_resolver, mock_runner, vec![].into(), vec![]).await;
// Instance not found.
{
let mut child_ref = fsys::ChildRef { name: "missing".to_string(), collection: None };
let (_, server_end) = endpoints::create_proxy::<DirectoryMarker>().unwrap();
let err = test
.realm_proxy
.bind_child(&mut child_ref, server_end)
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::InstanceNotFound);
}
// Instance cannot resolve.
{
let mut child_ref =
fsys::ChildRef { name: "unresolvable".to_string(), collection: None };
let (_, server_end) = endpoints::create_proxy::<DirectoryMarker>().unwrap();
let err = test
.realm_proxy
.bind_child(&mut child_ref, server_end)
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::InstanceCannotResolve);
}
}
// If a runner fails to launch a child, the error should not occur at `bind_child`.
#[fuchsia_async::run_singlethreaded(test)]
async fn bind_child_runner_failure() {
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("unrunnable")
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component("unrunnable", component_decl_with_test_runner());
let mock_runner = Arc::new(MockRunner::new());
mock_runner.cause_failure("unrunnable");
let test =
RealmCapabilityTest::new(mock_resolver, mock_runner, vec![].into(), vec![]).await;
let mut child_ref = fsys::ChildRef { name: "unrunnable".to_string(), collection: None };
let (_, server_end) = endpoints::create_proxy::<DirectoryMarker>().unwrap();
test.realm_proxy
.bind_child(&mut child_ref, server_end)
.await
.expect("fidl call failed")
.expect("bind failed");
// TODO(fxb/46913): Assert that `server_end` closes once instance death is monitored.
}
fn child_decl(name: &str) -> fsys::ChildDecl {
ChildDecl {
name: name.to_string(),
url: format!("test:///{}", name),
startup: fsys::StartupMode::Lazy,
environment: None,
}
.native_into_fidl()
}
#[fuchsia_async::run_singlethreaded(test)]
async fn list_children() {
// Create a root component with collections and a static child.
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_lazy_child("static")
.add_collection("coll", fsys::Durability::Transient)
.add_collection("coll2", fsys::Durability::Transient)
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
mock_resolver.add_component("static", component_decl_with_test_runner());
let mock_runner = Arc::new(MockRunner::new());
let test =
RealmCapabilityTest::new(mock_resolver, mock_runner, vec![].into(), vec![]).await;
// Create children "a" and "b" in collection 1, "c" in collection 2.
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("a")).await;
let _ = res.expect("failed to create child a").expect("failed to create child a");
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("b")).await;
let _ = res.expect("failed to create child b").expect("failed to create child b");
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("c")).await;
let _ = res.expect("failed to create child c").expect("failed to create child c");
let mut collection_ref = fsys::CollectionRef { name: "coll2".to_string() };
let res = test.realm_proxy.create_child(&mut collection_ref, child_decl("d")).await;
let _ = res.expect("failed to create child d").expect("failed to create child d");
// Verify that we see the expected children when listing the collection.
let (iterator_proxy, server_end) = endpoints::create_proxy().unwrap();
let mut collection_ref = fsys::CollectionRef { name: "coll".to_string() };
let res = test.realm_proxy.list_children(&mut collection_ref, server_end).await;
let _ = res.expect("failed to list children").expect("failed to list children");
let res = iterator_proxy.next().await;
let children = res.expect("failed to iterate over children");
assert_eq!(
children,
vec![
fsys::ChildRef { name: "a".to_string(), collection: Some("coll".to_string()) },
fsys::ChildRef { name: "b".to_string(), collection: Some("coll".to_string()) },
]
);
let res = iterator_proxy.next().await;
let children = res.expect("failed to iterate over children");
assert_eq!(
children,
vec![fsys::ChildRef { name: "c".to_string(), collection: Some("coll".to_string()) },]
);
let res = iterator_proxy.next().await;
let children = res.expect("failed to iterate over children");
assert_eq!(children, vec![]);
}
#[fuchsia_async::run_singlethreaded(test)]
async fn list_children_errors() {
// Create a root component with a collection.
let mut mock_resolver = MockResolver::new();
mock_resolver.add_component(
"root",
ComponentDeclBuilder::new()
.add_collection("coll", fsys::Durability::Transient)
.offer_runner_to_children(TEST_RUNNER_NAME)
.build(),
);
let mock_runner = Arc::new(MockRunner::new());
let test =
RealmCapabilityTest::new(mock_resolver, mock_runner, vec![].into(), vec![]).await;
// Collection not found.
{
let mut collection_ref = fsys::CollectionRef { name: "nonexistent".to_string() };
let (_, server_end) = endpoints::create_proxy().unwrap();
let err = test
.realm_proxy
.list_children(&mut collection_ref, server_end)
.await
.expect("fidl call failed")
.expect_err("unexpected success");
assert_eq!(err, fcomponent::Error::CollectionNotFound);
}
}
}