blob: f22939a05c0a3881a2dfdd18466009bb7e3b3d74 [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.
//! The `ElementManagement` library provides utilities for Sessions to service
//! incoming [`fidl_fuchsia_session::ElementManagerRequest`]s.
//!
//! Elements are instantiated as dynamic component instances in a component collection of the
//! calling component.
use {
async_trait::async_trait, failure::Fail, fidl_fuchsia_session::ElementSpec,
fidl_fuchsia_sys2 as fsys, realm_management,
};
/// Errors returned by calls to [`ElementManager`].
#[derive(Debug, Fail, Clone, PartialEq)]
pub enum ElementManagerError {
/// Returned when the element manager fails to created the component instance associated with
/// a given element.
#[fail(display = "Element spec for \"{}/{}\" missing url.", name, collection)]
UrlMissing { name: String, collection: String },
/// Returned when the element manager fails to created the component instance associated with
/// a given element.
#[fail(display = "Element {} not created at \"{}/{}\": {:?}", url, collection, name, err)]
NotCreated { name: String, collection: String, url: String, err: fsys::Error },
/// Returned when the element manager fails to bind to the component instance associated with
/// a given element.
#[fail(display = "Element {} not bound at \"{}/{}\": {:?}", url, collection, name, err)]
NotBound { name: String, collection: String, url: String, err: fsys::Error },
}
impl ElementManagerError {
pub fn url_missing(
name: impl Into<String>,
collection: impl Into<String>,
) -> ElementManagerError {
ElementManagerError::UrlMissing { name: name.into(), collection: collection.into() }
}
pub fn not_created(
name: impl Into<String>,
collection: impl Into<String>,
url: impl Into<String>,
err: impl Into<fsys::Error>,
) -> ElementManagerError {
ElementManagerError::NotCreated {
name: name.into(),
collection: collection.into(),
url: url.into(),
err: err.into(),
}
}
pub fn not_bound(
name: impl Into<String>,
collection: impl Into<String>,
url: impl Into<String>,
err: impl Into<fsys::Error>,
) -> ElementManagerError {
ElementManagerError::NotBound {
name: name.into(),
collection: collection.into(),
url: url.into(),
err: err.into(),
}
}
}
/// Manages the elements associated with a session.
#[async_trait]
pub trait ElementManager {
/// Adds an element to the session.
///
/// This method creates the component instance and binds to it, causing it to start running.
///
/// # Parameters
/// - `spec`: The description of the element to add as a child.
/// - `child_name`: The name of the element, must be unique within a session. The name must be
/// non-empty, of the form [a-z0-9-_.].
/// - `child_collection`: The collection to add the element in, must match a collection in the
/// calling component's CML file.
///
/// # Errors
/// If the child cannot be created or bound in the current [`fidl_fuchsia_sys2::Realm`]. In
/// particular, it is an error to call [`add_element`] twice with the same `child_name`.
async fn add_element(
&self,
spec: ElementSpec,
child_name: &str,
child_collection: &str,
) -> Result<(), ElementManagerError>;
}
/// A [`SimpleElementManager`] creates and binds elements.
///
/// The [`SimpleElementManager`] provides no additional functionality for managing elements (e.g.,
/// tracking which elements are running, de-duplicating elements, etc.).
pub struct SimpleElementManager {
/// The realm which this element manager uses to create components.
realm: fsys::RealmProxy,
}
impl SimpleElementManager {
pub fn new(realm: fsys::RealmProxy) -> SimpleElementManager {
SimpleElementManager { realm }
}
}
#[async_trait]
impl ElementManager for SimpleElementManager {
async fn add_element(
&self,
spec: ElementSpec,
child_name: &str,
child_collection: &str,
) -> Result<(), ElementManagerError> {
let child_url = spec
.component_url
.ok_or_else(|| ElementManagerError::url_missing(child_name, child_collection))?;
realm_management::create_child(&child_name, &child_url, child_collection, &self.realm)
.await
.map_err(|err: fsys::Error| {
ElementManagerError::not_created(child_name, child_collection, &child_url, err)
})?;
realm_management::bind_child(&child_name, child_collection, &self.realm).await.map_err(
|err: fsys::Error| {
ElementManagerError::not_bound(child_name, child_collection, &child_url, err)
},
)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use {
super::{ElementManager, ElementManagerError, SimpleElementManager},
fidl::endpoints::create_proxy_and_stream,
fidl_fuchsia_session::ElementSpec,
fidl_fuchsia_sys2 as fsys, fuchsia_async as fasync,
futures::prelude::*,
};
/// 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 which 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) -> fsys::RealmProxy
where
F: Fn(fsys::RealmRequest) + Send,
{
let (realm_proxy, mut realm_server) = create_proxy_and_stream::<fsys::RealmMarker>()
.expect("Failed to create realm proxy and server.");
fasync::spawn(async move {
while let Some(realm_request) = realm_server.try_next().await.unwrap() {
request_handler(realm_request);
}
});
realm_proxy
}
/// Tests that adding an element successfully returns [`Ok`].
#[fasync::run_singlethreaded(test)]
async fn add_element_success() {
let component_url = "test_url";
let child_name = "child";
let child_collection = "elements";
let realm = spawn_realm_server(move |realm_request| match realm_request {
fsys::RealmRequest::CreateChild { collection, decl, responder } => {
assert_eq!(decl.url.unwrap(), component_url);
assert_eq!(decl.name.unwrap(), child_name);
assert_eq!(&collection.name, child_collection);
let _ = responder.send(&mut Ok(()));
}
fsys::RealmRequest::BindChild { child, exposed_dir: _, responder } => {
assert_eq!(child.collection, Some(child_collection.to_string()));
let _ = responder.send(&mut Ok(()));
}
_ => {
assert!(false);
}
});
let element_manager = SimpleElementManager::new(realm);
assert!(element_manager
.add_element(
ElementSpec { component_url: Some(component_url.to_string()) },
child_name,
child_collection,
)
.await
.is_ok());
}
/// Tests that adding an element with no URL returns the appropriate error.
#[fasync::run_singlethreaded(test)]
async fn add_element_no_url() {
// The following match errors if it sees a bind request: since the child was not created
// successfully the bind should not be called.
let realm = spawn_realm_server(move |realm_request| match realm_request {
_ => {
assert!(false);
}
});
let element_manager = SimpleElementManager::new(realm);
assert_eq!(
element_manager.add_element(ElementSpec { component_url: None }, "", "").await,
Err(ElementManagerError::url_missing("", ""))
);
}
/// Tests that adding an element which is not successfully created in the realm returns an
/// appropriate error.
#[fasync::run_singlethreaded(test)]
async fn add_element_create_error_internal() {
let component_url = "test_url";
// The following match errors if it sees a bind request: since the child was not created
// successfully the bind should not be called.
let realm = spawn_realm_server(move |realm_request| match realm_request {
fsys::RealmRequest::CreateChild { collection: _, decl: _, responder } => {
let _ = responder.send(&mut Err(fsys::Error::Internal));
}
_ => {
assert!(false);
}
});
let element_manager = SimpleElementManager::new(realm);
assert_eq!(
element_manager
.add_element(
ElementSpec { component_url: Some(component_url.to_string()) },
"",
"",
)
.await,
Err(ElementManagerError::not_created("", "", component_url, fsys::Error::Internal))
);
}
/// Tests that adding an element which is not successfully created in the realm returns an
/// appropriate error.
#[fasync::run_singlethreaded(test)]
async fn add_element_create_error_no_space() {
let component_url = "test_url";
// The following match errors if it sees a bind request: since the child was not created
// successfully the bind should not be called.
let realm = spawn_realm_server(move |realm_request| match realm_request {
fsys::RealmRequest::CreateChild { collection: _, decl: _, responder } => {
let _ = responder.send(&mut Err(fsys::Error::NoSpace));
}
_ => {
assert!(false);
}
});
let element_manager = SimpleElementManager::new(realm);
assert_eq!(
element_manager
.add_element(
ElementSpec { component_url: Some(component_url.to_string()) },
"",
"",
)
.await,
Err(ElementManagerError::not_created("", "", component_url, fsys::Error::NoSpace))
);
}
/// Tests that adding an element which is not successfully bound in the realm returns an
/// appropriate error.
#[fasync::run_singlethreaded(test)]
async fn add_element_bind_error() {
let component_url = "test_url";
// The following match errors if it sees a bind request: since the child was not created
// successfully the bind should not be called.
let realm = spawn_realm_server(move |realm_request| match realm_request {
fsys::RealmRequest::CreateChild { collection: _, decl: _, responder } => {
let _ = responder.send(&mut Ok(()));
}
fsys::RealmRequest::BindChild { child: _, exposed_dir: _, responder } => {
let _ = responder.send(&mut Err(fsys::Error::InstanceCannotStart));
}
_ => {
assert!(false);
}
});
let element_manager = SimpleElementManager::new(realm);
assert_eq!(
element_manager
.add_element(
ElementSpec { component_url: Some(component_url.to_string()) },
"",
"",
)
.await,
Err(ElementManagerError::not_bound(
"",
"",
component_url,
fsys::Error::InstanceCannotStart
))
);
}
}