[componentmgr] Hub: Implement in directory
This CL implements an exec/in directory in Hub v2
that corresponds to the incoming namespace of a component.
Bug: CF-541
Change-Id: I30170e2f789224366396aa4f8012e36732421b99
diff --git a/examples/components/routing/meta/echo_client.cml b/examples/components/routing/meta/echo_client.cml
index fa9f88c..865c536 100644
--- a/examples/components/routing/meta/echo_client.cml
+++ b/examples/components/routing/meta/echo_client.cml
@@ -3,6 +3,7 @@
{
"program": {
"binary": "bin/echo_client",
+ "args": ["Hippos", "rule!"],
},
"use": [
{
diff --git a/src/sys/component_manager/src/directory_broker.rs b/src/sys/component_manager/src/directory_broker.rs
index ba36b99..a47469f 100644
--- a/src/sys/component_manager/src/directory_broker.rs
+++ b/src/sys/component_manager/src/directory_broker.rs
@@ -12,6 +12,8 @@
void::Void,
};
+pub type RoutingFn = Box<FnMut(u32, u32, String, ServerEnd<NodeMarker>) + Send>;
+
// TODO(ZX-3606): move this into the pseudo dir fs crate.
/// DirectoryBroker exists to hold a slot in a fuchsia_vfs_pseudo_fs directory and proxy open
/// requests. A DirectoryBroker holds a closure provided at creation time, and whenever an open
@@ -23,15 +25,13 @@
/// mode: u32
/// relative_path: String
/// server_end: ServerEnd<NodeMarker>
- route_open: Box<FnMut(u32, u32, String, ServerEnd<NodeMarker>) + Send>,
+ route_open: RoutingFn,
entry_info: fvfs::directory::entry::EntryInfo,
}
impl DirectoryBroker {
/// new will create a new DirectoryBroker to forward directory open requests.
- pub fn new(
- route_open: Box<FnMut(u32, u32, String, ServerEnd<NodeMarker>) + Send>,
- ) -> DirectoryBroker {
+ pub fn new(route_open: RoutingFn) -> DirectoryBroker {
return DirectoryBroker {
route_open,
entry_info: fvfs::directory::entry::EntryInfo::new(INO_UNKNOWN, DIRENT_TYPE_SERVICE),
diff --git a/src/sys/component_manager/src/model/dir_tree.rs b/src/sys/component_manager/src/model/dir_tree.rs
new file mode 100644
index 0000000..787d820
--- /dev/null
+++ b/src/sys/component_manager/src/model/dir_tree.rs
@@ -0,0 +1,180 @@
+// 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::directory_broker::{DirectoryBroker, RoutingFn},
+ crate::model::{
+ error::ModelError, moniker::AbsoluteMoniker, routing_fn_factory::RoutingFnFactory,
+ },
+ cm_rust::{Capability, ComponentDecl},
+ fuchsia_vfs_pseudo_fs::directory,
+ log::*,
+ std::collections::HashMap,
+};
+
+/// Represents the directory hierarchy of the exposed directory, not including the nodes for the
+/// capabilities themselves.
+pub struct DirTree {
+ directory_nodes: HashMap<String, Box<DirTree>>,
+ broker_nodes: HashMap<String, RoutingFn>,
+}
+
+impl DirTree {
+ /// Builds a directory hierarchy from a component's `uses` declarations.
+ pub fn build_from_uses(
+ route_fn_factory: RoutingFnFactory,
+ abs_moniker: &AbsoluteMoniker,
+ decl: ComponentDecl,
+ ) -> Result<Self, ModelError> {
+ let mut tree = DirTree { directory_nodes: HashMap::new(), broker_nodes: HashMap::new() };
+ for use_ in decl.uses {
+ let capability = match use_ {
+ cm_rust::UseDecl::Directory(d) => Capability::Directory(d.target_path),
+ cm_rust::UseDecl::Service(d) => Capability::Service(d.target_path),
+ cm_rust::UseDecl::Storage(_) => {
+ error!("storage capabilities are not supported");
+ return Err(ModelError::ComponentInvalid);
+ }
+ };
+ tree.add_capability(&route_fn_factory, abs_moniker, capability);
+ }
+ Ok(tree)
+ }
+
+ /// Installs the directory tree into `root_dir`.
+ pub fn install(
+ self,
+ abs_moniker: &AbsoluteMoniker,
+ root_dir: &mut directory::simple::Simple<'static>,
+ ) -> Result<(), ModelError> {
+ for (name, subtree) in self.directory_nodes {
+ let mut subdir = directory::simple::empty();
+ subtree.install(abs_moniker, &mut subdir)?;
+ root_dir
+ .add_entry(&name, subdir)
+ .map_err(|_| ModelError::add_entry_error(abs_moniker.clone(), &name as &str))?;
+ }
+ for (name, route_fn) in self.broker_nodes {
+ let node = DirectoryBroker::new(route_fn);
+ root_dir
+ .add_entry(&name, node)
+ .map_err(|_| ModelError::add_entry_error(abs_moniker.clone(), &name as &str))?;
+ }
+ Ok(())
+ }
+
+ fn add_capability(
+ &mut self,
+ route_fn_factory: &RoutingFnFactory,
+ abs_moniker: &AbsoluteMoniker,
+ capability: Capability,
+ ) {
+ let path = capability.path().expect("missing path");
+ let components = path.dirname.split("/");
+ let mut tree = self;
+ for component in components {
+ if !component.is_empty() {
+ tree = tree.directory_nodes.entry(component.to_string()).or_insert(Box::new(
+ DirTree { directory_nodes: HashMap::new(), broker_nodes: HashMap::new() },
+ ));
+ }
+ }
+ tree.broker_nodes.insert(
+ path.basename.to_string(),
+ route_fn_factory.create_route_fn(&abs_moniker, capability),
+ );
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use {
+ super::*,
+ crate::model::testing::{mocks, routing_test_helpers::default_component_decl, test_utils},
+ cm_rust::{CapabilityPath, UseDecl, UseDirectoryDecl, UseServiceDecl},
+ fidl::endpoints::{ClientEnd, ServerEnd},
+ fidl_fuchsia_io::{DirectoryMarker, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE},
+ fuchsia_async as fasync,
+ fuchsia_vfs_pseudo_fs::{
+ directory::{self, entry::DirectoryEntry},
+ file::simple::read_only,
+ },
+ fuchsia_zircon as zx,
+ std::{convert::TryFrom, iter, sync::Arc},
+ };
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn make_in_directory() {
+ // Make a directory tree that will be forwarded to by ProxyingRoutingFnFactory.
+ let mut sub_dir = directory::simple::empty();
+ let (sub_dir_client, sub_dir_server) = zx::Channel::create().unwrap();
+ sub_dir.open(
+ OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
+ 0,
+ &mut iter::empty(),
+ ServerEnd::<NodeMarker>::new(sub_dir_server.into()),
+ );
+
+ // Add a 'hello' file in the subdirectory for testing purposes.
+ sub_dir
+ .add_entry("hello", { read_only(move || Ok(b"friend".to_vec())) })
+ .map_err(|(s, _)| s)
+ .expect("Failed to add 'hello' entry");
+
+ let sub_dir_proxy = ClientEnd::<DirectoryMarker>::new(sub_dir_client)
+ .into_proxy()
+ .expect("failed to create directory proxy");
+ fasync::spawn(async move {
+ let _ = await!(sub_dir);
+ });
+
+ let route_fn_factory = Arc::new(mocks::ProxyingRoutingFnFactory::new(sub_dir_proxy));
+ let decl = ComponentDecl {
+ uses: vec![
+ UseDecl::Directory(UseDirectoryDecl {
+ source_path: CapabilityPath::try_from("/data/baz").unwrap(),
+ target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
+ }),
+ UseDecl::Service(UseServiceDecl {
+ source_path: CapabilityPath::try_from("/svc/baz").unwrap(),
+ target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
+ }),
+ UseDecl::Directory(UseDirectoryDecl {
+ source_path: CapabilityPath::try_from("/data/foo").unwrap(),
+ target_path: CapabilityPath::try_from("/data/bar").unwrap(),
+ }),
+ ],
+ ..default_component_decl()
+ };
+ let abs_moniker = AbsoluteMoniker::root();
+ let tree = DirTree::build_from_uses(route_fn_factory, &abs_moniker, decl.clone())
+ .expect("Unable to build 'uses' directory");
+
+ let mut in_dir = directory::simple::empty();
+ tree.install(&abs_moniker, &mut in_dir).expect("Unable to build pseudodirectory");
+ let (in_dir_client, in_dir_server) = zx::Channel::create().unwrap();
+ in_dir.open(
+ OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE,
+ 0,
+ &mut iter::empty(),
+ ServerEnd::<NodeMarker>::new(in_dir_server.into()),
+ );
+ fasync::spawn(async move {
+ let _ = await!(in_dir);
+ });
+ let in_dir_proxy = ClientEnd::<DirectoryMarker>::new(in_dir_client)
+ .into_proxy()
+ .expect("failed to create directory proxy");
+ assert_eq!(
+ vec!["data/bar", "data/hippo", "svc/hippo"],
+ await!(test_utils::list_directory_recursive(&in_dir_proxy))
+ );
+
+ // All entries in the in directory lead to foo as a result of ProxyingRoutingFnFactory.
+ assert_eq!("friend", await!(test_utils::read_file(&in_dir_proxy, "data/bar/hello")));
+ assert_eq!("friend", await!(test_utils::read_file(&in_dir_proxy, "data/hippo/hello")));
+ assert_eq!("friend", await!(test_utils::read_file(&in_dir_proxy, "svc/hippo/hello")));
+ }
+
+}
diff --git a/src/sys/component_manager/src/model/error.rs b/src/sys/component_manager/src/model/error.rs
index a051b58..c5aeebd 100644
--- a/src/sys/component_manager/src/model/error.rs
+++ b/src/sys/component_manager/src/model/error.rs
@@ -56,6 +56,8 @@
#[fail(cause)]
err: Error,
},
+ #[fail(display = "add entry error")]
+ AddEntryError { moniker: AbsoluteMoniker, entry_name: String },
}
impl ModelError {
@@ -86,6 +88,10 @@
pub fn capability_discovery_error(err: impl Into<Error>) -> ModelError {
ModelError::CapabilityDiscoveryError { err: err.into() }
}
+
+ pub fn add_entry_error(moniker: AbsoluteMoniker, entry_name: impl Into<String>) -> ModelError {
+ ModelError::AddEntryError { moniker, entry_name: entry_name.into() }
+ }
}
impl From<HubError> for ModelError {
diff --git a/src/sys/component_manager/src/model/hub.rs b/src/sys/component_manager/src/model/hub.rs
index 6428331..d4e8f57 100644
--- a/src/sys/component_manager/src/model/hub.rs
+++ b/src/sys/component_manager/src/model/hub.rs
@@ -179,6 +179,7 @@
&'a self,
realm: Arc<model::Realm>,
realm_state: &'a model::RealmState,
+ route_fn_factory: model::RoutingFnFactory,
) -> Result<(), ModelError> {
let component_url = realm.component_url.clone();
let abs_moniker = realm.abs_moniker.clone();
@@ -216,6 +217,16 @@
HubError::add_directory_entry_error(abs_moniker.clone(), "resolved_url")
})?;
+ // Add an 'in' directory.
+ let decl = realm_state.decl.as_ref().expect("ComponentDecl unavailable.");
+ let tree =
+ model::DirTree::build_from_uses(route_fn_factory, &abs_moniker, decl.clone())?;
+ let mut in_dir = directory::simple::empty();
+ tree.install(&abs_moniker, &mut in_dir)?;
+ controlled
+ .add_entry("in", in_dir)
+ .map_err(|_| HubError::add_directory_entry_error(abs_moniker.clone(), "in"))?;
+
// Install the out directory if we can successfully clone it.
// TODO(fsamuel): We should probably preserve the original error messages
// instead of dropping them.
@@ -271,8 +282,9 @@
&'a self,
realm: Arc<model::Realm>,
realm_state: &'a model::RealmState,
+ route_fn_factory: model::RoutingFnFactory,
) -> BoxFuture<Result<(), ModelError>> {
- Box::pin(self.on_bind_instance_async(realm, realm_state))
+ Box::pin(self.on_bind_instance_async(realm, realm_state, route_fn_factory))
}
fn on_add_dynamic_child(&self, _realm: Arc<model::Realm>) -> BoxFuture<Result<(), ModelError>> {
@@ -286,18 +298,27 @@
use {
super::*,
crate::model::{
- self, hub::Hub, testing::mocks, testing::routing_test_helpers::default_component_decl,
+ self,
+ hub::Hub,
+ testing::mocks,
+ testing::{
+ routing_test_helpers::default_component_decl,
+ test_utils::{dir_contains, list_directory_recursive, read_file},
+ },
},
- cm_rust::{self, ChildDecl, ComponentDecl},
+ cm_rust::{
+ self, CapabilityPath, ChildDecl, ComponentDecl, UseDecl, UseDirectoryDecl,
+ UseServiceDecl,
+ },
fidl::endpoints::{ClientEnd, ServerEnd},
fidl_fuchsia_io::{
DirectoryMarker, DirectoryProxy, NodeMarker, MODE_TYPE_DIRECTORY, OPEN_RIGHT_READABLE,
OPEN_RIGHT_WRITABLE,
},
- fidl_fuchsia_sys2 as fsys, files_async,
+ fidl_fuchsia_sys2 as fsys,
fuchsia_vfs_pseudo_fs::directory::entry::DirectoryEntry,
fuchsia_zircon as zx,
- std::{iter, path::PathBuf},
+ std::{convert::TryFrom, iter, path::PathBuf},
};
/// Hosts an out directory with a 'foo' file.
@@ -355,25 +376,6 @@
})
}
- async fn read_file<'a>(root_proxy: &'a DirectoryProxy, path: &'a str) -> String {
- let file_proxy =
- io_util::open_file(&root_proxy, &PathBuf::from(path)).expect("Failed to open file.");
- let res = await!(io_util::read_file(&file_proxy));
- res.expect("Unable to read file.")
- }
-
- async fn dir_contains<'a>(
- root_proxy: &'a DirectoryProxy,
- path: &'a str,
- entry_name: &'a str,
- ) -> bool {
- let dir = io_util::open_directory(&root_proxy, &PathBuf::from(path))
- .expect("Failed to open directory");
- let entries = await!(files_async::readdir(&dir)).expect("readdir failed");
- let listing = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
- listing.contains(&String::from(entry_name))
- }
-
type DirectoryCallback = Box<Fn(ServerEnd<DirectoryMarker>) + Send + Sync>;
struct ComponentDescriptor {
@@ -383,17 +385,6 @@
pub runtime_host_fn: Option<DirectoryCallback>,
}
- impl ComponentDescriptor {
- pub fn new(
- name: &str,
- decl: ComponentDecl,
- host_fn: Option<DirectoryCallback>,
- runtime_host_fn: Option<DirectoryCallback>,
- ) -> Self {
- ComponentDescriptor { name: name.to_string(), decl, host_fn, runtime_host_fn }
- }
- }
-
async fn start_component_manager_with_hub(
root_component_url: String,
components: Vec<ComponentDescriptor>,
@@ -449,14 +440,14 @@
}
#[fuchsia_async::run_singlethreaded(test)]
- async fn run_hub_basic() {
+ async fn hub_basic() {
let root_component_url = "test:///root".to_string();
let (_model, hub_proxy) = await!(start_component_manager_with_hub(
root_component_url.clone(),
vec![
- ComponentDescriptor::new(
- "root",
- ComponentDecl {
+ ComponentDescriptor {
+ name: "root".to_string(),
+ decl: ComponentDecl {
children: vec![ChildDecl {
name: "a".to_string(),
url: "test:///a".to_string(),
@@ -464,15 +455,15 @@
}],
..default_component_decl()
},
- None,
- None
- ),
- ComponentDescriptor::new(
- "a",
- ComponentDecl { children: vec![], ..default_component_decl() },
- None,
- None
- )
+ host_fn: None,
+ runtime_host_fn: None
+ },
+ ComponentDescriptor {
+ name: "a".to_string(),
+ decl: ComponentDecl { children: vec![], ..default_component_decl() },
+ host_fn: None,
+ runtime_host_fn: None
+ }
]
));
@@ -485,13 +476,13 @@
}
#[fuchsia_async::run_singlethreaded(test)]
- async fn run_hub_out_directory() {
+ async fn hub_out_directory() {
let root_component_url = "test:///root".to_string();
let (_model, hub_proxy) = await!(start_component_manager_with_hub(
root_component_url.clone(),
- vec![ComponentDescriptor::new(
- "root",
- ComponentDecl {
+ vec![ComponentDescriptor {
+ name: "root".to_string(),
+ decl: ComponentDecl {
children: vec![ChildDecl {
name: "a".to_string(),
url: "test:///a".to_string(),
@@ -499,9 +490,9 @@
}],
..default_component_decl()
},
- Some(foo_out_dir_fn()),
- None
- )]
+ host_fn: Some(foo_out_dir_fn()),
+ runtime_host_fn: None
+ }]
));
assert!(await!(dir_contains(&hub_proxy, "self/exec", "out")));
@@ -512,13 +503,13 @@
}
#[fuchsia_async::run_singlethreaded(test)]
- async fn run_hub_runtime_directory() {
+ async fn hub_runtime_directory() {
let root_component_url = "test:///root".to_string();
let (_model, hub_proxy) = await!(start_component_manager_with_hub(
root_component_url.clone(),
- vec![ComponentDescriptor::new(
- "root",
- ComponentDecl {
+ vec![ComponentDescriptor {
+ name: "root".to_string(),
+ decl: ComponentDecl {
children: vec![ChildDecl {
name: "a".to_string(),
url: "test:///a".to_string(),
@@ -526,11 +517,53 @@
}],
..default_component_decl()
},
- None,
- Some(bleep_runtime_dir_fn())
- )]
+ host_fn: None,
+ runtime_host_fn: Some(bleep_runtime_dir_fn())
+ }]
));
assert_eq!("blah", await!(read_file(&hub_proxy, "self/exec/runtime/bleep")));
}
+
+ #[fuchsia_async::run_singlethreaded(test)]
+ async fn hub_in_directory() {
+ let root_component_url = "test:///root".to_string();
+ let (_model, hub_proxy) = await!(start_component_manager_with_hub(
+ root_component_url.clone(),
+ vec![ComponentDescriptor {
+ name: "root".to_string(),
+ decl: ComponentDecl {
+ children: vec![ChildDecl {
+ name: "a".to_string(),
+ url: "test:///a".to_string(),
+ startup: fsys::StartupMode::Lazy,
+ }],
+ uses: vec![
+ UseDecl::Directory(UseDirectoryDecl {
+ source_path: CapabilityPath::try_from("/data/baz").unwrap(),
+ target_path: CapabilityPath::try_from("/data/hippo").unwrap(),
+ }),
+ UseDecl::Service(UseServiceDecl {
+ source_path: CapabilityPath::try_from("/svc/baz").unwrap(),
+ target_path: CapabilityPath::try_from("/svc/hippo").unwrap(),
+ }),
+ UseDecl::Directory(UseDirectoryDecl {
+ source_path: CapabilityPath::try_from("/data/foo").unwrap(),
+ target_path: CapabilityPath::try_from("/data/bar").unwrap(),
+ }),
+ ],
+ ..default_component_decl()
+ },
+ host_fn: None,
+ runtime_host_fn: None,
+ }]
+ ));
+
+ let in_dir = io_util::open_directory(&hub_proxy, &PathBuf::from("self/exec/in"))
+ .expect("Failed to open directory");
+ assert_eq!(
+ vec!["data/bar", "data/hippo", "svc/hippo"],
+ await!(list_directory_recursive(&in_dir))
+ );
+ }
}
diff --git a/src/sys/component_manager/src/model/mod.rs b/src/sys/component_manager/src/model/mod.rs
index 1f8f1fe..a7d67ca 100644
--- a/src/sys/component_manager/src/model/mod.rs
+++ b/src/sys/component_manager/src/model/mod.rs
@@ -4,6 +4,7 @@
pub mod ambient;
mod component;
+pub mod dir_tree;
pub mod error;
pub mod hub;
mod model;
@@ -11,12 +12,13 @@
mod namespace;
mod resolver;
mod routing;
+pub mod routing_fn_factory;
mod runner;
pub mod testing;
#[cfg(test)]
pub(crate) mod tests;
pub use self::{
- ambient::*, component::*, error::*, hub::*, model::*, moniker::*, namespace::*, resolver::*,
- routing::*, runner::*,
+ ambient::*, component::*, dir_tree::*, error::*, hub::*, model::*, moniker::*, namespace::*,
+ resolver::*, routing::*, routing_fn_factory::*, runner::*,
};
diff --git a/src/sys/component_manager/src/model/model.rs b/src/sys/component_manager/src/model/model.rs
index 10380f3..1c7a263 100644
--- a/src/sys/component_manager/src/model/model.rs
+++ b/src/sys/component_manager/src/model/model.rs
@@ -29,6 +29,7 @@
&'a self,
realm: Arc<Realm>,
realm_state: &'a RealmState,
+ route_fn_factory: Arc<dyn CapabilityRoutingFnFactory + Send + Sync>,
) -> BoxFuture<Result<(), ModelError>>;
// Called when a dynamic instance is added with `realm`.
@@ -217,8 +218,9 @@
async fn bind_instance<'a>(&'a self, realm: Arc<Realm>) -> Result<Vec<Arc<Realm>>, ModelError> {
let mut state = await!(realm.state.lock());
let eager_children = await!(self.populate_realm_state(&mut *state, realm.clone()))?;
+ let route_fn_factory = Arc::new(ModelCapabilityRoutingFnFactory::new(&self));
for hook in self.hooks.iter() {
- await!(hook.on_bind_instance(realm.clone(), &*state))?;
+ await!(hook.on_bind_instance(realm.clone(), &*state, route_fn_factory.clone()))?;
}
Ok(eager_children)
diff --git a/src/sys/component_manager/src/model/routing_fn_factory.rs b/src/sys/component_manager/src/model/routing_fn_factory.rs
new file mode 100644
index 0000000..12c3f82
--- /dev/null
+++ b/src/sys/component_manager/src/model/routing_fn_factory.rs
@@ -0,0 +1,65 @@
+// 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::{
+ directory_broker::RoutingFn,
+ model::{moniker::AbsoluteMoniker, routing::*, Model},
+ },
+ cm_rust::Capability,
+ fidl::endpoints::ServerEnd,
+ fidl_fuchsia_io::NodeMarker,
+ fuchsia_async as fasync,
+ log::*,
+ std::sync::Arc,
+};
+
+pub trait CapabilityRoutingFnFactory {
+ fn create_route_fn(&self, abs_moniker: &AbsoluteMoniker, capability: Capability) -> RoutingFn;
+}
+
+pub type RoutingFnFactory = Arc<dyn CapabilityRoutingFnFactory + Send + Sync>;
+
+pub struct ModelCapabilityRoutingFnFactory {
+ pub model: Model,
+}
+
+impl ModelCapabilityRoutingFnFactory {
+ pub fn new(model: &Model) -> Self {
+ ModelCapabilityRoutingFnFactory { model: model.clone() }
+ }
+}
+
+impl CapabilityRoutingFnFactory for ModelCapabilityRoutingFnFactory {
+ fn create_route_fn(&self, abs_moniker: &AbsoluteMoniker, capability: Capability) -> RoutingFn {
+ let abs_moniker = abs_moniker.clone();
+ let model = self.model.clone();
+ Box::new(
+ move |_flags: u32,
+ _mode: u32,
+ _relative_path: String,
+ server_end: ServerEnd<NodeMarker>| {
+ let model = model.clone();
+ let abs_moniker = abs_moniker.clone();
+ let capability = capability.clone();
+ fasync::spawn(async move {
+ // `route_service` is used for directories as well. The directory capability
+ // is modeled as a service node of the containing directory.
+ // TODO(fsamuel): we might want to get rid of `route_directory` and
+ // `route_service`; instead, we can expose `route_use_capability` and have the
+ // caller be responsible for passing in the right mode.
+ let res = await!(route_service(
+ &model,
+ &capability,
+ abs_moniker.clone(),
+ server_end.into_channel()
+ ));
+ if let Err(e) = res {
+ error!("failed to route service for exposed dir {}: {:?}", abs_moniker, e);
+ }
+ });
+ },
+ )
+ }
+}
diff --git a/src/sys/component_manager/src/model/testing/mocks.rs b/src/sys/component_manager/src/model/testing/mocks.rs
index 4ef4d05..e3d2bcf 100644
--- a/src/sys/component_manager/src/model/testing/mocks.rs
+++ b/src/sys/component_manager/src/model/testing/mocks.rs
@@ -4,17 +4,60 @@
use {
crate::model::*,
- cm_rust::ComponentDecl,
+ cm_rust::{Capability, ComponentDecl},
failure::{format_err, Error},
- fidl::endpoints::ServerEnd,
- fidl_fuchsia_io::DirectoryMarker,
- fidl_fuchsia_sys2 as fsys,
+ fidl::endpoints::{ClientEnd, ServerEnd},
+ fidl_fuchsia_io::{
+ DirectoryMarker, DirectoryProxy, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
+ },
+ fidl_fuchsia_sys2 as fsys, fuchsia_zircon as zx,
futures::future::FutureObj,
futures::lock::Mutex,
futures::prelude::*,
std::{collections::HashMap, convert::TryFrom, sync::Arc},
};
+pub struct ProxyingRoutingFnFactory {
+ pub root_dir: DirectoryProxy,
+}
+
+impl ProxyingRoutingFnFactory {
+ pub fn new(root_dir: DirectoryProxy) -> Self {
+ ProxyingRoutingFnFactory { root_dir }
+ }
+}
+
+impl CapabilityRoutingFnFactory for ProxyingRoutingFnFactory {
+ fn create_route_fn(
+ &self,
+ _abs_moniker: &AbsoluteMoniker,
+ _capability: Capability,
+ ) -> Box<FnMut(u32, u32, String, ServerEnd<NodeMarker>) + Send> {
+ // Create a DirectoryProxy for use by every route function we stamp out.
+ let (client_chan, server_chan) = zx::Channel::create().unwrap();
+ let flags = OPEN_RIGHT_READABLE | OPEN_RIGHT_WRITABLE;
+ self.root_dir
+ .clone(flags, ServerEnd::<NodeMarker>::new(server_chan.into()))
+ .expect("Unable to clone root directory.");
+ let dir = ClientEnd::<DirectoryMarker>::new(client_chan)
+ .into_proxy()
+ .expect("failed to create directory proxy");
+ Box::new(
+ move |flags: u32,
+ mode: u32,
+ relative_path: String,
+ server_end: ServerEnd<NodeMarker>| {
+ if !relative_path.is_empty() {
+ dir.open(flags, mode, &relative_path, server_end)
+ .expect("Unable to open 'dir'.");
+ } else {
+ dir.clone(flags, server_end).expect("Unable to clone 'dir'.");
+ }
+ },
+ )
+ }
+}
+
pub struct MockResolver {
components: HashMap<String, ComponentDecl>,
}
diff --git a/src/sys/component_manager/src/model/testing/mod.rs b/src/sys/component_manager/src/model/testing/mod.rs
index 68e3b56..fe7660b 100644
--- a/src/sys/component_manager/src/model/testing/mod.rs
+++ b/src/sys/component_manager/src/model/testing/mod.rs
@@ -5,3 +5,4 @@
pub mod mocks;
pub mod routing_test_helpers;
pub mod test_hook;
+pub mod test_utils;
diff --git a/src/sys/component_manager/src/model/testing/test_hook.rs b/src/sys/component_manager/src/model/testing/test_hook.rs
index 77421fc..e26afd4 100644
--- a/src/sys/component_manager/src/model/testing/test_hook.rs
+++ b/src/sys/component_manager/src/model/testing/test_hook.rs
@@ -103,6 +103,7 @@
&'a self,
realm: Arc<Realm>,
realm_state: &'a RealmState,
+ _route_fn_factory: RoutingFnFactory,
) -> Result<(), ModelError> {
await!(self.create_instance_if_necessary(realm.abs_moniker.clone()))?;
for child_realm in
@@ -150,8 +151,9 @@
&'a self,
realm: Arc<Realm>,
realm_state: &'a RealmState,
+ route_fn_factory: RoutingFnFactory,
) -> BoxFuture<Result<(), ModelError>> {
- Box::pin(self.on_bind_instance_async(realm, &realm_state))
+ Box::pin(self.on_bind_instance_async(realm, &realm_state, route_fn_factory))
}
fn on_add_dynamic_child(&self, realm: Arc<Realm>) -> BoxFuture<Result<(), ModelError>> {
diff --git a/src/sys/component_manager/src/model/testing/test_utils.rs b/src/sys/component_manager/src/model/testing/test_utils.rs
new file mode 100644
index 0000000..3f849cc
--- /dev/null
+++ b/src/sys/component_manager/src/model/testing/test_utils.rs
@@ -0,0 +1,39 @@
+// 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 {fidl_fuchsia_io::DirectoryProxy, std::path::PathBuf};
+
+pub async fn dir_contains<'a>(
+ root_proxy: &'a DirectoryProxy,
+ path: &'a str,
+ entry_name: &'a str,
+) -> bool {
+ let dir = io_util::open_directory(&root_proxy, &PathBuf::from(path))
+ .expect("Failed to open directory");
+ let entries = await!(files_async::readdir(&dir)).expect("readdir failed");
+ let listing = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
+ listing.contains(&String::from(entry_name))
+}
+
+pub async fn list_directory<'a>(root_proxy: &'a DirectoryProxy) -> Vec<String> {
+ let entries = await!(files_async::readdir(&root_proxy)).expect("readdir failed");
+ let mut items = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
+ items.sort();
+ items
+}
+
+pub async fn list_directory_recursive<'a>(root_proxy: &'a DirectoryProxy) -> Vec<String> {
+ let dir = io_util::clone_directory(&root_proxy).expect("Failed to clone DirectoryProxy");
+ let entries = await!(files_async::readdir_recursive(dir)).expect("readdir failed");
+ let mut items = entries.iter().map(|entry| entry.name.clone()).collect::<Vec<String>>();
+ items.sort();
+ items
+}
+
+pub async fn read_file<'a>(root_proxy: &'a DirectoryProxy, path: &'a str) -> String {
+ let file_proxy =
+ io_util::open_file(&root_proxy, &PathBuf::from(path)).expect("Failed to open file.");
+ let res = await!(io_util::read_file(&file_proxy));
+ res.expect("Unable to read file.")
+}
diff --git a/src/sys/component_manager/tests/BUILD.gn b/src/sys/component_manager/tests/BUILD.gn
index 973622f..a2b4724 100644
--- a/src/sys/component_manager/tests/BUILD.gn
+++ b/src/sys/component_manager/tests/BUILD.gn
@@ -11,6 +11,7 @@
edition = "2018"
source_root = "hub_integration_test.rs"
deps = [
+ "//examples/components/routing/fidl:echo-rustc",
"//garnet/lib/rust/cm_fidl_translator",
"//garnet/lib/rust/cm_rust",
"//garnet/lib/rust/io_util",
@@ -37,7 +38,8 @@
deps = [
":hub_integration_test_bin",
":mock_pkg_resolver_bin",
- "//examples/components/basic:echo_args_bin",
+ "//examples/components/routing/echo_client",
+ "//examples/components/routing/echo_server",
]
meta = [
@@ -46,8 +48,16 @@
dest = "mock_pkg_resolver.cmx"
},
{
- path = rebase_path("//examples/components/basic/meta/echo_args.cml")
- dest = "echo_args.cm"
+ path = rebase_path("//examples/components/routing/meta/echo_server.cml")
+ dest = "echo_server.cm"
+ },
+ {
+ path = rebase_path("//examples/components/routing/meta/echo_client.cml")
+ dest = "echo_client.cm"
+ },
+ {
+ path = rebase_path("meta/echo_realm_for_hub_integration.cml")
+ dest = "echo_realm.cm"
},
]
@@ -56,7 +66,10 @@
name = "mock_pkg_resolver"
},
{
- name = "echo_args"
+ name = "echo_server"
+ },
+ {
+ name = "echo_client"
},
]
diff --git a/src/sys/component_manager/tests/hub_integration_test.rs b/src/sys/component_manager/tests/hub_integration_test.rs
index dab32a2..529f993 100644
--- a/src/sys/component_manager/tests/hub_integration_test.rs
+++ b/src/sys/component_manager/tests/hub_integration_test.rs
@@ -8,26 +8,20 @@
component_manager_lib::{
ambient::RealAmbientEnvironment,
elf_runner::{ElfRunner, ProcessLauncherConnector},
- model::{self, Hub, Model, ModelParams},
+ model::{self, Hub, Model, ModelParams, testing::test_utils::{list_directory, read_file}},
startup,
},
failure::{self, Error},
fidl::endpoints::{ClientEnd, ServerEnd},
+ fidl_fidl_examples_routing_echo as fecho,
fidl_fuchsia_io::{
- DirectoryMarker, DirectoryProxy, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
+ DirectoryMarker, MODE_TYPE_SERVICE, NodeMarker, OPEN_RIGHT_READABLE, OPEN_RIGHT_WRITABLE,
},
fuchsia_vfs_pseudo_fs::directory::{self, entry::DirectoryEntry},
fuchsia_zircon as zx,
- std::{iter, path::PathBuf, sync::Arc, vec::Vec},
+ std::{iter, sync::Arc, vec::Vec, path::PathBuf},
};
-async fn read_file<'a>(root_proxy: &'a DirectoryProxy, path: &'a str) -> String {
- let file_proxy =
- io_util::open_file(&root_proxy, &PathBuf::from(path)).expect("Failed to open file.");
- let res = await!(io_util::read_file(&file_proxy));
- res.expect("Unable to read file.")
-}
-
#[fuchsia_async::run_singlethreaded]
async fn main() -> Result<(), Error> {
let args = startup::Arguments { use_builtin_process_launcher: false, ..Default::default() };
@@ -36,7 +30,7 @@
let runner = ElfRunner::new(launcher_connector);
let resolver_registry = startup::available_resolvers()?;
let root_component_url =
- "fuchsia-pkg://fuchsia.com/hub_integration_test#meta/echo_args.cm".to_string();
+ "fuchsia-pkg://fuchsia.com/hub_integration_test#meta/echo_realm.cm".to_string();
let (client_chan, server_chan) = zx::Channel::create().unwrap();
let mut root_directory = directory::simple::empty();
@@ -68,9 +62,29 @@
.into_proxy()
.expect("failed to create directory proxy");
- // These args are from echo_args.cml.
- assert_eq!("Hippos", await!(read_file(&hub_proxy, "self/exec/runtime/args/0")));
- assert_eq!("rule!", await!(read_file(&hub_proxy, "self/exec/runtime/args/1")));
+ // Verify that echo_realm has two children.
+ let children_dir_proxy = io_util::open_directory(&hub_proxy, &PathBuf::from("self/children"))
+ .expect("Failed to open directory");
+ assert_eq!(vec!["echo_client", "echo_server"], await!(list_directory(&children_dir_proxy)));
+
+ // These args are from echo_client.cml.
+ assert_eq!("Hippos", await!(read_file(&hub_proxy, "self/children/echo_client/exec/runtime/args/0")));
+ assert_eq!("rule!", await!(read_file(&hub_proxy, "self/children/echo_client/exec/runtime/args/1")));
+
+ let svc_dir = "self/children/echo_client/exec/in/svc";
+ let svc_dir_proxy = io_util::open_directory(&hub_proxy, &PathBuf::from(svc_dir))
+ .expect("Failed to open directory");
+ let echo_service_name = "fidl.examples.routing.echo.Echo";
+ assert_eq!(vec![echo_service_name], await!(list_directory(&svc_dir_proxy)));
+
+ // Verify that we can connect to the echo service from the hub.
+ let echo_service = format!("{}/{}", svc_dir, echo_service_name);
+ let node_proxy =
+ io_util::open_node(&hub_proxy, &PathBuf::from(echo_service), MODE_TYPE_SERVICE)
+ .expect("failed to open echo service");
+ let echo_proxy = fecho::EchoProxy::new(node_proxy.into_channel().unwrap());
+ let res = await!(echo_proxy.echo_string(Some("hippos")));
+ assert_eq!(res.expect("failed to use echo service"), Some("hippos".to_string()));
Ok(())
}
diff --git a/src/sys/component_manager/tests/meta/echo_realm_for_hub_integration.cml b/src/sys/component_manager/tests/meta/echo_realm_for_hub_integration.cml
new file mode 100644
index 0000000..9edc121
--- /dev/null
+++ b/src/sys/component_manager/tests/meta/echo_realm_for_hub_integration.cml
@@ -0,0 +1,30 @@
+// Realm for integration test that provisions an Echo client and service and eagerly runs the
+// client. We don't use the routing examples's echo_realm.cml because we need the component URLs
+// to refer to the test package.
+{
+ // Route Echo service from server to client.
+ "offer": [
+ {
+ "service": "/svc/fidl.examples.routing.echo.Echo",
+ "from": "#echo_server",
+ "to": [
+ {
+ "dest": "#echo_client",
+ },
+ ],
+ },
+ ],
+ // Two children: a server and client. "echo_client" has "eager" startup so it
+ // will be started along with the realm.
+ "children": [
+ {
+ "name": "echo_server",
+ "url": "fuchsia-pkg://fuchsia.com/hub_integration_test#meta/echo_server.cm",
+ },
+ {
+ "name": "echo_client",
+ "url": "fuchsia-pkg://fuchsia.com/hub_integration_test#meta/echo_client.cm",
+ "startup": "eager",
+ },
+ ],
+}