use crate::modular::types::{
BasemgrResult, KillSessionResult, RestartSessionResult, StartBasemgrRequest,
};
use anyhow::{format_err, Error};
use fuchsia_component::client::connect_to_protocol;
use serde_json::{from_value, Value};
use {
fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_session as fsession,
fidl_fuchsia_sys2 as fsys,
};
const SESSION_PARENT_MONIKER: &str = "./core/session-manager";
const SESSION_COLLECTION_NAME: &str = "session";
const SESSION_CHILD_NAME: &str = "session";
const SESSION_MONIKER: &str = "./core/session-manager/session:session";
#[derive(Debug)]
pub struct ModularFacade {
session_launcher: fsession::LauncherProxy,
session_restarter: fsession::RestarterProxy,
lifecycle_controller: fsys::LifecycleControllerProxy,
realm_query: fsys::RealmQueryProxy,
}
impl ModularFacade {
pub fn new() -> ModularFacade {
let session_launcher = connect_to_protocol::<fsession::LauncherMarker>()
.expect("failed to connect to fuchsia.session.Launcher");
let session_restarter = connect_to_protocol::<fsession::RestarterMarker>()
.expect("failed to connect to fuchsia.session.Restarter");
let lifecycle_controller = connect_to_protocol::<fsys::LifecycleControllerMarker>()
.expect("failed to connect to fuchsia.sys2.LifecycleController");
let realm_query =
fuchsia_component::client::connect_to_protocol::<fsys::RealmQueryMarker>()
.expect("failed to connect to fuchsia.sys2.RealmQuery");
ModularFacade { session_launcher, session_restarter, lifecycle_controller, realm_query }
}
pub fn new_with_proxies(
session_launcher: fsession::LauncherProxy,
session_restarter: fsession::RestarterProxy,
lifecycle_controller: fsys::LifecycleControllerProxy,
realm_query: fsys::RealmQueryProxy,
) -> ModularFacade {
ModularFacade { session_launcher, session_restarter, lifecycle_controller, realm_query }
}
pub async fn is_session_running(&self) -> Result<bool, Error> {
Ok(self
.realm_query
.get_instance(SESSION_MONIKER)
.await?
.ok()
.and_then(|instance| instance.resolved_info)
.and_then(|info| info.execution_info)
.is_some())
}
pub async fn restart_session(&self) -> Result<RestartSessionResult, Error> {
if self.session_restarter.restart().await?.is_err() {
return Ok(RestartSessionResult::Fail);
}
Ok(RestartSessionResult::Success)
}
pub async fn stop_session(&self) -> Result<KillSessionResult, Error> {
if !self.is_session_running().await? {
return Ok(KillSessionResult::NoSessionRunning);
}
self.lifecycle_controller
.destroy_instance(
SESSION_PARENT_MONIKER,
&fdecl::ChildRef {
name: SESSION_CHILD_NAME.to_string(),
collection: Some(SESSION_COLLECTION_NAME.to_string()),
},
)
.await?
.map_err(|err| format_err!("failed to destroy session: {:?}", err))?;
Ok(KillSessionResult::Success)
}
pub async fn start_session(&self, args: Value) -> Result<BasemgrResult, Error> {
let req: StartBasemgrRequest = from_value(args)?;
if self.is_session_running().await? {
self.stop_session().await?;
}
self.launch_session(&req.session_url).await?;
Ok(BasemgrResult::Success)
}
async fn launch_session(&self, session_url: &str) -> Result<(), Error> {
let config = fsession::LaunchConfiguration {
session_url: Some(session_url.to_string()),
..Default::default()
};
self.session_launcher
.launch(&config)
.await?
.map_err(|err| format_err!("failed to launch session: {:?}", err))
}
}
#[cfg(test)]
mod tests {
use crate::modular::facade::{
ModularFacade, SESSION_CHILD_NAME, SESSION_COLLECTION_NAME, SESSION_MONIKER,
SESSION_PARENT_MONIKER,
};
use crate::modular::types::{BasemgrResult, KillSessionResult, RestartSessionResult};
use anyhow::Error;
use assert_matches::assert_matches;
use fidl::endpoints::spawn_stream_handler;
use futures::Future;
use lazy_static::lazy_static;
use serde_json::json;
use test_util::Counter;
use {fidl_fuchsia_session as fsession, fidl_fuchsia_sys2 as fsys};
const TEST_SESSION_URL: &str = "fuchsia-pkg://fuchsia.com/test_session#meta/test_session.cm";
async fn test_facade<Fut>(
run_facade_fns: impl Fn(ModularFacade) -> Fut,
is_session_running: bool,
expected_launch_count: usize,
expected_destroy_count: usize,
expected_restart_count: usize,
) -> Result<(), Error>
where
Fut: Future<Output = Result<(), Error>>,
{
lazy_static! {
static ref SESSION_LAUNCH_CALL_COUNT: Counter = Counter::new(0);
static ref DESTROY_CHILD_CALL_COUNT: Counter = Counter::new(0);
static ref SESSION_RESTART_CALL_COUNT: Counter = Counter::new(0);
}
let session_launcher = spawn_stream_handler(move |launcher_request| async move {
match launcher_request {
fsession::LauncherRequest::Launch { configuration, responder } => {
assert!(configuration.session_url.is_some());
let session_url = configuration.session_url.unwrap();
assert!(session_url == TEST_SESSION_URL.to_string());
SESSION_LAUNCH_CALL_COUNT.inc();
let _ = responder.send(Ok(()));
}
}
});
let session_restarter = spawn_stream_handler(move |restarter_request| async move {
match restarter_request {
fsession::RestarterRequest::Restart { responder } => {
SESSION_RESTART_CALL_COUNT.inc();
let _ = responder.send(Ok(()));
}
}
});
let lifecycle_controller = spawn_stream_handler(|lifecycle_controller_request| async {
match lifecycle_controller_request {
fsys::LifecycleControllerRequest::DestroyInstance {
parent_moniker,
child,
responder,
} => {
assert_eq!(parent_moniker, SESSION_PARENT_MONIKER.to_string());
assert_eq!(child.name, SESSION_CHILD_NAME);
assert_eq!(child.collection.unwrap(), SESSION_COLLECTION_NAME);
DESTROY_CHILD_CALL_COUNT.inc();
let _ = responder.send(Ok(()));
}
r => {
panic!("didn't expect request: {:?}", r)
}
}
});
let realm_query = spawn_stream_handler(move |realm_query_request| async move {
match realm_query_request {
fsys::RealmQueryRequest::GetInstance { moniker, responder } => {
assert_eq!(moniker, SESSION_MONIKER.to_string());
let instance = if is_session_running {
fsys::Instance {
moniker: Some(SESSION_MONIKER.to_string()),
url: Some("fake".to_string()),
instance_id: None,
resolved_info: Some(fsys::ResolvedInfo {
resolved_url: Some("fake".to_string()),
execution_info: Some(fsys::ExecutionInfo {
start_reason: Some("fake".to_string()),
..Default::default()
}),
..Default::default()
}),
..Default::default()
}
} else {
fsys::Instance {
moniker: Some(SESSION_MONIKER.to_string()),
url: Some("fake".to_string()),
instance_id: None,
resolved_info: None,
..Default::default()
}
};
let _ = responder.send(Ok(&instance));
}
r => {
panic!("didn't expect request: {:?}", r)
}
}
});
let facade = ModularFacade::new_with_proxies(
session_launcher,
session_restarter,
lifecycle_controller,
realm_query,
);
run_facade_fns(facade).await?;
assert_eq!(
SESSION_LAUNCH_CALL_COUNT.get(),
expected_launch_count,
"SESSION_LAUNCH_CALL_COUNT"
);
assert_eq!(
DESTROY_CHILD_CALL_COUNT.get(),
expected_destroy_count,
"DESTROY_CHILD_CALL_COUNT"
);
assert_eq!(
SESSION_RESTART_CALL_COUNT.get(),
expected_restart_count,
"SESSION_RESTART_CALL_COUNT"
);
Ok(())
}
#[fuchsia_async::run(2, test)]
async fn test_stop_session() -> Result<(), Error> {
async fn stop_session_steps(facade: ModularFacade) -> Result<(), Error> {
assert_matches!(facade.is_session_running().await, Ok(true));
assert_matches!(facade.stop_session().await, Ok(KillSessionResult::Success));
Ok(())
}
test_facade(
&stop_session_steps,
true, 0, 1, 0, )
.await
}
#[fuchsia_async::run(2, test)]
async fn test_start_session_without_config() -> Result<(), Error> {
async fn start_session_v2_steps(facade: ModularFacade) -> Result<(), Error> {
let start_session_args = json!({
"session_url": TEST_SESSION_URL,
});
assert_matches!(
facade.start_session(start_session_args).await,
Ok(BasemgrResult::Success)
);
Ok(())
}
test_facade(
&start_session_v2_steps,
false, 1, 0, 0, )
.await
}
#[fuchsia_async::run(2, test)]
async fn test_start_session_shutdown_existing() -> Result<(), Error> {
async fn start_existing_steps(facade: ModularFacade) -> Result<(), Error> {
let start_session_args = json!({
"session_url": TEST_SESSION_URL,
});
assert_matches!(facade.is_session_running().await, Ok(true));
assert_matches!(
facade.start_session(start_session_args).await,
Ok(BasemgrResult::Success)
);
Ok(())
}
test_facade(
&start_existing_steps,
true, 1, 1, 0, )
.await
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_is_session_running_not_running() -> Result<(), Error> {
async fn not_running_steps(facade: ModularFacade) -> Result<(), Error> {
assert_matches!(facade.is_session_running().await, Ok(false));
Ok(())
}
test_facade(
¬_running_steps,
false, 0, 0, 0, )
.await
}
#[fuchsia_async::run(2, test)]
async fn test_is_session_running() -> Result<(), Error> {
async fn is_running_steps(facade: ModularFacade) -> Result<(), Error> {
assert_matches!(facade.is_session_running().await, Ok(true));
Ok(())
}
test_facade(
&is_running_steps,
true, 0, 0, 0, )
.await
}
#[fuchsia_async::run_singlethreaded(test)]
async fn test_restart_session() -> Result<(), Error> {
async fn restart_steps(facade: ModularFacade) -> Result<(), Error> {
assert_matches!(facade.restart_session().await, Ok(RestartSessionResult::Success));
Ok(())
}
test_facade(
&restart_steps,
true, 0, 0, 1, )
.await
}
#[fuchsia_async::run(2, test)]
async fn test_restart_session_does_not_destroy() -> Result<(), Error> {
async fn restart_steps(facade: ModularFacade) -> Result<(), Error> {
assert_matches!(facade.is_session_running().await, Ok(true));
assert_matches!(facade.restart_session().await, Ok(RestartSessionResult::Success));
Ok(())
}
test_facade(
&restart_steps,
true, 0, 0, 1, )
.await
}
}