blob: fb832e2f40233f91c8b89d94a6c5de581d64244f [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, CapabilitySource, FrameworkCapability, InternalCapabilityProvider,
},
model::{
component::instance::ResolvedInstanceState,
component::{ComponentInstance, WeakComponentInstance},
model::Model,
routing::{self, service::AnonymizedServiceRoute, Route, RouteRequest, RoutingError},
},
},
::routing::capability_source::InternalCapability,
async_trait::async_trait,
cm_rust::{ExposeDecl, SourceName, UseDecl},
cm_types::Name,
fidl::endpoints::{DiscoverableProtocolMarker, ServerEnd},
fidl_fuchsia_component as fcomponent, fidl_fuchsia_sys2 as fsys, fuchsia_zircon as zx,
futures::{future::join_all, TryStreamExt},
lazy_static::lazy_static,
moniker::{ExtendedMoniker, Moniker, MonikerBase},
std::{
cmp::Ordering,
sync::{Arc, Weak},
},
tracing::warn,
};
lazy_static! {
static ref CAPABILITY_NAME: Name = fsys::RouteValidatorMarker::PROTOCOL_NAME.parse().unwrap();
}
/// Serves the fuchsia.sys2.RouteValidator protocol.
pub struct RouteValidator {
model: Weak<Model>,
}
impl RouteValidator {
fn new(model: Weak<Model>) -> Arc<Self> {
Arc::new(Self { model })
}
async fn validate(
model: &Model,
scope_moniker: &Moniker,
moniker_str: &str,
) -> Result<Vec<fsys::RouteReport>, fcomponent::Error> {
// Construct the complete moniker using the scope moniker and the moniker string.
let moniker =
Moniker::try_from(moniker_str).map_err(|_| fcomponent::Error::InvalidArguments)?;
let moniker = scope_moniker.concat(&moniker);
let instance =
model.root().find(&moniker).await.ok_or(fcomponent::Error::InstanceNotFound)?;
// Get all use and expose declarations for this component
let (uses, exposes) = {
let state = instance.lock_state().await;
// TODO(https://fxbug.dev/42052917): The error is that the instance is not currently
// resolved. Use a better error here, when one exists.
let resolved =
state.get_resolved_state().ok_or(fcomponent::Error::InstanceCannotResolve)?;
let uses = resolved.decl().uses.clone();
let exposes = resolved.decl().exposes.clone();
(uses, exposes)
};
let mut reports = validate_uses(uses, &instance).await;
let mut expose_reports = validate_exposes(exposes, &instance).await;
reports.append(&mut expose_reports);
Ok(reports)
}
async fn route(
model: &Model,
scope_moniker: &Moniker,
moniker_str: &str,
targets: Vec<fsys::RouteTarget>,
) -> Result<Vec<fsys::RouteReport>, fsys::RouteValidatorError> {
// Construct the complete moniker using the scope moniker and the moniker string.
let moniker = Moniker::try_from(moniker_str)
.map_err(|_| fsys::RouteValidatorError::InvalidArguments)?;
let moniker = scope_moniker.concat(&moniker);
let instance = model
.root()
.find_and_maybe_resolve(&moniker)
.await
.map_err(|_| fsys::RouteValidatorError::InstanceNotFound)?;
let state = instance.lock_state().await;
// TODO(https://fxbug.dev/42052917): The error is that the instance is not currently
// resolved. Use a better error here, when one exists.
let resolved =
state.get_resolved_state().ok_or(fsys::RouteValidatorError::InstanceNotResolved)?;
let route_requests = Self::generate_route_requests(&resolved, targets)?;
drop(state);
let route_futs = route_requests.into_iter().map(|pair| async {
let (target, request) = pair;
let capability = Some(target.name.into());
let decl_type = Some(target.decl_type);
match Self::route_instance(scope_moniker, request, &instance).await {
Ok(info) => {
let (source_moniker, service_instances) = info;
fsys::RouteReport {
capability,
decl_type,
error: None,
source_moniker: Some(source_moniker),
service_instances,
..Default::default()
}
}
Err(e) => {
let error = Some(fsys::RouteError {
summary: Some(e.to_string()),
..Default::default()
});
fsys::RouteReport { capability, decl_type, error, ..Default::default() }
}
}
});
Ok(join_all(route_futs).await)
}
fn generate_route_requests(
resolved: &ResolvedInstanceState,
targets: Vec<fsys::RouteTarget>,
) -> Result<Vec<(fsys::RouteTarget, RouteRequest)>, fsys::RouteValidatorError> {
if targets.is_empty() {
let use_requests = resolved.decl().uses.iter().map(|use_| {
let target = fsys::RouteTarget {
name: use_.source_name().as_str().into(),
decl_type: fsys::DeclType::Use,
};
let request = use_.clone().into();
(target, request)
});
let exposes = routing::aggregate_exposes(resolved.decl().exposes.iter());
let expose_requests = exposes.into_iter().map(|(target_name, e)| {
let target = fsys::RouteTarget {
name: target_name.to_string(),
decl_type: fsys::DeclType::Expose,
};
let request = e.into();
(target, request)
});
Ok(use_requests.chain(expose_requests).collect())
} else {
// Return results that fuzzy match (substring match) `target.name`.
let targets = targets
.into_iter()
.map(|target| match target.decl_type {
fsys::DeclType::Any => {
let mut use_target = target.clone();
use_target.decl_type = fsys::DeclType::Use;
let mut expose_target = target.clone();
expose_target.decl_type = fsys::DeclType::Expose;
vec![use_target, expose_target].into_iter()
}
_ => vec![target].into_iter(),
})
.flatten();
targets
.map(|target| match target.decl_type {
fsys::DeclType::Use => {
let matching_requests: Vec<_> = resolved
.decl()
.uses
.iter()
.filter_map(|u| {
if !u.source_name().as_str().contains(&target.name) {
return None;
}
// This could be a fuzzy match so update the capability name.
let target = fsys::RouteTarget {
name: u.source_name().to_string(),
decl_type: target.decl_type,
};
let request = u.clone().into();
Some(Ok((target, request)))
})
.collect();
matching_requests.into_iter()
}
fsys::DeclType::Expose => {
let exposes = routing::aggregate_exposes(resolved.decl().exposes.iter());
let matching_requests: Vec<_> = exposes
.into_iter()
.filter_map(|(target_name, e)| {
if !target_name.as_str().contains(&target.name) {
return None;
}
// This could be a fuzzy match so update the capability name.
let target = fsys::RouteTarget {
name: target_name.to_string(),
decl_type: target.decl_type,
};
let request = e.into();
Some(Ok((target, request)))
})
.collect();
matching_requests.into_iter()
}
fsys::DeclType::Any => unreachable!("Any was expanded"),
fsys::DeclTypeUnknown!() => {
vec![Err(fsys::RouteValidatorError::InvalidArguments)].into_iter()
}
})
.flatten()
.collect()
}
}
/// Serve the fuchsia.sys2.RouteValidator protocol for a given scope on a given stream
async fn serve(
self: Arc<Self>,
scope_moniker: Moniker,
mut stream: fsys::RouteValidatorRequestStream,
) {
let res: Result<(), fidl::Error> = async move {
while let Some(request) = stream.try_next().await? {
let Some(model) = self.model.upgrade() else {
return Ok(());
};
match request {
fsys::RouteValidatorRequest::Validate { moniker, responder } => {
let result = Self::validate(&model, &scope_moniker, &moniker).await;
if let Err(e) = responder.send(result.as_deref().map_err(|e| *e)) {
warn!(error = %e, "RouteValidator failed to send Validate response");
}
}
fsys::RouteValidatorRequest::Route { moniker, targets, responder } => {
let result = Self::route(&model, &scope_moniker, &moniker, targets).await;
if let Err(e) = responder.send(result.as_deref().map_err(|e| *e)) {
warn!(error = %e, "RouteValidator failed to send Route response");
}
}
}
}
Ok(())
}
.await;
if let Err(e) = &res {
warn!(error = %e, "RouteValidator server failed");
}
}
/// Routes `request` from component `target` to the source.
///
/// Returns information to populate in `fuchsia.sys2.RouteReport`.
async fn route_instance(
scope_moniker: &Moniker,
request: RouteRequest,
target: &Arc<ComponentInstance>,
) -> Result<(String, Option<Vec<fsys::ServiceInstance>>), RoutingError> {
let source = request.route(target).await?;
let source = &source.source;
let service_dir = match source {
CapabilitySource::AnonymizedAggregate { capability, component, members, .. } => {
let component = component.upgrade()?;
let route = AnonymizedServiceRoute {
source_moniker: component.moniker.clone(),
members: members.clone(),
service_name: capability.source_name().clone(),
};
let state = component.lock_state().await;
state.get_resolved_state().and_then(|r| r.anonymized_services.get(&route).cloned())
}
_ => None,
};
let moniker = Self::extended_moniker_to_str(
scope_moniker,
source.source_instance().extended_moniker(),
);
let service_info = match service_dir {
Some(service_dir) => {
let mut service_info: Vec<_> = service_dir.entries().await;
// Sort the entries (they can show up in any order)
service_info.sort_by(|a, b| match a.source_id.cmp(&b.source_id) {
Ordering::Equal => a.service_instance.cmp(&b.service_instance),
o => o,
});
let service_info = service_info
.into_iter()
.map(|e| {
let child_name = format!("{}", e.source_id);
fsys::ServiceInstance {
instance_name: Some(e.name.clone()),
child_name: Some(child_name),
child_instance_name: Some(e.service_instance.to_string()),
..Default::default()
}
})
.collect();
Some(service_info)
}
None => None,
};
Ok((moniker, service_info))
}
fn extended_moniker_to_str(scope_moniker: &Moniker, m: ExtendedMoniker) -> String {
match m {
ExtendedMoniker::ComponentManager => m.to_string(),
ExtendedMoniker::ComponentInstance(m) => match m.strip_prefix(scope_moniker) {
Ok(r) => r.to_string(),
Err(_) => "<above scope>".to_string(),
},
}
}
}
pub struct RouteValidatorFrameworkCapability {
host: Arc<RouteValidator>,
}
impl RouteValidatorFrameworkCapability {
pub fn new(model: Weak<Model>) -> Self {
Self { host: RouteValidator::new(model) }
}
}
impl FrameworkCapability for RouteValidatorFrameworkCapability {
fn matches(&self, capability: &InternalCapability) -> bool {
capability.matches_protocol(&CAPABILITY_NAME)
}
fn new_provider(
&self,
scope: WeakComponentInstance,
_target: WeakComponentInstance,
) -> Box<dyn CapabilityProvider> {
Box::new(RouteValidatorCapabilityProvider::new(self.host.clone(), scope.moniker.clone()))
}
}
pub struct RouteValidatorCapabilityProvider {
query: Arc<RouteValidator>,
scope_moniker: Moniker,
}
impl RouteValidatorCapabilityProvider {
fn new(query: Arc<RouteValidator>, scope_moniker: Moniker) -> Self {
Self { query, scope_moniker }
}
}
#[async_trait]
impl InternalCapabilityProvider for RouteValidatorCapabilityProvider {
async fn open_protocol(self: Box<Self>, server_end: zx::Channel) {
let server_end = ServerEnd::<fsys::RouteValidatorMarker>::new(server_end);
self.query.serve(self.scope_moniker, server_end.into_stream().unwrap()).await;
}
}
async fn validate_uses(
uses: Vec<UseDecl>,
instance: &Arc<ComponentInstance>,
) -> Vec<fsys::RouteReport> {
let mut reports = vec![];
for use_ in uses {
let capability = Some(use_.source_name().to_string());
let decl_type = Some(fsys::DeclType::Use);
let route_request = RouteRequest::from(use_);
let error = if let Err(e) = route_request.route(&instance).await {
Some(fsys::RouteError { summary: Some(e.to_string()), ..Default::default() })
} else {
None
};
reports.push(fsys::RouteReport { capability, decl_type, error, ..Default::default() })
}
reports
}
async fn validate_exposes(
exposes: Vec<ExposeDecl>,
instance: &Arc<ComponentInstance>,
) -> Vec<fsys::RouteReport> {
let mut reports = vec![];
let exposes = routing::aggregate_exposes(exposes.iter());
for (target_name, e) in exposes {
let capability = Some(target_name.to_string());
let decl_type = Some(fsys::DeclType::Expose);
let route_request = RouteRequest::from(e);
let error = if let Err(e) = route_request.route(instance).await {
Some(fsys::RouteError { summary: Some(e.to_string()), ..Default::default() })
} else {
None
};
reports.push(fsys::RouteReport { capability, decl_type, error, ..Default::default() })
}
reports
}
#[cfg(test)]
mod tests {
use {
super::*,
crate::model::{
component::StartReason,
start::Start,
structured_dict::ComponentInput,
testing::{
out_dir::OutDir,
test_helpers::{TestEnvironmentBuilder, TestModelResult},
},
},
assert_matches::assert_matches,
cm_rust::*,
cm_rust_testing::*,
fidl::endpoints,
fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_io as fio, fuchsia_async as fasync,
};
#[derive(Ord, PartialOrd, Eq, PartialEq)]
struct Key {
capability: String,
decl_type: fsys::DeclType,
}
#[fuchsia::test]
async fn validate() {
let use_from_framework_decl = UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm")
.build();
let use_from_child_decl = UseBuilder::protocol()
.source(UseSource::Child("my_child".into()))
.name("foo.bar")
.build();
let expose_from_child_decl = ExposeBuilder::protocol()
.name("foo.bar")
.source(ExposeSource::Child("my_child".to_string()))
.build();
let expose_from_self_decl =
ExposeBuilder::protocol().name("foo.bar").source(ExposeSource::Self_).build();
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.use_(use_from_framework_decl)
.use_(use_from_child_decl)
.expose(expose_from_child_decl)
.child_default("my_child")
.build(),
),
(
"my_child",
ComponentDeclBuilder::new()
.protocol_default("foo.bar")
.expose(expose_from_self_decl)
.build(),
),
];
let TestModelResult { model, builtin_environment: _b, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
let validator_server = RouteValidator::new(Arc::downgrade(&model));
let (validator, request_stream) =
endpoints::create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
validator_server.serve(Moniker::root(), request_stream).await
});
model.start(ComponentInput::default()).await;
// `my_child` should not be resolved right now
let instance = model.root().find_resolved(&vec!["my_child"].try_into().unwrap()).await;
assert!(instance.is_none());
// Validate the root
let mut results = validator.validate(".").await.unwrap().unwrap();
results.sort_by_key(|r| Key {
capability: r.capability.clone().unwrap(),
decl_type: r.decl_type.clone().unwrap(),
});
assert_eq!(results.len(), 3);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: None,
error: None,
..
} if s == "foo.bar"
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: None,
error: None,
..
} if s == "foo.bar"
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: None,
error: None,
..
} if s == "fuchsia.component.Realm"
);
// This validation should have caused `my_child` to be resolved
let instance = model.root().find_resolved(&vec!["my_child"].try_into().unwrap()).await;
assert!(instance.is_some());
// Validate `my_child`
let mut results = validator.validate("my_child").await.unwrap().unwrap();
assert_eq!(results.len(), 1);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: None,
error: None,
..
} if s == "foo.bar"
);
}
#[fuchsia::test]
async fn validate_error() {
let invalid_source_name_use_from_child_decl =
UseBuilder::protocol().source(UseSource::Child("my_child".into())).name("a").build();
let invalid_source_name_expose_from_child_decl = ExposeBuilder::protocol()
.name("c")
.source(ExposeSource::Child("my_child".to_string()))
.build();
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.use_(invalid_source_name_use_from_child_decl)
.expose(invalid_source_name_expose_from_child_decl)
.child_default("my_child")
.build(),
),
("my_child", ComponentDeclBuilder::new().build()),
];
let TestModelResult { model, builtin_environment: _b, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
let validator_server = RouteValidator::new(Arc::downgrade(&model));
let (validator, validator_request_stream) =
endpoints::create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
validator_server.serve(Moniker::root(), validator_request_stream).await
});
model.start(ComponentInput::default()).await;
// `my_child` should not be resolved right now
let instance = model.root().find_resolved(&vec!["my_child"].try_into().unwrap()).await;
assert!(instance.is_none());
// Validate the root
let mut results = validator.validate(".").await.unwrap().unwrap();
assert_eq!(results.len(), 2);
results.sort_by_key(|r| Key {
capability: r.capability.clone().unwrap(),
decl_type: r.decl_type.clone().unwrap(),
});
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: None,
error: Some(_),
..
} if s == "a"
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: None,
error: Some(_),
..
} if s == "c"
);
// This validation should have caused `my_child` to be resolved
let instance = model.root().find_resolved(&vec!["my_child"].try_into().unwrap()).await;
assert!(instance.is_some());
}
#[fuchsia::test]
async fn route() {
let use_from_framework_decl = UseBuilder::protocol()
.source(UseSource::Framework)
.name("fuchsia.component.Realm")
.build();
let use_from_child_decl = UseBuilder::protocol()
.source(UseSource::Child("my_child".into()))
.name("biz.buz")
.path("/svc/foo.bar")
.build();
let expose_from_child_decl = ExposeBuilder::protocol()
.name("biz.buz")
.target_name("foo.bar")
.source(ExposeSource::Child("my_child".into()))
.build();
let expose_from_self_decl =
ExposeBuilder::protocol().name("biz.buz").source(ExposeSource::Self_).build();
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.use_(use_from_framework_decl)
.use_(use_from_child_decl)
.expose(expose_from_child_decl)
.child_default("my_child")
.build(),
),
(
"my_child",
ComponentDeclBuilder::new()
.capability(
CapabilityBuilder::protocol().name("biz.buz").path("/svc/foo.bar").build(),
)
.expose(expose_from_self_decl)
.build(),
),
];
let TestModelResult { model, builtin_environment: _b, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
let validator_server = RouteValidator::new(Arc::downgrade(&model));
let (validator, request_stream) =
endpoints::create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
validator_server.serve(Moniker::root(), request_stream).await
});
model.start(ComponentInput::default()).await;
// Validate the root
let targets = &[
fsys::RouteTarget { name: "biz.buz".parse().unwrap(), decl_type: fsys::DeclType::Use },
fsys::RouteTarget {
name: "foo.bar".parse().unwrap(),
decl_type: fsys::DeclType::Expose,
},
fsys::RouteTarget {
name: "fuchsia.component.Realm".parse().unwrap(),
decl_type: fsys::DeclType::Use,
},
];
let mut results = validator.route(".", targets).await.unwrap().unwrap();
assert_eq!(results.len(), 3);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: Some(m),
error: None,
..
} if s == "biz.buz" && m == "my_child"
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: Some(m),
error: None,
..
} if s == "foo.bar" && m == "my_child"
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: Some(m),
error: None,
..
} if s == "fuchsia.component.Realm" && m == "."
);
// Validate `my_child`
let targets = &[fsys::RouteTarget {
name: "biz.buz".parse().unwrap(),
decl_type: fsys::DeclType::Expose,
}];
let mut results = validator.route("my_child", targets).await.unwrap().unwrap();
assert_eq!(results.len(), 1);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: Some(m),
error: None,
..
} if s == "biz.buz" && m == "my_child"
);
}
#[fuchsia::test]
async fn route_all() {
let use_from_framework_decl =
UseBuilder::protocol().source(UseSource::Framework).name("foo.bar").build();
let expose_from_child_decl = ExposeBuilder::resolver()
.name("qax.qux")
.target_name("foo.buz")
.source(ExposeSource::Child("my_child".into()))
.build();
let expose_from_self_decl =
ExposeBuilder::resolver().name("qax.qux").source(ExposeSource::Self_).build();
let capability_decl =
CapabilityBuilder::resolver().name("qax.qux").path("/svc/qax.qux").build();
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.use_(use_from_framework_decl)
.expose(expose_from_child_decl)
.child_default("my_child")
.build(),
),
(
"my_child",
ComponentDeclBuilder::new()
.capability(capability_decl)
.expose(expose_from_self_decl)
.build(),
),
];
let TestModelResult { model, builtin_environment: _b, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
let validator_server = RouteValidator::new(Arc::downgrade(&model));
let (validator, request_stream) =
endpoints::create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
validator_server.serve(Moniker::root(), request_stream).await
});
model.start(ComponentInput::default()).await;
// Validate the root, passing an empty vector. This should match both capabilities
let mut results = validator.route(".", &[]).await.unwrap().unwrap();
assert_eq!(results.len(), 2);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: Some(m),
error: None,
..
} if s == "foo.bar" && m == "."
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: Some(m),
error: None,
..
} if s == "foo.buz" && m == "my_child"
);
}
#[fuchsia::test]
async fn route_fuzzy() {
let use_from_framework_decl =
UseBuilder::protocol().source(UseSource::Framework).name("foo.bar").build();
let use_from_framework_decl2 =
UseBuilder::protocol().source(UseSource::Framework).name("foo.buz").build();
let use_from_framework_decl3 =
UseBuilder::protocol().source(UseSource::Framework).name("no.match").build();
let expose_from_child_decl = ExposeBuilder::protocol()
.name("qax.qux")
.target_name("foo.buz")
.source(ExposeSource::Child("my_child".into()))
.build();
let expose_from_child_decl2 = ExposeBuilder::protocol()
.name("qax.qux")
.target_name("foo.biz")
.source(ExposeSource::Child("my_child".into()))
.build();
let expose_from_child_decl3 =
ExposeBuilder::protocol().name("no.match").source(ExposeSource::Framework).build();
let expose_from_self_decl =
ExposeBuilder::protocol().name("qax.qux").source(ExposeSource::Self_).build();
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.use_(use_from_framework_decl)
.use_(use_from_framework_decl2)
.use_(use_from_framework_decl3)
.expose(expose_from_child_decl)
.expose(expose_from_child_decl2)
.expose(expose_from_child_decl3)
.child_default("my_child")
.build(),
),
(
"my_child",
ComponentDeclBuilder::new()
.protocol_default("qax.qux")
.expose(expose_from_self_decl)
.build(),
),
];
let TestModelResult { model, builtin_environment: _b, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
let validator_server = RouteValidator::new(Arc::downgrade(&model));
let (validator, request_stream) =
endpoints::create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
validator_server.serve(Moniker::root(), request_stream).await
});
model.start(ComponentInput::default()).await;
// Validate the root
let targets =
&[fsys::RouteTarget { name: "foo.".parse().unwrap(), decl_type: fsys::DeclType::Any }];
let mut results = validator.route(".", targets).await.unwrap().unwrap();
assert_eq!(results.len(), 4);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: Some(m),
error: None,
..
} if s == "foo.bar" && m == "."
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: Some(m),
error: None,
..
} if s == "foo.buz" && m == "."
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: Some(m),
error: None,
..
} if s == "foo.biz" && m == "my_child"
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: Some(m),
error: None,
..
} if s == "foo.buz" && m == "my_child"
);
}
#[fuchsia::test]
async fn route_service() {
let offer_from_collection_decl = OfferBuilder::service()
.name("my_service")
.source(OfferSource::Collection("coll".parse().unwrap()))
.target(OfferTarget::static_child("target".into()))
.build();
let expose_from_self_decl =
ExposeBuilder::service().name("my_service").source(ExposeSource::Self_).build();
let use_decl = UseBuilder::service().name("my_service").path("/svc/foo.bar").build();
let capability_decl =
CapabilityBuilder::service().name("my_service").path("/svc/foo.bar").build();
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.offer(offer_from_collection_decl)
.collection_default("coll")
.child_default("target")
.build(),
),
("target", ComponentDeclBuilder::new().use_(use_decl).build()),
(
"child_a",
ComponentDeclBuilder::new()
.capability(capability_decl.clone())
.expose(expose_from_self_decl.clone())
.build(),
),
(
"child_b",
ComponentDeclBuilder::new()
.capability(capability_decl.clone())
.expose(expose_from_self_decl.clone())
.build(),
),
];
let TestModelResult { model, realm_proxy, mock_runner, builtin_environment: _b, .. } =
TestEnvironmentBuilder::new()
.set_components(components)
.set_realm_moniker(Moniker::root())
.build()
.await;
let realm_proxy = realm_proxy.unwrap();
let validator_server = RouteValidator::new(Arc::downgrade(&model));
let (validator, request_stream) =
endpoints::create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
validator_server.serve(Moniker::root(), request_stream).await
});
model.start(ComponentInput::default()).await;
// Create two children in the collection, each exposing `my_service` with two instances.
let collection_ref = fdecl::CollectionRef { name: "coll".parse().unwrap() };
for name in &["child_a", "child_b"] {
realm_proxy
.create_child(
&collection_ref,
&child_decl(name),
fcomponent::CreateChildArgs::default(),
)
.await
.unwrap()
.unwrap();
let mut out_dir = OutDir::new();
out_dir.add_echo_protocol("/svc/foo.bar/instance_a/echo".parse().unwrap());
out_dir.add_echo_protocol("/svc/foo.bar/instance_b/echo".parse().unwrap());
mock_runner.add_host_fn(&format!("test:///{}_resolved", name), out_dir.host_fn());
let child = model
.root()
.find_and_maybe_resolve(&format!("coll:{}", name).as_str().try_into().unwrap())
.await
.unwrap();
child.ensure_started(&StartReason::Debug).await.unwrap();
}
// Open the service directory from `target` so that it gets instantiated.
{
let target =
model.root().find_and_maybe_resolve(&"target".try_into().unwrap()).await.unwrap();
target.ensure_started(&StartReason::Debug).await.unwrap();
mock_runner.wait_for_url("test:///target_resolved").await;
let ns = mock_runner.get_namespace("test:///target_resolved").unwrap();
let ns = ns.lock().await;
// /pkg and /svc
let mut ns = ns.clone().flatten();
ns.sort();
assert_eq!(ns.len(), 2);
let ns = ns.remove(1);
assert_eq!(ns.path.to_string(), "/svc");
let svc_dir = ns.directory.into_proxy().unwrap();
fuchsia_fs::directory::open_directory(&svc_dir, "foo.bar", fio::OpenFlags::empty())
.await
.unwrap();
}
let targets = &[fsys::RouteTarget {
name: "my_service".parse().unwrap(),
decl_type: fsys::DeclType::Use,
}];
let mut results = validator.route("target", targets).await.unwrap().unwrap();
assert_eq!(results.len(), 1);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: Some(m),
service_instances: Some(_),
error: None,
..
} if s == "my_service" && m == "."
);
let service_instances = report.service_instances.unwrap();
assert_eq!(service_instances.len(), 4);
// (child_id, instance_id)
let pairs = vec![("a", "a"), ("a", "b"), ("b", "a"), ("b", "b")];
for (service_instance, pair) in service_instances.into_iter().zip(pairs) {
let (child_id, instance_id) = pair;
assert_matches!(
service_instance,
fsys::ServiceInstance {
instance_name: Some(instance_name),
child_name: Some(child_name),
child_instance_name: Some(child_instance_name),
..
} if instance_name.len() == 32 &&
instance_name.chars().all(|c| c.is_ascii_hexdigit()) &&
child_name == format!("child `coll:child_{}`", child_id) &&
child_instance_name == format!("instance_{}", instance_id)
);
}
}
fn child_decl(name: &str) -> fdecl::Child {
fdecl::Child {
name: Some(name.to_owned()),
url: Some(format!("test:///{}", name)),
startup: Some(fdecl::StartupMode::Lazy),
..Default::default()
}
}
#[fuchsia::test]
async fn route_error() {
let invalid_source_name_use_from_child_decl =
UseBuilder::protocol().source(UseSource::Child("my_child".into())).name("a").build();
let invalid_source_name_expose_from_child_decl = ExposeBuilder::protocol()
.name("c")
.source(ExposeSource::Child("my_child".to_string()))
.build();
let components = vec![
(
"root",
ComponentDeclBuilder::new()
.use_(invalid_source_name_use_from_child_decl)
.expose(invalid_source_name_expose_from_child_decl)
.child_default("my_child")
.build(),
),
("my_child", ComponentDeclBuilder::new().build()),
];
let TestModelResult { model, builtin_environment: _b, .. } =
TestEnvironmentBuilder::new().set_components(components).build().await;
let validator_server = RouteValidator::new(Arc::downgrade(&model));
let (validator, request_stream) =
endpoints::create_proxy_and_stream::<fsys::RouteValidatorMarker>().unwrap();
let _validator_task = fasync::Task::local(async move {
validator_server.serve(Moniker::root(), request_stream).await
});
model.start(ComponentInput::default()).await;
// `my_child` should not be resolved right now
let instance = model.root().find_resolved(&vec!["my_child"].try_into().unwrap()).await;
assert!(instance.is_none());
let targets = &[
fsys::RouteTarget { name: "a".parse().unwrap(), decl_type: fsys::DeclType::Use },
fsys::RouteTarget { name: "c".parse().unwrap(), decl_type: fsys::DeclType::Expose },
];
let mut results = validator.route(".", targets).await.unwrap().unwrap();
assert_eq!(results.len(), 2);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Use),
source_moniker: None,
error: Some(_),
..
} if s == "a"
);
let report = results.remove(0);
assert_matches!(
report,
fsys::RouteReport {
capability: Some(s),
decl_type: Some(fsys::DeclType::Expose),
source_moniker: None,
error: Some(_),
..
} if s == "c"
);
}
}