| // Copyright 2020 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| //! Component that serves the `fuchsia.element.Manager` protocol. |
| //! |
| //! Elements launched through the `Manager` protocol are created in a collection as |
| //! children in of this component. |
| //! |
| //! # Lifecycle |
| //! |
| //! If a client closes its connection to `Manager`, any running elements that it |
| //! has proposed without an associated `fuchsia.element.Controller` will continue to run. |
| |
| use { |
| anyhow::Error, |
| element_management::{ElementManager, SimpleElementManager}, |
| fidl_fuchsia_element as felement, fidl_fuchsia_sys2 as fsys2, fuchsia_async as fasync, |
| fuchsia_component::{client::connect_to_service, server::ServiceFs}, |
| futures::StreamExt, |
| std::cell::RefCell, |
| }; |
| |
| /// This enum allows the session to match on incoming messages. |
| enum ExposedServices { |
| Manager(felement::ManagerRequestStream), |
| } |
| |
| /// The child collection to add elements to. This must match a collection name declared in |
| /// element_manager's CML file. |
| const ELEMENT_COLLECTION_NAME: &str = "elements"; |
| |
| /// The maximum number of concurrent requests. |
| const NUM_CONCURRENT_REQUESTS: usize = 5; |
| |
| #[fasync::run_singlethreaded] |
| async fn main() -> Result<(), Error> { |
| fuchsia_syslog::init_with_tags(&["element_manager"]).expect("Failed to initialize logger"); |
| |
| let realm = |
| connect_to_service::<fsys2::RealmMarker>().expect("Failed to connect to Realm service"); |
| let element_manager = RefCell::new(SimpleElementManager::new(realm, ELEMENT_COLLECTION_NAME)); |
| |
| let mut fs = ServiceFs::new_local(); |
| fs.dir("svc").add_fidl_service(ExposedServices::Manager); |
| |
| fs.take_and_serve_directory_handle()?; |
| |
| fs.for_each_concurrent(NUM_CONCURRENT_REQUESTS, |service_request: ExposedServices| async { |
| match service_request { |
| ExposedServices::Manager(request_stream) => { |
| element_manager |
| .borrow_mut() |
| .handle_requests(request_stream) |
| .await |
| .expect("Failed to handle element manager requests"); |
| } |
| } |
| }) |
| .await; |
| Ok(()) |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::ELEMENT_COLLECTION_NAME, |
| element_management::{ElementManager, SimpleElementManager}, |
| fidl::endpoints::create_proxy_and_stream, |
| fidl_fuchsia_element as felement, fidl_fuchsia_sys2 as fsys2, fuchsia_async as fasync, |
| futures::TryStreamExt, |
| lazy_static::lazy_static, |
| test_util::Counter, |
| }; |
| |
| /// Spawns a local `fidl_fuchsia_sys2::Realm` server, and returns a proxy to the spawned server. |
| /// The provided `request_handler` is notified when an incoming request is received. |
| /// |
| /// # Parameters |
| /// - `request_handler`: A function that is called with incoming requests to the spawned |
| /// `Realm` server. |
| /// # Returns |
| /// A `RealmProxy` to the spawned server. |
| fn spawn_realm_server<F: 'static>(request_handler: F) -> fsys2::RealmProxy |
| where |
| F: Fn(fsys2::RealmRequest) + Send, |
| { |
| let (realm_proxy, mut realm_stream) = create_proxy_and_stream::<fsys2::RealmMarker>() |
| .expect("Failed to create Realm proxy and stream."); |
| |
| fasync::Task::spawn(async move { |
| while let Some(realm_request) = realm_stream.try_next().await.unwrap() { |
| request_handler(realm_request); |
| } |
| }) |
| .detach(); |
| |
| realm_proxy |
| } |
| |
| /// Spawns a local `Manager` server. |
| /// |
| /// # Parameters |
| /// - `element_manager`: The `ElementManager` that Manager uses to launch elements. |
| /// |
| /// # Returns |
| /// A `ManagerProxy` to the spawned server. |
| fn spawn_manager_server( |
| mut element_manager: Box<dyn ElementManager + Send + Sync>, |
| ) -> felement::ManagerProxy { |
| let (proxy, stream) = create_proxy_and_stream::<felement::ManagerMarker>() |
| .expect("Failed to create Manager proxy and stream"); |
| |
| fasync::Task::spawn(async move { |
| element_manager |
| .handle_requests(stream) |
| .await |
| .expect("Failed to handle element manager requests"); |
| }) |
| .detach(); |
| |
| proxy |
| } |
| |
| /// Tests that ProposeElement launches the element as a child in a realm. |
| #[fasync::run_until_stalled(test)] |
| async fn propose_element_launches_element() { |
| lazy_static! { |
| static ref CREATE_CHILD_CALL_COUNT: Counter = Counter::new(0); |
| static ref BIND_CHILD_CALL_COUNT: Counter = Counter::new(0); |
| } |
| |
| let component_url = "fuchsia-pkg://fuchsia.com/simple_element#meta/simple_element.cm"; |
| |
| let realm = spawn_realm_server(move |realm_request| match realm_request { |
| fsys2::RealmRequest::CreateChild { collection: _, decl, responder } => { |
| assert_eq!(decl.url.unwrap(), component_url); |
| CREATE_CHILD_CALL_COUNT.inc(); |
| let _ = responder.send(&mut Ok(())); |
| } |
| fsys2::RealmRequest::BindChild { child: _, exposed_dir: _, responder } => { |
| BIND_CHILD_CALL_COUNT.inc(); |
| let _ = responder.send(&mut Ok(())); |
| } |
| _ => { |
| assert!(false); |
| } |
| }); |
| |
| let element_manager = Box::new(SimpleElementManager::new(realm, ELEMENT_COLLECTION_NAME)); |
| let manager_proxy = spawn_manager_server(element_manager); |
| |
| let result = manager_proxy |
| .propose_element( |
| felement::Spec { |
| component_url: Some(component_url.to_string()), |
| ..felement::Spec::EMPTY |
| }, |
| None, |
| ) |
| .await; |
| assert!(result.is_ok()); |
| |
| assert_eq!(CREATE_CHILD_CALL_COUNT.get(), 1); |
| assert_eq!(BIND_CHILD_CALL_COUNT.get(), 1); |
| } |
| } |