blob: ea1d252239392953605940267763f9074ad1b8c6 [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.
mod moniker;
mod resolver;
mod runner;
pub use self::{moniker::*, resolver::*, runner::*};
use {
crate::data,
fidl_fuchsia_sys2 as fsys,
futures::lock::Mutex,
std::{cell::RefCell, error, fmt, rc::Rc},
};
/// Parameters for initializing a component model, particularly the root of the component
/// instance tree.
pub struct ModelParams {
/// The URI of the root component.
pub root_component_uri: String,
/// The component resolver registry used in the root realm.
/// In particular, it will be used to resolve the root component itself.
pub root_resolver_registry: ResolverRegistry,
/// The default runner used in the root realm (nominally runs ELF binaries).
pub root_default_runner: Box<dyn Runner>,
}
/// The component model holds authoritative state about a tree of component instances, including
/// each instance's identity, lifecycle, capabilities, and topological relationships. It also
/// provides operations for instantiating, destroying, querying, and controlling component
/// instances at runtime.
///
/// To facilitate unit testing, the component model does not directly perform IPC. Instead, it
/// delegates external interfacing concerns to other objects that implement traits such as
/// `Runner` and `Resolver`.
pub struct Model {
root_realm: Rc<RefCell<Realm>>,
}
/// A realm is a container for an individual component instance and its children. It is provided
/// by the parent of the instance or by the component manager itself in the case of the root realm.
///
/// The realm's properties influence the runtime behavior of the subtree of component instances
/// that it contains, including component resolution, execution, and service discovery.
struct Realm {
/// The registry for resolving component URIs within the realm.
resolver_registry: Rc<ResolverRegistry>,
/// The default runner (nominally runs ELF binaries) for executing components
/// within the realm that do not explicitly specify a runner.
default_runner: Rc<Box<dyn Runner>>,
/// The component that has been instantiated within the realm.
instance: Instance,
}
/// An instance of a component.
/// TODO: Describe child instances (map of child moniker to realm).
struct Instance {
/// The component's URI.
/// The URI is only meaningful
component_uri: String,
/// Execution state for the component instance or `None` if not running.
execution: Mutex<Option<Execution>>,
}
/// The execution state for a component instance that has started running.
// TODO: Hold the component instance's controller.
struct Execution {
component: fsys::Component,
}
impl Model {
/// Creates a new component model and initializes its topology.
pub fn new(params: ModelParams) -> Model {
Model {
root_realm: Rc::new(RefCell::new(Realm {
resolver_registry: Rc::new(params.root_resolver_registry),
default_runner: Rc::new(params.root_default_runner),
instance: Instance {
component_uri: params.root_component_uri,
execution: Mutex::new(None),
},
})),
}
}
/// Binds to the component instance with the specified moniker, causing it to start if it is
/// not already running.
pub async fn bind_instance(&self, moniker: AbsoluteMoniker) -> Result<(), ModelError> {
// TODO: Use moniker to locate non-root component instances.
if moniker.is_root() {
await!(self.bind_instance_in_realm(self.root_realm.clone()))
} else {
Err(ModelError::InstanceNotFound)
}
}
async fn bind_instance_in_realm(
&self, realm_cell: Rc<RefCell<Realm>>,
) -> Result<(), ModelError> {
// There can only be one task manipulating an instance's execution at a time.
let realm = realm_cell.borrow();
let mut execution_lock = await!(realm.instance.execution.lock());
match &*execution_lock {
Some(_) => Ok(()),
None => {
let component = await!(realm
.resolver_registry
.resolve(&realm.instance.component_uri))?;
let start_info = fsys::ComponentStartInfo {
resolved_uri: component.resolved_uri.clone(),
program: component
.decl
.as_ref()
.and_then(|x| data::clone_option_dictionary(&x.program)),
ns: None, // TODO: Populate the component's namespace
};
await!(realm.default_runner.start(start_info))?;
*execution_lock = Some(Execution { component });
Ok(())
}
}
}
}
/// Errors produced by `Model`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ModelError {
InstanceNotFound,
ResolverError(ResolverError),
RunnerError(RunnerError),
}
impl fmt::Display for ModelError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ModelError::InstanceNotFound => write!(f, "component instance not found"),
ModelError::ResolverError(err) => err.fmt(f),
ModelError::RunnerError(err) => err.fmt(f),
}
}
}
impl error::Error for ModelError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
ModelError::InstanceNotFound => None,
ModelError::ResolverError(err) => err.source(),
ModelError::RunnerError(err) => err.source(),
}
}
}
impl From<ResolverError> for ModelError {
fn from(err: ResolverError) -> Self {
ModelError::ResolverError(err)
}
}
impl From<RunnerError> for ModelError {
fn from(err: RunnerError) -> Self {
ModelError::RunnerError(err)
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::future::{self, FutureObj};
struct MockResolver {}
impl Resolver for MockResolver {
fn resolve(
&self, component_uri: &str,
) -> FutureObj<Result<fsys::Component, ResolverError>> {
assert_eq!("test:///root", component_uri);
FutureObj::new(Box::new(future::ok(fsys::Component {
resolved_uri: Some("test:///resolved_root".to_string()),
decl: Some(fsys::ComponentDecl {
program: None,
uses: None,
exposes: None,
offers: None,
facets: None,
children: None,
}),
package: None,
})))
}
}
struct MockRunner {}
impl Runner for MockRunner {
fn start(
&self, start_info: fsys::ComponentStartInfo,
) -> FutureObj<Result<(), RunnerError>> {
assert_eq!(
Some("test:///resolved_root".to_string()),
start_info.resolved_uri
);
FutureObj::new(Box::new(future::ok(())))
}
}
#[fuchsia_async::run_until_stalled(test)]
async fn bind_instance_non_existent() {
let resolver = ResolverRegistry::new();
let model = Model::new(ModelParams {
root_component_uri: "test:///root".to_string(),
root_resolver_registry: resolver,
root_default_runner: Box::new(MockRunner {}),
});
assert_eq!(
Err(ModelError::InstanceNotFound),
await!(
model.bind_instance(AbsoluteMoniker::new(vec![ChildMoniker::new(
"no-such-instance".to_string()
)]))
)
);
}
#[fuchsia_async::run_until_stalled(test)]
async fn bind_instance_successfully() {
let mut resolver = ResolverRegistry::new();
resolver.register("test".to_string(), Box::new(MockResolver {}));
let model = Model::new(ModelParams {
root_component_uri: "test:///root".to_string(),
root_resolver_registry: resolver,
root_default_runner: Box::new(MockRunner {}),
});
assert_eq!(Ok(()), await!(model.bind_instance(AbsoluteMoniker::root())));
}
}