blob: 62260bb783a794743f113f7cab2bd760629b4d89 [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.
use {
crate::model::{
actions::{ActionSet, ShutdownAction},
error::ModelError,
realm::{Realm, RealmState},
},
cm_rust::{
CapabilityDecl, CapabilityName, ComponentDecl, DependencyType, OfferDecl,
OfferDirectorySource, OfferResolverSource, OfferRunnerSource, OfferServiceSource,
OfferStorageSource, OfferTarget, RegistrationSource, StorageDirectorySource,
},
futures::future::select_all,
maplit::hashset,
moniker::{ChildMoniker, PartialMoniker},
std::collections::{HashMap, HashSet},
std::fmt,
std::sync::Arc,
};
/// A DependencyNode represents a provider or user of a capability. This
/// may be either a component or a component collection.
#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub enum DependencyNode {
Child(String),
Collection(String),
}
/// Examines a group of StorageDecls looking for one whose name matches the
/// String passed in and whose source is a child. `None` is returned if either
/// no declaration has the specified name or the declaration represents an
/// offer from Self or Parent.
fn find_storage_provider(
capabilities: &Vec<CapabilityDecl>,
name: &CapabilityName,
) -> Option<String> {
for decl in capabilities {
match decl {
CapabilityDecl::Storage(decl) if &decl.name == name => match &decl.source {
StorageDirectorySource::Child(child) => {
return Some(child.to_string());
}
StorageDirectorySource::Self_ | StorageDirectorySource::Parent => {
return None;
}
},
_ => {}
}
}
None
}
async fn shutdown_component(child: ShutdownInfo) -> Result<ChildMoniker, ModelError> {
ActionSet::register(child.realm, ShutdownAction::new()).await?;
Ok(child.moniker.clone())
}
/// Structure which holds bidirectional capability maps used during the
/// shutdown process.
struct ShutdownJob {
/// A map from users of capabilities to the components that provide those
/// capabilities
target_to_sources: HashMap<ChildMoniker, Vec<ChildMoniker>>,
/// A map from providers of capabilities to those components which use the
/// capabilities
source_to_targets: HashMap<ChildMoniker, ShutdownInfo>,
}
/// ShutdownJob encapsulates the logic and state require to shutdown a realm.
impl ShutdownJob {
/// Creates a new ShutdownJob by examining the Realm's declaration and
/// runtime state to build up the necessary data structures to stop
/// components in the realm in dependency order.
pub async fn new(state: &RealmState) -> ShutdownJob {
// `children` represents the dependency relationships between the
// children as expressed in the realm's component declaration.
// This representation must be reconciled with the runtime state of the
// realm. This means mapping children in the declaration with the one
// or more children that may exist in collections and one or more
// instances with a matching PartialMoniker that may exist.
let children = process_component_dependencies(state.decl());
let mut source_to_targets: HashMap<ChildMoniker, ShutdownInfo> = HashMap::new();
for (child_name, sibling_deps) in children {
let deps = get_child_monikers(&sibling_deps, state);
let singleton_child_set = hashset![child_name];
// The shutdown target may be a collection, if so this will expand
// the collection out into a list of all its members, otherwise it
// contains a single component.
let matching_children: Vec<_> =
get_child_monikers(&singleton_child_set, state).into_iter().collect();
for child in matching_children {
let realm = state
.get_child_instance(&child)
.expect("component not found in children")
.clone();
source_to_targets.insert(
child.clone(),
ShutdownInfo { moniker: child, dependents: deps.clone(), realm: realm },
);
}
}
let mut target_to_sources: HashMap<ChildMoniker, Vec<ChildMoniker>> = HashMap::new();
// Look at each of the children
for provider in source_to_targets.values() {
// All listed siblings are ones that depend on this child
// and all those siblings must stop before this one
for consumer in &provider.dependents {
// Make or update a map entry for the consumer that points to the
// list of siblings that offer it capabilities
target_to_sources
.entry(consumer.clone())
.or_insert(vec![])
.push(provider.moniker.clone());
}
}
let new_job = ShutdownJob { source_to_targets, target_to_sources };
return new_job;
}
/// Perform shutdown of the Realm that was used to create this ShutdownJob
/// A Realm must wait to shut down until all its children are shut down.
/// The shutdown procedure looks at the children of Realm, if any, and
/// determines the dependency relationships of the children.
pub async fn execute(&mut self) -> Result<(), ModelError> {
// Relationship maps are maintained to track dependencies. A map is
// maintained both from a Realm to its dependents and from a Realm to
// that Realm's dependencies. With this dependency tracking, the
// children of the Realm can be shut down progressively in dependency
// order.
//
// The progressive shutdown of Realms is performed in this order:
// Note: These steps continue until the shutdown process is no longer
// asynchronously waiting for any shut downs to complete.
// * Identify the one or more Realms that have no dependents
// * A shutdown action is set to the identified realms. During the
// shut down process, the result of the process is received
// asynchronously.
// * After a Realm is shut down, the Realms are removed from the list
// of dependents of the Realms on which they had a dependency.
// * The list of Realms is checked again to see which Realms have no
// remaining dependents.
// Look for any children that have no dependents
let mut stop_targets = vec![];
for moniker in self.source_to_targets.keys().map(|key| key.clone()).collect::<Vec<_>>() {
let no_dependents = {
let info = self.source_to_targets.get(&moniker).expect("key disappeared from map");
info.dependents.is_empty()
};
if no_dependents {
stop_targets.push(
self.source_to_targets.remove(&moniker).expect("key disappeared from map"),
);
}
}
let mut futs = vec![];
// Continue while we have new stop targets or unfinished futures
while !stop_targets.is_empty() || !futs.is_empty() {
for target in stop_targets.drain(..) {
futs.push(Box::pin(shutdown_component(target)));
}
let (moniker, _, remaining) = select_all(futs).await;
futs = remaining;
let moniker = moniker?;
// Look up the dependencies of the component that stopped
match self.target_to_sources.remove(&moniker) {
Some(vec) => {
for dep_moniker in vec {
let ready_to_stop = {
if let Some(child) = self.source_to_targets.get_mut(&dep_moniker) {
child.dependents.remove(&moniker);
// Have all of this components dependents stopped?
child.dependents.is_empty()
} else {
// The component that provided a capability to
// the stopped component doesn't exist or
// somehow already stopped. This is unexpected.
panic!(
"The component '{}' appears to have stopped before its \
dependency '{}'",
moniker, dep_moniker
);
}
};
// This components had zero remaining dependents
if ready_to_stop {
stop_targets.push(
self.source_to_targets
.remove(&dep_moniker)
.expect("A key that was just available has disappeared."),
);
}
}
}
None => {
// Oh well, component didn't have any dependencies
}
}
}
// We should have stopped all children, if not probably there is a
// dependency cycle
if !self.source_to_targets.is_empty() {
panic!(
"Something failed, all children should have been removed! {:?}",
self.source_to_targets
);
}
Ok(())
}
}
pub async fn do_shutdown(realm: &Arc<Realm>) -> Result<(), ModelError> {
{
let state_lock = realm.lock_state().await;
{
let exec_state = realm.lock_execution().await;
if exec_state.is_shut_down() {
return Ok(());
}
}
if let Some(state) = state_lock.as_ref() {
let mut shutdown_job = ShutdownJob::new(state).await;
drop(state_lock);
Box::pin(shutdown_job.execute()).await?;
}
}
// Now that all children have shut down, shut down the parent.
// TODO: Put the parent in a "shutting down" state so that if it creates new instances
// after this point, they are created in a shut down state.
realm.stop_instance(true).await?;
Ok(())
}
/// Used to track information during the shutdown process. The dependents
/// are all the component which must stop before the component represented
/// by this struct.
struct ShutdownInfo {
// TODO(jmatt) reduce visibility of fields
/// The identifier for this component
pub moniker: ChildMoniker,
/// The components that this component offers capabilities to
pub dependents: HashSet<ChildMoniker>,
pub realm: Arc<Realm>,
}
impl fmt::Debug for ShutdownInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "moniker: '{:?}'", self.moniker)
}
}
/// Given a set of DependencyNodes, find all the ChildMonikers in the supplied
/// Realm that match.
fn get_child_monikers(
child_names: &HashSet<DependencyNode>,
realm_state: &RealmState,
) -> HashSet<ChildMoniker> {
let mut deps: HashSet<ChildMoniker> = HashSet::new();
let realms = realm_state.all_child_realms();
for child in child_names {
match child {
DependencyNode::Child(name) => {
let dep_moniker = PartialMoniker::new(name.to_string(), None);
let matching_children = realm_state.get_all_child_monikers(&dep_moniker);
for m in matching_children {
deps.insert(m);
}
}
DependencyNode::Collection(name) => {
for moniker in realms.keys() {
match moniker.collection() {
Some(m) => {
if m == name {
deps.insert(moniker.clone());
}
}
None => {}
}
}
}
}
}
deps
}
/// Maps a dependency node (child or collection) to the nodes that depend on it.
pub type DependencyMap = HashMap<DependencyNode, HashSet<DependencyNode>>;
/// For a given ComponentDecl, parse it, identify capability dependencies
/// between children and collections in the ComponentDecl. A map is returned
/// which maps from a child to a set of other children to which that child
/// provides capabilities. The siblings to which the child offers capabilities
/// must be shut down before that child. This function panics if there is a
/// capability routing where either the source or target is not present in this
/// ComponentDecl. Panics are not expected because ComponentDecls should be
/// validated before this function is called.
pub fn process_component_dependencies(decl: &ComponentDecl) -> DependencyMap {
let mut dependency_map: DependencyMap = decl
.children
.iter()
.map(|c| (DependencyNode::Child(c.name.clone()), HashSet::new()))
.collect();
dependency_map.extend(
decl.collections
.iter()
.map(|c| (DependencyNode::Collection(c.name.clone()), HashSet::new())),
);
get_dependencies_from_offers(decl, &mut dependency_map);
get_dependencies_from_environments(decl, &mut dependency_map);
dependency_map
}
/// Loops through all the offer declarations to determine which siblings
/// provide capabilities to other siblings.
fn get_dependencies_from_offers(decl: &ComponentDecl, dependency_map: &mut DependencyMap) {
for dep in &decl.offers {
// Identify the source and target of the offer. We only care about
// dependencies where the provider of the dependency is another child,
// otherwise the capability comes from the parent or component manager
// itself in which case the relationship is not relevant for ordering
// here.
let source_target_pairs = match dep {
OfferDecl::Protocol(svc_offer) => {
if svc_offer.dependency_type == DependencyType::WeakForMigration {
// weak dependencies are ignored by this algorithm, because weak dependencies
// can be broken arbitrarily.
continue;
}
match &svc_offer.source {
OfferServiceSource::Child(source) => match &svc_offer.target {
OfferTarget::Child(target) => vec![(
DependencyNode::Child(source.clone()),
DependencyNode::Child(target.clone()),
)],
OfferTarget::Collection(target) => vec![(
DependencyNode::Child(source.clone()),
DependencyNode::Collection(target.clone()),
)],
},
OfferServiceSource::Self_
| OfferServiceSource::Parent
| OfferServiceSource::Capability(_) => {
// Capabilities offered by the parent, routed in from the realm, or
// provided by the framework (based on some other capability) are not
// relevant.
continue;
}
}
}
OfferDecl::Service(svc_offers) => {
let mut pairs = vec![];
for svc_offer in &svc_offers.sources {
match &svc_offer.source {
OfferServiceSource::Child(source) => match &svc_offers.target {
OfferTarget::Child(target) => pairs.push((
DependencyNode::Child(source.clone()),
DependencyNode::Child(target.clone()),
)),
OfferTarget::Collection(target) => pairs.push((
DependencyNode::Child(source.clone()),
DependencyNode::Collection(target.clone()),
)),
},
OfferServiceSource::Self_
| OfferServiceSource::Parent
| OfferServiceSource::Capability(_) => {
// Capabilities offered by the parent, routed in from the realm, or
// provided by the framework (based on some other capability) are not
// relevant.
continue;
}
}
}
pairs
}
OfferDecl::Directory(dir_offer) => {
if dir_offer.dependency_type == DependencyType::WeakForMigration {
// weak dependencies are ignored by this algorithm, because weak dependencies
// can be broken arbitrarily.
continue;
}
match &dir_offer.source {
OfferDirectorySource::Child(source) => match &dir_offer.target {
OfferTarget::Child(target) => vec![(
DependencyNode::Child(source.clone()),
DependencyNode::Child(target.clone()),
)],
OfferTarget::Collection(target) => vec![(
DependencyNode::Child(source.clone()),
DependencyNode::Collection(target.clone()),
)],
},
OfferDirectorySource::Self_
| OfferDirectorySource::Parent
| OfferDirectorySource::Framework => {
// Capabilities offered by the parent or routed in from
// the realm are not relevant.
continue;
}
}
}
OfferDecl::Storage(s) => {
match &s.source {
OfferStorageSource::Self_ => {
match find_storage_provider(&decl.capabilities, &s.source_name) {
Some(storage_source) => match &s.target {
OfferTarget::Child(target) => vec![(
DependencyNode::Child(storage_source.clone()),
DependencyNode::Child(target.clone()),
)],
OfferTarget::Collection(target) => vec![(
DependencyNode::Child(storage_source.clone()),
DependencyNode::Collection(target.clone()),
)],
},
None => {
// The storage offer is not from a child, so it
// can be ignored.
continue;
}
}
}
OfferStorageSource::Parent => {
// Capabilities coming from the parent aren't tracked.
continue;
}
}
}
OfferDecl::Runner(runner_offer) => {
match &runner_offer.source {
OfferRunnerSource::Child(source) => match &runner_offer.target {
OfferTarget::Child(target) => vec![(
DependencyNode::Child(source.clone()),
DependencyNode::Child(target.clone()),
)],
OfferTarget::Collection(target) => vec![(
DependencyNode::Child(source.clone()),
DependencyNode::Collection(target.clone()),
)],
},
OfferRunnerSource::Self_ | OfferRunnerSource::Parent => {
// Capabilities coming from the parent aren't tracked.
continue;
}
}
}
OfferDecl::Resolver(resolver_offer) => {
match &resolver_offer.source {
OfferResolverSource::Child(source) => match &resolver_offer.target {
OfferTarget::Child(target) => vec![(
DependencyNode::Child(source.clone()),
DependencyNode::Child(target.clone()),
)],
OfferTarget::Collection(target) => vec![(
DependencyNode::Child(source.clone()),
DependencyNode::Collection(target.clone()),
)],
},
OfferResolverSource::Self_ | OfferResolverSource::Parent => {
// Capabilities coming from the parent aren't tracked.
continue;
}
}
}
OfferDecl::Event(_) => {
// Events aren't tracked as dependencies for shutdown.
continue;
}
};
for (capability_provider, capability_target) in source_target_pairs {
if !dependency_map.contains_key(&capability_target) {
panic!(
"This capability routing seems invalid, the target \
does not exist in this realm. Source: {:?} Target: {:?}",
capability_provider, capability_target,
);
}
let sibling_deps = dependency_map.get_mut(&capability_provider).expect(&format!(
"This capability routing seems invalid, the source \
does not exist in this realm. Source: {:?} Target: {:?}",
capability_provider, capability_target,
));
sibling_deps.insert(capability_target);
}
}
}
/// Loops through all the child and collection declarations to determine what siblings provide
/// capabilities to other siblings through an environment.
fn get_dependencies_from_environments(decl: &ComponentDecl, dependency_map: &mut DependencyMap) {
let mut env_source_children = HashMap::new();
for env in &decl.environments {
env_source_children.insert(&env.name, vec![]);
for runner in &env.runners {
if let RegistrationSource::Child(source_child) = &runner.source {
env_source_children.get_mut(&env.name).unwrap().push(source_child);
}
}
}
for dest_child in &decl.children {
if let Some(env_name) = dest_child.environment.as_ref() {
for source_child in env_source_children.get(env_name).expect(&format!(
"environment `{}` from child `{}` is not a valid environment",
env_name, dest_child.name,
)) {
dependency_map
.entry(DependencyNode::Child((*source_child).clone()))
.or_insert(HashSet::new())
.insert(DependencyNode::Child(dest_child.name.clone()));
}
}
}
for dest_collection in &decl.collections {
if let Some(env_name) = dest_collection.environment.as_ref() {
for source_child in env_source_children.get(env_name).expect(&format!(
"environment `{}` from collection `{}` is not a valid environment",
env_name, dest_collection.name,
)) {
dependency_map
.entry(DependencyNode::Child((*source_child).clone()))
.or_insert(HashSet::new())
.insert(DependencyNode::Collection(dest_collection.name.clone()));
}
}
}
}
#[cfg(test)]
mod tests {
// Tests for ShutdownJob are found in actions.rs where we try to shutdown
// various component topologies.
use {
super::*,
crate::model::testing::test_helpers::{
default_component_decl, ChildDeclBuilder, CollectionDeclBuilder, EnvironmentDeclBuilder,
},
anyhow::Error,
cm_rust::{
CapabilityName, ChildDecl, DependencyType, ExposeDecl, ExposeProtocolDecl,
ExposeSource, ExposeTarget, OfferProtocolDecl, OfferResolverDecl, OfferServiceSource,
OfferTarget,
},
fidl_fuchsia_sys2 as fsys,
std::collections::HashMap,
std::convert::TryFrom,
};
// TODO(jmatt) Add tests for all capability types
/// Validates that actual looks like expected and panics if they don't.
/// `expected` must be sorted and so must the second member of each
/// tuple in the vec.
fn validate_results(
expected: Vec<(DependencyNode, Vec<DependencyNode>)>,
mut actual: HashMap<DependencyNode, HashSet<DependencyNode>>,
) {
let mut actual_sorted: Vec<(DependencyNode, Vec<DependencyNode>)> = actual
.drain()
.map(|(k, v)| {
let mut new_vec = Vec::new();
new_vec.extend(v.into_iter());
new_vec.sort_unstable();
(k, new_vec)
})
.collect();
actual_sorted.sort_unstable();
assert_eq!(expected, actual_sorted);
}
#[test]
fn test_service_from_parent() -> Result<(), Error> {
let decl = ComponentDecl {
offers: vec![OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Self_,
source_name: "serviceParent".into(),
target_name: "serviceParent".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::Strong,
})],
children: vec![ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
}],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((DependencyNode::Child("childA".to_string()), vec![]));
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_weak_service_from_parent() -> Result<(), Error> {
let decl = ComponentDecl {
offers: vec![OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Self_,
source_name: "serviceParent".into(),
target_name: "serviceParent".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::WeakForMigration,
})],
children: vec![ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
}],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((DependencyNode::Child("childA".to_string()), vec![]));
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_service_from_child() -> Result<(), Error> {
let decl = ComponentDecl {
exposes: vec![ExposeDecl::Protocol(ExposeProtocolDecl {
target: ExposeTarget::Parent,
source_name: "serviceFromChild".into(),
target_name: "serviceFromChild".into(),
source: ExposeSource::Child("childA".to_string()),
})],
children: vec![ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
}],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((DependencyNode::Child("childA".to_string()), vec![]));
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_single_dependency() -> Result<(), Error> {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_b = ChildDecl {
name: "childB".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let decl = ComponentDecl {
offers: vec![
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Self_,
source_name: "serviceParent".into(),
target_name: "serviceParent".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::Strong,
}),
],
children: vec![child_a.clone(), child_b.clone()],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
let mut v = vec![DependencyNode::Child(child_a.name.clone())];
v.sort_unstable();
expected.push((DependencyNode::Child(child_b.name.clone()), v));
expected.push((DependencyNode::Child(child_a.name.clone()), vec![]));
expected.sort_unstable();
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_environment_with_runner_from_parent() -> Result<(), Error> {
let decl = ComponentDecl {
environments: vec![EnvironmentDeclBuilder::new()
.name("env")
.add_runner(cm_rust::RunnerRegistration {
source: RegistrationSource::Parent,
source_name: "foo".into(),
target_name: "foo".into(),
})
.build()],
children: vec![
ChildDeclBuilder::new_lazy_child("childA").build(),
ChildDeclBuilder::new_lazy_child("childB").environment("env").build(),
],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((DependencyNode::Child("childA".to_string()), vec![]));
expected.push((DependencyNode::Child("childB".to_string()), vec![]));
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_environment_with_runner_from_child() -> Result<(), Error> {
let decl = ComponentDecl {
environments: vec![EnvironmentDeclBuilder::new()
.name("env")
.add_runner(cm_rust::RunnerRegistration {
source: RegistrationSource::Child("childA".into()),
source_name: "foo".into(),
target_name: "foo".into(),
})
.build()],
children: vec![
ChildDeclBuilder::new_lazy_child("childA").build(),
ChildDeclBuilder::new_lazy_child("childB").environment("env").build(),
],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((
DependencyNode::Child("childA".to_string()),
vec![DependencyNode::Child("childB".to_string())],
));
expected.push((DependencyNode::Child("childB".to_string()), vec![]));
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_environment_with_runner_from_child_to_collection() -> Result<(), Error> {
let decl = ComponentDecl {
environments: vec![EnvironmentDeclBuilder::new()
.name("env")
.add_runner(cm_rust::RunnerRegistration {
source: RegistrationSource::Child("childA".into()),
source_name: "foo".into(),
target_name: "foo".into(),
})
.build()],
collections: vec![CollectionDeclBuilder::new().name("coll").environment("env").build()],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((
DependencyNode::Child("childA".to_string()),
vec![DependencyNode::Collection("coll".to_string())],
));
expected.push((DependencyNode::Collection("coll".to_string()), vec![]));
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_chained_environments() -> Result<(), Error> {
let decl = ComponentDecl {
environments: vec![
EnvironmentDeclBuilder::new()
.name("env")
.add_runner(cm_rust::RunnerRegistration {
source: RegistrationSource::Child("childA".into()),
source_name: "foo".into(),
target_name: "foo".into(),
})
.build(),
EnvironmentDeclBuilder::new()
.name("env2")
.add_runner(cm_rust::RunnerRegistration {
source: RegistrationSource::Child("childB".into()),
source_name: "bar".into(),
target_name: "bar".into(),
})
.build(),
],
children: vec![
ChildDeclBuilder::new_lazy_child("childA").build(),
ChildDeclBuilder::new_lazy_child("childB").environment("env").build(),
ChildDeclBuilder::new_lazy_child("childC").environment("env2").build(),
],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((
DependencyNode::Child("childA".to_string()),
vec![DependencyNode::Child("childB".to_string())],
));
expected.push((
DependencyNode::Child("childB".to_string()),
vec![DependencyNode::Child("childC".to_string())],
));
expected.push((DependencyNode::Child("childC".to_string()), vec![]));
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_environment_and_offer() -> Result<(), Error> {
let decl = ComponentDecl {
offers: vec![OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childC".to_string()),
dependency_type: DependencyType::Strong,
})],
environments: vec![EnvironmentDeclBuilder::new()
.name("env")
.add_runner(cm_rust::RunnerRegistration {
source: RegistrationSource::Child("childA".into()),
source_name: "foo".into(),
target_name: "foo".into(),
})
.build()],
children: vec![
ChildDeclBuilder::new_lazy_child("childA").build(),
ChildDeclBuilder::new_lazy_child("childB").environment("env").build(),
ChildDeclBuilder::new_lazy_child("childC").build(),
],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((
DependencyNode::Child("childA".to_string()),
vec![DependencyNode::Child("childB".to_string())],
));
expected.push((
DependencyNode::Child("childB".to_string()),
vec![DependencyNode::Child("childC".to_string())],
));
expected.push((DependencyNode::Child("childC".to_string()), vec![]));
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_single_weak_dependency() -> Result<(), Error> {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_b = ChildDecl {
name: "childB".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let decl = ComponentDecl {
offers: vec![
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Self_,
source_name: "serviceParent".into(),
target_name: "serviceParent".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::WeakForMigration,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::WeakForMigration,
}),
],
children: vec![child_a.clone(), child_b.clone()],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((DependencyNode::Child(child_b.name.clone()), vec![]));
expected.push((DependencyNode::Child(child_a.name.clone()), vec![]));
expected.sort_unstable();
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_multiple_dependencies_same_source() -> Result<(), Error> {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_b = ChildDecl {
name: "childB".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let decl = ComponentDecl {
offers: vec![
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Self_,
source_name: "serviceParent".into(),
target_name: "serviceParent".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBOtherOffer".into(),
target_name: "serviceOtherSibling".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::Strong,
}),
],
children: vec![child_a.clone(), child_b.clone()],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
let mut v = vec![DependencyNode::Child(child_a.name.clone())];
v.sort_unstable();
expected.push((DependencyNode::Child(child_b.name.clone()), v));
expected.push((DependencyNode::Child(child_a.name.clone()), vec![]));
expected.sort_unstable();
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_multiple_dependents_same_source() -> Result<(), Error> {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_b = ChildDecl {
name: "childB".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_c = ChildDecl {
name: "childC".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let decl = ComponentDecl {
offers: vec![
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBToC".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childC".to_string()),
dependency_type: DependencyType::Strong,
}),
],
children: vec![child_a.clone(), child_b.clone(), child_c.clone()],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
let mut v = vec![
DependencyNode::Child(child_a.name.clone()),
DependencyNode::Child(child_c.name.clone()),
];
v.sort_unstable();
expected.push((DependencyNode::Child(child_b.name.clone()), v));
expected.push((DependencyNode::Child(child_a.name.clone()), vec![]));
expected.push((DependencyNode::Child(child_c.name.clone()), vec![]));
expected.sort_unstable();
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_multiple_dependencies() -> Result<(), Error> {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_b = ChildDecl {
name: "childB".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_c = ChildDecl {
name: "childC".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let decl = ComponentDecl {
offers: vec![
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childA".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childC".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBToC".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childC".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childC".to_string()),
source_name: "childCToA".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::WeakForMigration,
}),
],
children: vec![child_a.clone(), child_b.clone(), child_c.clone()],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((
DependencyNode::Child(child_b.name.clone()),
vec![DependencyNode::Child(child_c.name.clone())],
));
expected.push((
DependencyNode::Child(child_a.name.clone()),
vec![DependencyNode::Child(child_c.name.clone())],
));
expected.push((DependencyNode::Child(child_c.name.clone()), vec![]));
expected.sort_unstable();
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
fn test_component_is_source_and_target() -> Result<(), Error> {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_b = ChildDecl {
name: "childB".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_c = ChildDecl {
name: "childC".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let decl = ComponentDecl {
offers: vec![
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childA".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childB".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBToC".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childC".to_string()),
dependency_type: DependencyType::Strong,
}),
],
children: vec![child_a.clone(), child_b.clone(), child_c.clone()],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((
DependencyNode::Child(child_a.name.clone()),
vec![DependencyNode::Child(child_b.name.clone())],
));
expected.push((
DependencyNode::Child(child_b.name.clone()),
vec![DependencyNode::Child(child_c.name.clone())],
));
expected.push((DependencyNode::Child(child_c.name.clone()), vec![]));
expected.sort_unstable();
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
/// Tests a graph that looks like the below, tildes indicate a
/// capability route. Route point toward the target of the capability
/// offer. The manifest constructed is for 'P'.
/// P
/// ___|___
/// / / | \ \
/// e<~c<~a~>b~>d
/// \ /
/// *>~~>*
#[test]
fn test_complex_routing() -> Result<(), Error> {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_b = ChildDecl {
name: "childB".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_c = ChildDecl {
name: "childC".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_d = ChildDecl {
name: "childD".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_e = ChildDecl {
name: "childE".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let decl = ComponentDecl {
offers: vec![
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childA".to_string()),
source_name: "childAService".into(),
target_name: "childAService".into(),
target: OfferTarget::Child("childB".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childA".to_string()),
source_name: "childAService".into(),
target_name: "childAService".into(),
target: OfferTarget::Child("childC".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBService".into(),
target_name: "childBService".into(),
target: OfferTarget::Child("childD".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childC".to_string()),
source_name: "childAService".into(),
target_name: "childAService".into(),
target: OfferTarget::Child("childD".to_string()),
dependency_type: DependencyType::Strong,
}),
OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childC".to_string()),
source_name: "childAService".into(),
target_name: "childAService".into(),
target: OfferTarget::Child("childE".to_string()),
dependency_type: DependencyType::Strong,
}),
],
children: vec![
child_a.clone(),
child_b.clone(),
child_c.clone(),
child_d.clone(),
child_e.clone(),
],
..default_component_decl()
};
let mut expected: Vec<(DependencyNode, Vec<DependencyNode>)> = Vec::new();
expected.push((
DependencyNode::Child(child_a.name.clone()),
vec![
DependencyNode::Child(child_b.name.clone()),
DependencyNode::Child(child_c.name.clone()),
],
));
expected.push((
DependencyNode::Child(child_b.name.clone()),
vec![DependencyNode::Child(child_d.name.clone())],
));
expected.push((
DependencyNode::Child(child_c.name.clone()),
vec![
DependencyNode::Child(child_d.name.clone()),
DependencyNode::Child(child_e.name.clone()),
],
));
expected.push((DependencyNode::Child(child_d.name.clone()), vec![]));
expected.push((DependencyNode::Child(child_e.name.clone()), vec![]));
expected.sort_unstable();
validate_results(expected, process_component_dependencies(&decl));
Ok(())
}
#[test]
#[should_panic]
fn test_target_does_not_exist() {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
// This declaration is invalid because the offer target doesn't exist
let decl = ComponentDecl {
offers: vec![OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childA".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childB".to_string()),
dependency_type: DependencyType::Strong,
})],
children: vec![child_a.clone()],
..default_component_decl()
};
process_component_dependencies(&decl);
}
#[test]
#[should_panic]
fn test_source_does_not_exist() {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
// This declaration is invalid because the offer target doesn't exist
let decl = ComponentDecl {
offers: vec![OfferDecl::Protocol(OfferProtocolDecl {
source: OfferServiceSource::Child("childB".to_string()),
source_name: "childBOffer".into(),
target_name: "serviceSibling".into(),
target: OfferTarget::Child("childA".to_string()),
dependency_type: DependencyType::Strong,
})],
children: vec![child_a.clone()],
..default_component_decl()
};
process_component_dependencies(&decl);
}
#[test]
fn test_resolver_capability_creates_dependency() {
let child_a = ChildDecl {
name: "childA".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let child_b = ChildDecl {
name: "childB".to_string(),
url: "ignored:///child".to_string(),
startup: fsys::StartupMode::Lazy,
environment: None,
};
let decl = ComponentDecl {
offers: vec![OfferDecl::Resolver(OfferResolverDecl {
source: OfferResolverSource::Child("childA".to_string()),
source_name: CapabilityName::try_from("resolver").unwrap(),
target_name: CapabilityName::try_from("resolver").unwrap(),
target: OfferTarget::Child("childB".to_string()),
})],
children: vec![child_a.clone(), child_b.clone()],
..default_component_decl()
};
let mut expected = vec![
(
DependencyNode::Child(child_a.name.clone()),
vec![DependencyNode::Child(child_b.name.clone())],
),
(DependencyNode::Child(child_b.name.clone()), vec![]),
];
expected.sort_unstable();
validate_results(expected, process_component_dependencies(&decl));
}
}