1use crate::modular::types::{
5 BasemgrResult, KillSessionResult, RestartSessionResult, StartBasemgrRequest,
6};
7use anyhow::{format_err, Error};
8use fuchsia_component::client::connect_to_protocol;
9use serde_json::{from_value, 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 lazy_static::lazy_static;
144 use serde_json::json;
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 lazy_static! {
165 static ref SESSION_LAUNCH_CALL_COUNT: Counter = Counter::new(0);
166 static ref DESTROY_CHILD_CALL_COUNT: Counter = Counter::new(0);
167 static ref SESSION_RESTART_CALL_COUNT: Counter = Counter::new(0);
168 }
169
170 let session_launcher = spawn_stream_handler(move |launcher_request| async move {
171 match launcher_request {
172 fsession::LauncherRequest::Launch { configuration, responder } => {
173 assert!(configuration.session_url.is_some());
174 let session_url = configuration.session_url.unwrap();
175 assert!(session_url == TEST_SESSION_URL.to_string());
176
177 SESSION_LAUNCH_CALL_COUNT.inc();
178 let _ = responder.send(Ok(()));
179 }
180 }
181 });
182
183 let session_restarter = spawn_stream_handler(move |restarter_request| async move {
184 match restarter_request {
185 fsession::RestarterRequest::Restart { responder } => {
186 SESSION_RESTART_CALL_COUNT.inc();
187 let _ = responder.send(Ok(()));
188 }
189 }
190 });
191
192 let lifecycle_controller = spawn_stream_handler(|lifecycle_controller_request| async {
193 match lifecycle_controller_request {
194 fsys::LifecycleControllerRequest::DestroyInstance {
195 parent_moniker,
196 child,
197 responder,
198 } => {
199 assert_eq!(parent_moniker, SESSION_PARENT_MONIKER.to_string());
200 assert_eq!(child.name, SESSION_CHILD_NAME);
201 assert_eq!(child.collection.unwrap(), SESSION_COLLECTION_NAME);
202
203 DESTROY_CHILD_CALL_COUNT.inc();
204 let _ = responder.send(Ok(()));
205 }
206 r => {
207 panic!("didn't expect request: {:?}", r)
208 }
209 }
210 });
211
212 let realm_query = spawn_stream_handler(move |realm_query_request| async move {
213 match realm_query_request {
214 fsys::RealmQueryRequest::GetInstance { moniker, responder } => {
215 assert_eq!(moniker, SESSION_MONIKER.to_string());
216 let instance = if is_session_running {
217 fsys::Instance {
218 moniker: Some(SESSION_MONIKER.to_string()),
219 url: Some("fake".to_string()),
220 instance_id: None,
221 resolved_info: Some(fsys::ResolvedInfo {
222 resolved_url: Some("fake".to_string()),
223 execution_info: Some(fsys::ExecutionInfo {
224 start_reason: Some("fake".to_string()),
225 ..Default::default()
226 }),
227 ..Default::default()
228 }),
229 ..Default::default()
230 }
231 } else {
232 fsys::Instance {
233 moniker: Some(SESSION_MONIKER.to_string()),
234 url: Some("fake".to_string()),
235 instance_id: None,
236 resolved_info: None,
237 ..Default::default()
238 }
239 };
240 let _ = responder.send(Ok(&instance));
241 }
242 r => {
243 panic!("didn't expect request: {:?}", r)
244 }
245 }
246 });
247
248 let facade = ModularFacade::new_with_proxies(
249 session_launcher,
250 session_restarter,
251 lifecycle_controller,
252 realm_query,
253 );
254
255 run_facade_fns(facade).await?;
256
257 assert_eq!(
258 SESSION_LAUNCH_CALL_COUNT.get(),
259 expected_launch_count,
260 "SESSION_LAUNCH_CALL_COUNT"
261 );
262 assert_eq!(
263 DESTROY_CHILD_CALL_COUNT.get(),
264 expected_destroy_count,
265 "DESTROY_CHILD_CALL_COUNT"
266 );
267 assert_eq!(
268 SESSION_RESTART_CALL_COUNT.get(),
269 expected_restart_count,
270 "SESSION_RESTART_CALL_COUNT"
271 );
272
273 Ok(())
274 }
275
276 #[fuchsia_async::run(2, test)]
277 async fn test_stop_session() -> Result<(), Error> {
278 async fn stop_session_steps(facade: ModularFacade) -> Result<(), Error> {
279 assert_matches!(facade.is_session_running().await, Ok(true));
280 assert_matches!(facade.stop_session().await, Ok(KillSessionResult::Success));
281 Ok(())
282 }
283
284 test_facade(
285 &stop_session_steps,
286 true, 0, 1, 0, )
291 .await
292 }
293
294 #[fuchsia_async::run(2, test)]
295 async fn test_start_session_without_config() -> Result<(), Error> {
296 async fn start_session_v2_steps(facade: ModularFacade) -> Result<(), Error> {
297 let start_session_args = json!({
298 "session_url": TEST_SESSION_URL,
299 });
300 assert_matches!(
301 facade.start_session(start_session_args).await,
302 Ok(BasemgrResult::Success)
303 );
304 Ok(())
305 }
306
307 test_facade(
308 &start_session_v2_steps,
309 false, 1, 0, 0, )
314 .await
315 }
316
317 #[fuchsia_async::run(2, test)]
318 async fn test_start_session_shutdown_existing() -> Result<(), Error> {
319 async fn start_existing_steps(facade: ModularFacade) -> Result<(), Error> {
320 let start_session_args = json!({
321 "session_url": TEST_SESSION_URL,
322 });
323
324 assert_matches!(facade.is_session_running().await, Ok(true));
325 assert_matches!(
328 facade.start_session(start_session_args).await,
329 Ok(BasemgrResult::Success)
330 );
331 Ok(())
332 }
333
334 test_facade(
335 &start_existing_steps,
336 true, 1, 1, 0, )
341 .await
342 }
343
344 #[fuchsia_async::run_singlethreaded(test)]
345 async fn test_is_session_running_not_running() -> Result<(), Error> {
346 async fn not_running_steps(facade: ModularFacade) -> Result<(), Error> {
347 assert_matches!(facade.is_session_running().await, Ok(false));
348
349 Ok(())
350 }
351
352 test_facade(
353 ¬_running_steps,
354 false, 0, 0, 0, )
359 .await
360 }
361
362 #[fuchsia_async::run(2, test)]
363 async fn test_is_session_running() -> Result<(), Error> {
364 async fn is_running_steps(facade: ModularFacade) -> Result<(), Error> {
365 assert_matches!(facade.is_session_running().await, Ok(true));
366
367 Ok(())
368 }
369
370 test_facade(
371 &is_running_steps,
372 true, 0, 0, 0, )
377 .await
378 }
379
380 #[fuchsia_async::run_singlethreaded(test)]
381 async fn test_restart_session() -> Result<(), Error> {
382 async fn restart_steps(facade: ModularFacade) -> Result<(), Error> {
383 assert_matches!(facade.restart_session().await, Ok(RestartSessionResult::Success));
384
385 Ok(())
386 }
387
388 test_facade(
389 &restart_steps,
390 true, 0, 0, 1, )
395 .await
396 }
397
398 #[fuchsia_async::run(2, test)]
399 async fn test_restart_session_does_not_destroy() -> Result<(), Error> {
400 async fn restart_steps(facade: ModularFacade) -> Result<(), Error> {
401 assert_matches!(facade.is_session_running().await, Ok(true));
402
403 assert_matches!(facade.restart_session().await, Ok(RestartSessionResult::Success));
404
405 Ok(())
406 }
407
408 test_facade(
409 &restart_steps,
410 true, 0, 0, 1, )
415 .await
416 }
417}