blob: 172ae64140a17aef124ba6032e4f945d5fe95e79 [file] [log] [blame]
// Copyright 2022 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::{
capability::{CapabilityProvider, FrameworkCapability, InternalCapabilityProvider},
model::{
actions::{ActionSet, StopAction},
component::{IncomingCapabilities, StartReason, WeakComponentInstance},
error::ModelError,
model::Model,
},
},
::routing::capability_source::InternalCapability,
async_trait::async_trait,
cm_rust::FidlIntoNative,
cm_types::Name,
fidl::endpoints::{DiscoverableProtocolMarker, ServerEnd},
fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
fidl_fuchsia_sys2 as fsys, fuchsia_zircon as zx,
futures::prelude::*,
lazy_static::lazy_static,
moniker::{ChildName, Moniker, MonikerBase, MonikerError},
std::sync::{Arc, Weak},
tracing::warn,
};
lazy_static! {
static ref CAPABILITY_NAME: Name =
fsys::LifecycleControllerMarker::PROTOCOL_NAME.parse().unwrap();
}
pub struct LifecycleController {
model: Weak<Model>,
}
impl LifecycleController {
pub fn new(model: Weak<Model>) -> Arc<Self> {
Arc::new(Self { model })
}
async fn resolve_instance(
model: &Model,
scope_moniker: &Moniker,
moniker: String,
) -> Result<(), fsys::ResolveError> {
let moniker =
join_monikers(scope_moniker, &moniker).map_err(|_| fsys::ResolveError::BadMoniker)?;
let instance =
model.root().find(&moniker).await.ok_or(fsys::ResolveError::InstanceNotFound)?;
instance.resolve().await.map(|_| ()).map_err(|error| {
warn!(%moniker, %error, "failed to resolve instance");
error.into()
})
}
async fn start_instance(
model: &Model,
scope_moniker: &Moniker,
moniker: String,
binder: ServerEnd<fcomponent::BinderMarker>,
) -> Result<(), fsys::StartError> {
let moniker =
join_monikers(scope_moniker, &moniker).map_err(|_| fsys::StartError::BadMoniker)?;
let instance =
model.root().find(&moniker).await.ok_or(fsys::StartError::InstanceNotFound)?;
instance
.start(&StartReason::Debug, None, IncomingCapabilities::default())
.await
.map(|_| ())
.map_err(|error| {
warn!(%moniker, %error, "failed to start instance");
error
})?;
instance.scope_to_runtime(binder.into_channel()).await;
Ok(())
}
async fn stop_instance(
model: &Model,
scope_moniker: &Moniker,
moniker: String,
) -> Result<(), fsys::StopError> {
let moniker =
join_monikers(scope_moniker, &moniker).map_err(|_| fsys::StopError::BadMoniker)?;
let instance =
model.root().find(&moniker).await.ok_or(fsys::StopError::InstanceNotFound)?;
ActionSet::register(instance.clone(), StopAction::new(false)).await.map_err(|error| {
warn!(%moniker, %error, "failed to stop instance");
error
})?;
Ok(())
}
async fn unresolve_instance(
model: &Model,
scope_moniker: &Moniker,
moniker: String,
) -> Result<(), fsys::UnresolveError> {
let moniker =
join_monikers(scope_moniker, &moniker).map_err(|_| fsys::UnresolveError::BadMoniker)?;
let component =
model.root().find(&moniker).await.ok_or(fsys::UnresolveError::InstanceNotFound)?;
component.unresolve().await.map_err(|error| {
warn!(%moniker, %error, "failed to unresolve instance");
error
})?;
Ok(())
}
async fn create_instance(
model: &Model,
scope_moniker: &Moniker,
parent_moniker: String,
collection: fdecl::CollectionRef,
child_decl: fdecl::Child,
child_args: fcomponent::CreateChildArgs,
) -> Result<(), fsys::CreateError> {
let parent_moniker = join_monikers(scope_moniker, &parent_moniker)
.map_err(|_| fsys::CreateError::BadMoniker)?;
let parent_component =
model.root().find_and_maybe_resolve(&parent_moniker).await.map_err(|e| match e {
ModelError::UnexpectedComponentManagerMoniker
| ModelError::ComponentInstanceError { err: _ } => {
fsys::CreateError::InstanceNotFound
}
ModelError::MonikerError { err: _ } => fsys::CreateError::BadMoniker,
_ => fsys::CreateError::Internal,
})?;
cm_fidl_validator::validate_dynamic_child(&child_decl).map_err(|error| {
warn!(%parent_moniker, %error, "failed to create dynamic child. child decl is invalid");
fsys::CreateError::BadChildDecl
})?;
let child_decl = child_decl.fidl_into_native();
parent_component
.add_dynamic_child(collection.name.clone(), &child_decl, child_args)
.await
.map_err(|error| {
warn!(%parent_moniker, %error, "failed to add dynamic child");
error.into()
})
}
async fn destroy_instance(
model: &Model,
scope_moniker: &Moniker,
parent_moniker: String,
child: fdecl::ChildRef,
) -> Result<(), fsys::DestroyError> {
let parent_moniker = join_monikers(scope_moniker, &parent_moniker)
.map_err(|_| fsys::DestroyError::BadMoniker)?;
let parent_component =
model.root().find(&parent_moniker).await.ok_or(fsys::DestroyError::InstanceNotFound)?;
child.collection.as_ref().ok_or(fsys::DestroyError::BadChildRef)?;
let child_moniker = ChildName::try_new(&child.name, child.collection.as_ref())
.map_err(|_| fsys::DestroyError::BadChildRef)?;
parent_component.remove_dynamic_child(&child_moniker).await.map_err(|error| {
warn!(%parent_moniker, %error, "failed to destroy dynamic child");
error.into()
})
}
pub async fn serve(
&self,
scope_moniker: Moniker,
mut stream: fsys::LifecycleControllerRequestStream,
) {
while let Ok(Some(operation)) = stream.try_next().await {
let Some(model) = self.model.upgrade() else {
return;
};
match operation {
fsys::LifecycleControllerRequest::ResolveInstance { moniker, responder } => {
let res = Self::resolve_instance(&model, &scope_moniker, moniker).await;
responder.send(res).unwrap_or_else(
|error| warn!(%error, "LifecycleController.ResolveInstance failed to send"),
);
}
fsys::LifecycleControllerRequest::UnresolveInstance { moniker, responder } => {
let res = Self::unresolve_instance(&model, &scope_moniker, moniker).await;
responder.send(res).unwrap_or_else(
|error| warn!(%error, "LifecycleController.UnresolveInstance failed to send"),
);
}
fsys::LifecycleControllerRequest::StartInstance { moniker, binder, responder } => {
let res = Self::start_instance(&model, &scope_moniker, moniker, binder).await;
responder.send(res).unwrap_or_else(
|error| warn!(%error, "LifecycleController.StartInstance failed to send"),
);
}
fsys::LifecycleControllerRequest::StopInstance { moniker, responder } => {
let res = Self::stop_instance(&model, &scope_moniker, moniker).await;
responder.send(res).unwrap_or_else(
|error| warn!(%error, "LifecycleController.StopInstance failed to send"),
);
}
fsys::LifecycleControllerRequest::CreateInstance {
parent_moniker,
collection,
decl,
args,
responder,
} => {
let res = Self::create_instance(
&model,
&scope_moniker,
parent_moniker,
collection,
decl,
args,
)
.await;
responder.send(res).unwrap_or_else(
|error| warn!(%error, "LifecycleController.CreateInstance failed to send"),
);
}
fsys::LifecycleControllerRequest::DestroyInstance {
parent_moniker,
child,
responder,
} => {
let res =
Self::destroy_instance(&model, &scope_moniker, parent_moniker, child).await;
responder.send(res).unwrap_or_else(
|error| warn!(%error, "LifecycleController.DestroyInstance failed to send"),
);
}
}
}
}
}
pub struct LifecycleControllerFrameworkCapability {
host: Arc<LifecycleController>,
}
impl LifecycleControllerFrameworkCapability {
pub fn new(host: Arc<LifecycleController>) -> Self {
Self { host }
}
}
impl FrameworkCapability for LifecycleControllerFrameworkCapability {
fn matches(&self, capability: &InternalCapability) -> bool {
capability.matches_protocol(&CAPABILITY_NAME)
}
fn new_provider(
&self,
scope: WeakComponentInstance,
_target: WeakComponentInstance,
) -> Box<dyn CapabilityProvider> {
Box::new(LifecycleControllerCapabilityProvider::new(
self.host.clone(),
scope.moniker.clone(),
))
}
}
pub struct LifecycleControllerCapabilityProvider {
control: Arc<LifecycleController>,
scope_moniker: Moniker,
}
impl LifecycleControllerCapabilityProvider {
pub fn new(control: Arc<LifecycleController>, scope_moniker: Moniker) -> Self {
Self { control, scope_moniker }
}
}
#[async_trait]
impl InternalCapabilityProvider for LifecycleControllerCapabilityProvider {
async fn open_protocol(self: Box<Self>, server_end: zx::Channel) {
let server_end = ServerEnd::<fsys::LifecycleControllerMarker>::new(server_end);
self.control.serve(self.scope_moniker, server_end.into_stream().unwrap()).await;
}
}
/// Takes the scoped component's moniker and a moniker string and joins them into an
/// moniker.
fn join_monikers(scope_moniker: &Moniker, moniker_str: &str) -> Result<Moniker, MonikerError> {
let moniker = Moniker::try_from(moniker_str)?;
Ok(scope_moniker.concat(&moniker))
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::model::{
actions::test_utils::{is_discovered, is_resolved, is_shutdown},
testing::test_helpers::TestEnvironmentBuilder,
},
cm_rust_testing::ComponentDeclBuilder,
fidl::endpoints::create_proxy_and_stream,
fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
fidl_fuchsia_component_decl::{ChildRef, CollectionRef},
fuchsia_async as fasync,
};
#[fuchsia::test]
async fn lifecycle_controller_test() {
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.child(cm_rust::ChildDecl {
name: "a".parse().unwrap(),
url: "test:///a".to_string(),
startup: fdecl::StartupMode::Eager,
environment: None,
on_terminate: None,
config_overrides: None,
})
.child(cm_rust::ChildDecl {
name: "cant-resolve".parse().unwrap(),
url: "cant-resolve://cant-resolve".to_string(),
startup: fdecl::StartupMode::Eager,
environment: None,
on_terminate: None,
config_overrides: None,
})
.build(),
),
(
"a",
ComponentDeclBuilder::new()
.child(cm_rust::ChildDecl {
name: "b".parse().unwrap(),
url: "test:///b".to_string(),
startup: fdecl::StartupMode::Eager,
environment: None,
on_terminate: None,
config_overrides: None,
})
.build(),
),
("b", ComponentDeclBuilder::new().build()),
];
let test_model_result =
TestEnvironmentBuilder::new().set_components(components).build().await;
let lifecycle_controller = {
let env = test_model_result.builtin_environment.lock().await;
env.lifecycle_controller.clone().unwrap()
};
let (lifecycle_proxy, lifecycle_request_stream) =
create_proxy_and_stream::<fsys::LifecycleControllerMarker>().unwrap();
// async move {} is used here because we want this to own the lifecycle_controller
let _lifecycle_server_task = fasync::Task::local(async move {
lifecycle_controller.serve(Moniker::root(), lifecycle_request_stream).await
});
assert_eq!(lifecycle_proxy.resolve_instance(".").await.unwrap(), Ok(()));
assert_eq!(lifecycle_proxy.resolve_instance("./a").await.unwrap(), Ok(()));
assert_eq!(
lifecycle_proxy.resolve_instance(".\\scope-escape-attempt").await.unwrap(),
Err(fsys::ResolveError::BadMoniker)
);
assert_eq!(
lifecycle_proxy.resolve_instance("./doesnt-exist").await.unwrap(),
Err(fsys::ResolveError::InstanceNotFound)
);
assert_eq!(
lifecycle_proxy.resolve_instance("./cant-resolve").await.unwrap(),
Err(fsys::ResolveError::Internal)
);
}
#[fuchsia::test]
async fn lifecycle_controller_unresolve_component_test() {
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.child(cm_rust::ChildDecl {
name: "a".parse().unwrap(),
url: "test:///a".to_string(),
startup: fdecl::StartupMode::Eager,
environment: None,
on_terminate: None,
config_overrides: None,
})
.build(),
),
(
"a",
ComponentDeclBuilder::new()
.child(cm_rust::ChildDecl {
name: "b".parse().unwrap(),
url: "test:///b".to_string(),
startup: fdecl::StartupMode::Eager,
environment: None,
on_terminate: None,
config_overrides: None,
})
.build(),
),
("b", ComponentDeclBuilder::new().build()),
];
let test_model_result =
TestEnvironmentBuilder::new().set_components(components).build().await;
let root = test_model_result.model.root();
let lifecycle_controller = {
let env = test_model_result.builtin_environment.lock().await;
env.lifecycle_controller.clone().unwrap()
};
let (lifecycle_proxy, lifecycle_request_stream) =
create_proxy_and_stream::<fsys::LifecycleControllerMarker>().unwrap();
// async move {} is used here because we want this to own the lifecycle_controller
let _lifecycle_server_task = fasync::Task::local(async move {
lifecycle_controller.serve(Moniker::root(), lifecycle_request_stream).await
});
lifecycle_proxy.resolve_instance(".").await.unwrap().unwrap();
let component_a =
root.find_and_maybe_resolve(&vec!["a"].try_into().unwrap()).await.unwrap();
let component_b =
root.find_and_maybe_resolve(&vec!["a", "b"].try_into().unwrap()).await.unwrap();
assert!(is_resolved(&component_a).await);
assert!(is_resolved(&component_b).await);
lifecycle_proxy.unresolve_instance(".").await.unwrap().unwrap();
assert!(is_discovered(&root).await);
assert!(is_shutdown(&component_a).await);
assert!(is_shutdown(&component_b).await);
assert_eq!(
lifecycle_proxy.unresolve_instance("./nonesuch").await.unwrap(),
Err(fsys::UnresolveError::InstanceNotFound)
);
// Unresolve again, which is ok because UnresolveAction is idempotent.
assert_eq!(lifecycle_proxy.unresolve_instance(".").await.unwrap(), Ok(()));
assert!(is_discovered(&root).await);
assert!(is_shutdown(&component_a).await);
assert!(is_shutdown(&component_b).await);
}
#[fuchsia::test]
async fn lifecycle_create_and_destroy_test() {
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.collection_default("coll")
.child_default("child")
.build(),
),
("child", ComponentDeclBuilder::new().build()),
];
let test_model_result =
TestEnvironmentBuilder::new().set_components(components).build().await;
let lifecycle_controller = {
let env = test_model_result.builtin_environment.lock().await;
env.lifecycle_controller.clone().unwrap()
};
let (lifecycle_proxy, lifecycle_request_stream) =
create_proxy_and_stream::<fsys::LifecycleControllerMarker>().unwrap();
// async move {} is used here because we want this to own the lifecycle_controller
let _lifecycle_server_task = fasync::Task::local(async move {
lifecycle_controller.serve(Moniker::root(), lifecycle_request_stream).await
});
assert_eq!(
lifecycle_proxy
.create_instance(
"./",
&CollectionRef { name: "coll".to_string() },
&fdecl::Child {
name: Some("child".to_string()),
url: Some("test:///child".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..Default::default()
},
fcomponent::CreateChildArgs::default(),
)
.await
.unwrap(),
Ok(())
);
assert_eq!(lifecycle_proxy.resolve_instance("./coll:child").await.unwrap(), Ok(()));
assert_eq!(
lifecycle_proxy
.destroy_instance(
"./",
&ChildRef {
name: "child".parse().unwrap(),
collection: Some("coll".to_string())
}
)
.await
.unwrap(),
Ok(())
);
assert_eq!(
lifecycle_proxy.resolve_instance("./coll:child").await.unwrap(),
Err(fsys::ResolveError::InstanceNotFound)
);
}
#[fuchsia::test]
async fn lifecycle_create_fail_test() {
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.collection_default("coll")
.child_default("child")
.build(),
),
("child", ComponentDeclBuilder::new().build()),
];
let test_model_result =
TestEnvironmentBuilder::new().set_components(components).build().await;
let lifecycle_controller = {
let env = test_model_result.builtin_environment.lock().await;
env.lifecycle_controller.clone().unwrap()
};
let (lifecycle_proxy, lifecycle_request_stream) =
create_proxy_and_stream::<fsys::LifecycleControllerMarker>().unwrap();
// async move {} is used here because we want this to own the lifecycle_controller
let _lifecycle_server_task = fasync::Task::local(async move {
lifecycle_controller.serve(Moniker::root(), lifecycle_request_stream).await
});
assert_eq!(
lifecycle_proxy
.create_instance(
"&#^$%",
&CollectionRef { name: "coll".to_string() },
&fdecl::Child {
name: Some("child".to_string()),
url: Some("test:///child".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..Default::default()
},
fcomponent::CreateChildArgs::default(),
)
.await
.unwrap(),
Err(fsys::CreateError::BadMoniker)
);
assert_eq!(
lifecycle_proxy
.create_instance(
"./does_not_exist",
&CollectionRef { name: "coll".to_string() },
&fdecl::Child {
name: Some("child".to_string()),
url: Some("test:///child".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..Default::default()
},
fcomponent::CreateChildArgs::default(),
)
.await
.unwrap(),
Err(fsys::CreateError::InstanceNotFound)
);
assert_eq!(
lifecycle_proxy
.create_instance(
"./",
&CollectionRef { name: "not_coll".to_string() },
&fdecl::Child {
name: Some("child".to_string()),
url: Some("test:///child".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..Default::default()
},
fcomponent::CreateChildArgs::default(),
)
.await
.unwrap(),
Err(fsys::CreateError::CollectionNotFound)
);
assert_eq!(
lifecycle_proxy
.create_instance(
"./",
&CollectionRef { name: "coll".to_string() },
&fdecl::Child {
name: Some("&*^%&@#$".to_string()),
url: Some("test:///child".to_string()),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
on_terminate: None,
..Default::default()
},
fcomponent::CreateChildArgs::default(),
)
.await
.unwrap(),
Err(fsys::CreateError::BadChildDecl)
);
}
}