1use crate::modular::types::{
5 BasemgrResult, KillSessionResult, RestartSessionResult, StartBasemgrRequest,
6};
7use anyhow::{Error, format_err};
8use fuchsia_component::client::connect_to_protocol;
9use serde_json::{Value, from_value};
10use {
11 fidl_fuchsia_component_decl as fdecl, fidl_fuchsia_session as fsession,
12 fidl_fuchsia_sys2 as fsys,
13};
14
15const SESSION_PARENT_MONIKER: &str = "./core/session-manager";
17const SESSION_COLLECTION_NAME: &str = "session";
18const SESSION_CHILD_NAME: &str = "session";
19const SESSION_MONIKER: &str = "./core/session-manager/session:session";
20
21#[derive(Debug)]
23pub struct ModularFacade {
24 session_launcher: fsession::LauncherProxy,
25 session_restarter: fsession::RestarterProxy,
26 lifecycle_controller: fsys::LifecycleControllerProxy,
27 realm_query: fsys::RealmQueryProxy,
28}
29
30impl ModularFacade {
31 pub fn new() -> ModularFacade {
32 let session_launcher = connect_to_protocol::<fsession::LauncherMarker>()
33 .expect("failed to connect to fuchsia.session.Launcher");
34 let session_restarter = connect_to_protocol::<fsession::RestarterMarker>()
35 .expect("failed to connect to fuchsia.session.Restarter");
36 let lifecycle_controller = connect_to_protocol::<fsys::LifecycleControllerMarker>()
37 .expect("failed to connect to fuchsia.sys2.LifecycleController");
38 let realm_query =
39 fuchsia_component::client::connect_to_protocol::<fsys::RealmQueryMarker>()
40 .expect("failed to connect to fuchsia.sys2.RealmQuery");
41 ModularFacade { session_launcher, session_restarter, lifecycle_controller, realm_query }
42 }
43
44 pub fn new_with_proxies(
45 session_launcher: fsession::LauncherProxy,
46 session_restarter: fsession::RestarterProxy,
47 lifecycle_controller: fsys::LifecycleControllerProxy,
48 realm_query: fsys::RealmQueryProxy,
49 ) -> ModularFacade {
50 ModularFacade { session_launcher, session_restarter, lifecycle_controller, realm_query }
51 }
52
53 pub async fn is_session_running(&self) -> Result<bool, Error> {
55 Ok(self
56 .realm_query
57 .get_instance(SESSION_MONIKER)
58 .await?
59 .ok()
60 .and_then(|instance| instance.resolved_info)
61 .and_then(|info| info.execution_info)
62 .is_some())
63 }
64
65 pub async fn restart_session(&self) -> Result<RestartSessionResult, Error> {
67 if self.session_restarter.restart().await?.is_err() {
68 return Ok(RestartSessionResult::Fail);
69 }
70 Ok(RestartSessionResult::Success)
71 }
72
73 pub async fn stop_session(&self) -> Result<KillSessionResult, Error> {
75 if !self.is_session_running().await? {
76 return Ok(KillSessionResult::NoSessionRunning);
77 }
78
79 self.lifecycle_controller
82 .destroy_instance(
83 SESSION_PARENT_MONIKER,
84 &fdecl::ChildRef {
85 name: SESSION_CHILD_NAME.to_string(),
86 collection: Some(SESSION_COLLECTION_NAME.to_string()),
87 },
88 )
89 .await?
90 .map_err(|err| format_err!("failed to destroy session: {:?}", err))?;
91
92 Ok(KillSessionResult::Success)
93 }
94
95 pub async fn start_session(&self, args: Value) -> Result<BasemgrResult, Error> {
104 let req: StartBasemgrRequest = from_value(args)?;
105
106 if self.is_session_running().await? {
108 self.stop_session().await?;
109 }
110
111 self.launch_session(&req.session_url).await?;
112
113 Ok(BasemgrResult::Success)
114 }
115
116 async fn launch_session(&self, session_url: &str) -> Result<(), Error> {
121 let config = fsession::LaunchConfiguration {
122 session_url: Some(session_url.to_string()),
123 ..Default::default()
124 };
125 self.session_launcher
126 .launch(&config)
127 .await?
128 .map_err(|err| format_err!("failed to launch session: {:?}", err))
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use crate::modular::facade::{
135 ModularFacade, SESSION_CHILD_NAME, SESSION_COLLECTION_NAME, SESSION_MONIKER,
136 SESSION_PARENT_MONIKER,
137 };
138 use crate::modular::types::{BasemgrResult, KillSessionResult, RestartSessionResult};
139 use anyhow::Error;
140 use assert_matches::assert_matches;
141 use fidl_test_util::spawn_stream_handler;
142 use futures::Future;
143 use serde_json::json;
144 use std::sync::LazyLock;
145 use test_util::Counter;
146 use {fidl_fuchsia_session as fsession, fidl_fuchsia_sys2 as fsys};
147
148 const TEST_SESSION_URL: &str = "fuchsia-pkg://fuchsia.com/test_session#meta/test_session.cm";
149
150 async fn test_facade<Fut>(
155 run_facade_fns: impl Fn(ModularFacade) -> Fut,
156 is_session_running: bool,
157 expected_launch_count: usize,
158 expected_destroy_count: usize,
159 expected_restart_count: usize,
160 ) -> Result<(), Error>
161 where
162 Fut: Future<Output = Result<(), Error>>,
163 {
164 static SESSION_LAUNCH_CALL_COUNT: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
165 static DESTROY_CHILD_CALL_COUNT: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
166 static SESSION_RESTART_CALL_COUNT: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
167
168 let session_launcher = spawn_stream_handler(move |launcher_request| async move {
169 match launcher_request {
170 fsession::LauncherRequest::Launch { configuration, responder } => {
171 assert!(configuration.session_url.is_some());
172 let session_url = configuration.session_url.unwrap();
173 assert!(session_url == TEST_SESSION_URL.to_string());
174
175 SESSION_LAUNCH_CALL_COUNT.inc();
176 let _ = responder.send(Ok(()));
177 }
178 }
179 });
180
181 let session_restarter = spawn_stream_handler(move |restarter_request| async move {
182 match restarter_request {
183 fsession::RestarterRequest::Restart { responder } => {
184 SESSION_RESTART_CALL_COUNT.inc();
185 let _ = responder.send(Ok(()));
186 }
187 }
188 });
189
190 let lifecycle_controller = spawn_stream_handler(|lifecycle_controller_request| async {
191 match lifecycle_controller_request {
192 fsys::LifecycleControllerRequest::DestroyInstance {
193 parent_moniker,
194 child,
195 responder,
196 } => {
197 assert_eq!(parent_moniker, SESSION_PARENT_MONIKER.to_string());
198 assert_eq!(child.name, SESSION_CHILD_NAME);
199 assert_eq!(child.collection.unwrap(), SESSION_COLLECTION_NAME);
200
201 DESTROY_CHILD_CALL_COUNT.inc();
202 let _ = responder.send(Ok(()));
203 }
204 r => {
205 panic!("didn't expect request: {:?}", r)
206 }
207 }
208 });
209
210 let realm_query = spawn_stream_handler(move |realm_query_request| async move {
211 match realm_query_request {
212 fsys::RealmQueryRequest::GetInstance { moniker, responder } => {
213 assert_eq!(moniker, SESSION_MONIKER.to_string());
214 let instance = if is_session_running {
215 fsys::Instance {
216 moniker: Some(SESSION_MONIKER.to_string()),
217 url: Some("fake".to_string()),
218 instance_id: None,
219 resolved_info: Some(fsys::ResolvedInfo {
220 resolved_url: Some("fake".to_string()),
221 execution_info: Some(fsys::ExecutionInfo {
222 start_reason: Some("fake".to_string()),
223 ..Default::default()
224 }),
225 ..Default::default()
226 }),
227 ..Default::default()
228 }
229 } else {
230 fsys::Instance {
231 moniker: Some(SESSION_MONIKER.to_string()),
232 url: Some("fake".to_string()),
233 instance_id: None,
234 resolved_info: None,
235 ..Default::default()
236 }
237 };
238 let _ = responder.send(Ok(&instance));
239 }
240 r => {
241 panic!("didn't expect request: {:?}", r)
242 }
243 }
244 });
245
246 let facade = ModularFacade::new_with_proxies(
247 session_launcher,
248 session_restarter,
249 lifecycle_controller,
250 realm_query,
251 );
252
253 run_facade_fns(facade).await?;
254
255 assert_eq!(
256 SESSION_LAUNCH_CALL_COUNT.get(),
257 expected_launch_count,
258 "SESSION_LAUNCH_CALL_COUNT"
259 );
260 assert_eq!(
261 DESTROY_CHILD_CALL_COUNT.get(),
262 expected_destroy_count,
263 "DESTROY_CHILD_CALL_COUNT"
264 );
265 assert_eq!(
266 SESSION_RESTART_CALL_COUNT.get(),
267 expected_restart_count,
268 "SESSION_RESTART_CALL_COUNT"
269 );
270
271 Ok(())
272 }
273
274 #[fuchsia_async::run(2, test)]
275 async fn test_stop_session() -> Result<(), Error> {
276 async fn stop_session_steps(facade: ModularFacade) -> Result<(), Error> {
277 assert_matches!(facade.is_session_running().await, Ok(true));
278 assert_matches!(facade.stop_session().await, Ok(KillSessionResult::Success));
279 Ok(())
280 }
281
282 test_facade(
283 &stop_session_steps,
284 true, 0, 1, 0, )
289 .await
290 }
291
292 #[fuchsia_async::run(2, test)]
293 async fn test_start_session_without_config() -> Result<(), Error> {
294 async fn start_session_v2_steps(facade: ModularFacade) -> Result<(), Error> {
295 let start_session_args = json!({
296 "session_url": TEST_SESSION_URL,
297 });
298 assert_matches!(
299 facade.start_session(start_session_args).await,
300 Ok(BasemgrResult::Success)
301 );
302 Ok(())
303 }
304
305 test_facade(
306 &start_session_v2_steps,
307 false, 1, 0, 0, )
312 .await
313 }
314
315 #[fuchsia_async::run(2, test)]
316 async fn test_start_session_shutdown_existing() -> Result<(), Error> {
317 async fn start_existing_steps(facade: ModularFacade) -> Result<(), Error> {
318 let start_session_args = json!({
319 "session_url": TEST_SESSION_URL,
320 });
321
322 assert_matches!(facade.is_session_running().await, Ok(true));
323 assert_matches!(
326 facade.start_session(start_session_args).await,
327 Ok(BasemgrResult::Success)
328 );
329 Ok(())
330 }
331
332 test_facade(
333 &start_existing_steps,
334 true, 1, 1, 0, )
339 .await
340 }
341
342 #[fuchsia_async::run_singlethreaded(test)]
343 async fn test_is_session_running_not_running() -> Result<(), Error> {
344 async fn not_running_steps(facade: ModularFacade) -> Result<(), Error> {
345 assert_matches!(facade.is_session_running().await, Ok(false));
346
347 Ok(())
348 }
349
350 test_facade(
351 ¬_running_steps,
352 false, 0, 0, 0, )
357 .await
358 }
359
360 #[fuchsia_async::run(2, test)]
361 async fn test_is_session_running() -> Result<(), Error> {
362 async fn is_running_steps(facade: ModularFacade) -> Result<(), Error> {
363 assert_matches!(facade.is_session_running().await, Ok(true));
364
365 Ok(())
366 }
367
368 test_facade(
369 &is_running_steps,
370 true, 0, 0, 0, )
375 .await
376 }
377
378 #[fuchsia_async::run_singlethreaded(test)]
379 async fn test_restart_session() -> Result<(), Error> {
380 async fn restart_steps(facade: ModularFacade) -> Result<(), Error> {
381 assert_matches!(facade.restart_session().await, Ok(RestartSessionResult::Success));
382
383 Ok(())
384 }
385
386 test_facade(
387 &restart_steps,
388 true, 0, 0, 1, )
393 .await
394 }
395
396 #[fuchsia_async::run(2, test)]
397 async fn test_restart_session_does_not_destroy() -> Result<(), Error> {
398 async fn restart_steps(facade: ModularFacade) -> Result<(), Error> {
399 assert_matches!(facade.is_session_running().await, Ok(true));
400
401 assert_matches!(facade.restart_session().await, Ok(RestartSessionResult::Success));
402
403 Ok(())
404 }
405
406 test_facade(
407 &restart_steps,
408 true, 0, 0, 1, )
413 .await
414 }
415}