Skip to main content

component_debug/
lifecycle.rs

1// Copyright 2022 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use cm_types::{BorrowedLongName, BorrowedName};
6use flex_client::ProxyHasDomain;
7use flex_fuchsia_component as fcomponent;
8use flex_fuchsia_component_decl as fdecl;
9use flex_fuchsia_sys2 as fsys;
10use fuchsia_url::fuchsia_pkg::AbsoluteComponentUrl;
11use futures::future::BoxFuture;
12use futures::{FutureExt, StreamExt};
13use moniker::Moniker;
14use thiserror::Error;
15
16/// Errors that apply to all lifecycle actions.
17#[derive(Error, Debug)]
18pub enum ActionError {
19    #[error("the instance could not be found")]
20    InstanceNotFound,
21    #[error("the instance has not been resolved")]
22    InstanceNotResolved,
23    #[error("component manager could not parse the moniker")]
24    BadMoniker,
25    #[error("component manager encountered an internal error")]
26    Internal,
27    #[error("component manager responded with an unknown error code")]
28    UnknownError,
29    #[error("unexpected FIDL error with LifecycleController: {0}")]
30    Fidl(#[from] fidl::Error),
31}
32
33#[derive(Error, Debug)]
34pub enum CreateError {
35    #[error("the instance already exists")]
36    InstanceAlreadyExists,
37    #[error("component manager could not parse the given child declaration")]
38    BadChildDecl,
39    #[error("the parent instance does not have a collection with the given name")]
40    CollectionNotFound,
41    #[error(transparent)]
42    ActionError(#[from] ActionError),
43}
44
45#[derive(Error, Debug)]
46pub enum DestroyError {
47    #[error("component manager could not parse the given child reference")]
48    BadChildRef,
49    #[error(transparent)]
50    ActionError(#[from] ActionError),
51}
52
53#[derive(Error, Debug)]
54pub enum StartError {
55    #[error("the package identified by the instance URL could not be found")]
56    PackageNotFound,
57    #[error("the manifest for the instance could not be found in its package")]
58    ManifestNotFound,
59    #[error(transparent)]
60    ActionError(#[from] ActionError),
61}
62
63#[derive(Error, Debug)]
64pub enum ResolveError {
65    #[error("the package identified by the instance URL could not be found")]
66    PackageNotFound,
67    #[error("the manifest for the instance could not be found in its package")]
68    ManifestNotFound,
69    #[error(transparent)]
70    ActionError(#[from] ActionError),
71}
72
73/// Uses the `fuchsia.sys2.LifecycleController` protocol to create a dynamic component instance
74/// with the given `moniker` and `url`.
75pub async fn create_instance_in_collection(
76    lifecycle_controller: &fsys::LifecycleControllerProxy,
77    parent: &Moniker,
78    collection: &BorrowedName,
79    child_name: &BorrowedLongName,
80    url: &AbsoluteComponentUrl,
81    config_overrides: Vec<fdecl::ConfigOverride>,
82    child_args: Option<fcomponent::CreateChildArgs>,
83) -> Result<(), CreateError> {
84    let collection_ref = fdecl::CollectionRef { name: collection.to_string() };
85    let decl = fdecl::Child {
86        name: Some(child_name.to_string()),
87        url: Some(url.to_string()),
88        startup: Some(fdecl::StartupMode::Lazy),
89        environment: None,
90        config_overrides: Some(config_overrides),
91        ..Default::default()
92    };
93
94    lifecycle_controller
95        .create_instance(
96            parent.as_ref(),
97            &collection_ref,
98            &decl,
99            child_args.unwrap_or(fcomponent::CreateChildArgs::default()),
100        )
101        .await
102        .map_err(|e| ActionError::Fidl(e))?
103        .map_err(|e| match e {
104            fsys::CreateError::BadChildDecl => CreateError::BadChildDecl,
105            fsys::CreateError::CollectionNotFound => CreateError::CollectionNotFound,
106            fsys::CreateError::InstanceAlreadyExists => CreateError::InstanceAlreadyExists,
107            fsys::CreateError::Internal => ActionError::Internal.into(),
108            fsys::CreateError::BadMoniker => ActionError::BadMoniker.into(),
109            fsys::CreateError::InstanceNotFound => ActionError::InstanceNotFound.into(),
110            _ => ActionError::UnknownError.into(),
111        })?;
112
113    Ok(())
114}
115
116/// Uses the `fuchsia.sys2.LifecycleController` protocol to destroy a dynamic component instance
117/// with the given `moniker`.
118pub async fn destroy_instance_in_collection(
119    lifecycle_controller: &fsys::LifecycleControllerProxy,
120    parent: &Moniker,
121    collection: &BorrowedName,
122    child_name: &BorrowedLongName,
123) -> Result<(), DestroyError> {
124    let child =
125        fdecl::ChildRef { name: child_name.to_string(), collection: Some(collection.to_string()) };
126
127    lifecycle_controller
128        .destroy_instance(parent.as_ref(), &child)
129        .await
130        .map_err(|e| ActionError::Fidl(e))?
131        .map_err(|e| match e {
132            fsys::DestroyError::BadChildRef => DestroyError::BadChildRef,
133            fsys::DestroyError::Internal => ActionError::Internal.into(),
134            fsys::DestroyError::BadMoniker => ActionError::BadMoniker.into(),
135            fsys::DestroyError::InstanceNotFound => ActionError::InstanceNotFound.into(),
136            fsys::DestroyError::InstanceNotResolved => ActionError::InstanceNotResolved.into(),
137            _ => ActionError::UnknownError.into(),
138        })?;
139    Ok(())
140}
141
142// A future that returns when the component instance has stopped.
143// This notification comes over FIDL, which is why this future returns FIDL-specific errors.
144type StopFuture = BoxFuture<'static, Result<(), fidl::Error>>;
145
146/// Uses the `fuchsia.sys2.LifecycleController` protocol to start a component instance
147/// with the given `moniker`.
148///
149/// Returns a future that can be waited on to know when the component instance has stopped.
150pub async fn start_instance(
151    lifecycle_controller: &fsys::LifecycleControllerProxy,
152    moniker: &Moniker,
153) -> Result<StopFuture, StartError> {
154    let (client, server) = lifecycle_controller.domain().create_proxy::<fcomponent::BinderMarker>();
155    lifecycle_controller
156        .start_instance(moniker.as_ref(), server)
157        .await
158        .map_err(|e| ActionError::Fidl(e))?
159        .map_err(|e| match e {
160            fsys::StartError::PackageNotFound => StartError::PackageNotFound,
161            fsys::StartError::ManifestNotFound => StartError::ManifestNotFound,
162            fsys::StartError::Internal => ActionError::Internal.into(),
163            fsys::StartError::BadMoniker => ActionError::BadMoniker.into(),
164            fsys::StartError::InstanceNotFound => ActionError::InstanceNotFound.into(),
165            _ => ActionError::UnknownError.into(),
166        })?;
167    let stop_future = async move {
168        let mut event_stream = client.take_event_stream();
169        match event_stream.next().await {
170            Some(Err(e)) => return Err(e),
171            None => return Ok(()),
172        }
173    }
174    .boxed();
175    Ok(stop_future)
176}
177
178/// Uses the `fuchsia.sys2.LifecycleController` protocol to start a component instance
179/// with the given `moniker`.
180///
181/// Returns a future that can be waited on to know when the component instance has stopped.
182pub async fn start_instance_with_args(
183    lifecycle_controller: &fsys::LifecycleControllerProxy,
184    moniker: &Moniker,
185    arguments: fcomponent::StartChildArgs,
186) -> Result<StopFuture, StartError> {
187    let (client, server) = lifecycle_controller.domain().create_proxy::<fcomponent::BinderMarker>();
188    lifecycle_controller
189        .start_instance_with_args(moniker.as_ref(), server, arguments)
190        .await
191        .map_err(|e| ActionError::Fidl(e))?
192        .map_err(|e| match e {
193            fsys::StartError::PackageNotFound => StartError::PackageNotFound,
194            fsys::StartError::ManifestNotFound => StartError::ManifestNotFound,
195            fsys::StartError::Internal => ActionError::Internal.into(),
196            fsys::StartError::BadMoniker => ActionError::BadMoniker.into(),
197            fsys::StartError::InstanceNotFound => ActionError::InstanceNotFound.into(),
198            _ => ActionError::UnknownError.into(),
199        })?;
200    let stop_future = async move {
201        let mut event_stream = client.take_event_stream();
202        match event_stream.next().await {
203            Some(Err(e)) => return Err(e),
204            None => return Ok(()),
205        }
206    }
207    .boxed();
208    Ok(stop_future)
209}
210
211/// Uses the `fuchsia.sys2.LifecycleController` protocol to stop a component instance
212/// with the given `moniker`.
213pub async fn stop_instance(
214    lifecycle_controller: &fsys::LifecycleControllerProxy,
215    moniker: &Moniker,
216) -> Result<(), ActionError> {
217    lifecycle_controller
218        .stop_instance(moniker.as_ref())
219        .await
220        .map_err(|e| ActionError::Fidl(e))?
221        .map_err(|e| match e {
222        fsys::StopError::Internal => ActionError::Internal,
223        fsys::StopError::BadMoniker => ActionError::BadMoniker,
224        fsys::StopError::InstanceNotFound => ActionError::InstanceNotFound,
225        _ => ActionError::UnknownError,
226    })?;
227    Ok(())
228}
229
230/// Uses the `fuchsia.sys2.LifecycleController` protocol to resolve a component instance
231/// with the given `moniker`.
232pub async fn resolve_instance(
233    lifecycle_controller: &fsys::LifecycleControllerProxy,
234    moniker: &Moniker,
235) -> Result<(), ResolveError> {
236    lifecycle_controller
237        .resolve_instance(moniker.as_ref())
238        .await
239        .map_err(|e| ActionError::Fidl(e))?
240        .map_err(|e| match e {
241            fsys::ResolveError::PackageNotFound => ResolveError::PackageNotFound,
242            fsys::ResolveError::ManifestNotFound => ResolveError::ManifestNotFound,
243            fsys::ResolveError::Internal => ActionError::Internal.into(),
244            fsys::ResolveError::BadMoniker => ActionError::BadMoniker.into(),
245            fsys::ResolveError::InstanceNotFound => ActionError::InstanceNotFound.into(),
246            _ => ActionError::UnknownError.into(),
247        })?;
248    Ok(())
249}
250
251/// Uses the `fuchsia.sys2.LifecycleController` protocol to unresolve a component instance
252/// with the given `moniker`.
253pub async fn unresolve_instance(
254    lifecycle_controller: &fsys::LifecycleControllerProxy,
255    moniker: &Moniker,
256) -> Result<(), ActionError> {
257    lifecycle_controller
258        .unresolve_instance(moniker.as_ref())
259        .await
260        .map_err(|e| ActionError::Fidl(e))?
261        .map_err(|e| match e {
262            fsys::UnresolveError::Internal => ActionError::Internal,
263            fsys::UnresolveError::BadMoniker => ActionError::BadMoniker,
264            fsys::UnresolveError::InstanceNotFound => ActionError::InstanceNotFound,
265            _ => ActionError::UnknownError,
266        })?;
267    Ok(())
268}
269
270#[cfg(test)]
271mod test {
272    use super::*;
273    use assert_matches::assert_matches;
274    use fidl::endpoints::create_proxy_and_stream;
275    use flex_fuchsia_process as fprocess;
276    use futures::TryStreamExt;
277
278    fn lifecycle_create_instance(
279        expected_moniker: &'static str,
280        expected_collection: &'static str,
281        expected_name: &'static str,
282        expected_url: &'static str,
283        expected_numbered_handle_count: usize,
284    ) -> fsys::LifecycleControllerProxy {
285        let (lifecycle_controller, mut stream) =
286            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
287        fuchsia_async::Task::local(async move {
288            let req = stream.try_next().await.unwrap().unwrap();
289            match req {
290                fsys::LifecycleControllerRequest::CreateInstance {
291                    parent_moniker,
292                    collection,
293                    decl,
294                    args,
295                    responder,
296                    ..
297                } => {
298                    assert_eq!(
299                        Moniker::parse_str(expected_moniker),
300                        Moniker::parse_str(&parent_moniker)
301                    );
302                    assert_eq!(expected_collection, collection.name);
303                    assert_eq!(expected_name, decl.name.unwrap());
304                    assert_eq!(expected_url, decl.url.unwrap());
305                    assert_eq!(
306                        expected_numbered_handle_count,
307                        args.numbered_handles.unwrap_or(vec![]).len()
308                    );
309                    responder.send(Ok(())).unwrap();
310                }
311                _ => panic!("Unexpected Lifecycle Controller request"),
312            }
313        })
314        .detach();
315        lifecycle_controller
316    }
317
318    fn lifecycle_destroy_instance(
319        expected_moniker: &'static str,
320        expected_collection: &'static str,
321        expected_name: &'static str,
322    ) -> fsys::LifecycleControllerProxy {
323        let (lifecycle_controller, mut stream) =
324            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
325        fuchsia_async::Task::local(async move {
326            let req = stream.try_next().await.unwrap().unwrap();
327            match req {
328                fsys::LifecycleControllerRequest::DestroyInstance {
329                    parent_moniker,
330                    child,
331                    responder,
332                    ..
333                } => {
334                    assert_eq!(
335                        Moniker::parse_str(expected_moniker),
336                        Moniker::parse_str(&parent_moniker)
337                    );
338                    assert_eq!(expected_name, child.name);
339                    assert_eq!(expected_collection, child.collection.unwrap());
340                    responder.send(Ok(())).unwrap();
341                }
342                _ => panic!("Unexpected Lifecycle Controller request"),
343            }
344        })
345        .detach();
346        lifecycle_controller
347    }
348
349    fn lifecycle_start(expected_moniker: &'static str) -> fsys::LifecycleControllerProxy {
350        let (lifecycle_controller, mut stream) =
351            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
352        fuchsia_async::Task::local(async move {
353            let req = stream.try_next().await.unwrap().unwrap();
354            match req {
355                fsys::LifecycleControllerRequest::StartInstanceWithArgs {
356                    moniker,
357                    responder,
358                    ..
359                } => {
360                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
361                    responder.send(Ok(())).unwrap();
362                }
363                _ => panic!("Unexpected Lifecycle Controller request"),
364            }
365        })
366        .detach();
367        lifecycle_controller
368    }
369
370    fn lifecycle_stop(expected_moniker: &'static str) -> fsys::LifecycleControllerProxy {
371        let (lifecycle_controller, mut stream) =
372            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
373        fuchsia_async::Task::local(async move {
374            let req = stream.try_next().await.unwrap().unwrap();
375            match req {
376                fsys::LifecycleControllerRequest::StopInstance { moniker, responder, .. } => {
377                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
378                    responder.send(Ok(())).unwrap();
379                }
380                _ => panic!("Unexpected Lifecycle Controller request"),
381            }
382        })
383        .detach();
384        lifecycle_controller
385    }
386
387    fn lifecycle_resolve(expected_moniker: &'static str) -> fsys::LifecycleControllerProxy {
388        let (lifecycle_controller, mut stream) =
389            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
390        fuchsia_async::Task::local(async move {
391            let req = stream.try_next().await.unwrap().unwrap();
392            match req {
393                fsys::LifecycleControllerRequest::ResolveInstance {
394                    moniker, responder, ..
395                } => {
396                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
397                    responder.send(Ok(())).unwrap();
398                }
399                _ => panic!("Unexpected Lifecycle Controller request"),
400            }
401        })
402        .detach();
403        lifecycle_controller
404    }
405
406    fn lifecycle_unresolve(expected_moniker: &'static str) -> fsys::LifecycleControllerProxy {
407        let (lifecycle_controller, mut stream) =
408            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
409        fuchsia_async::Task::local(async move {
410            let req = stream.try_next().await.unwrap().unwrap();
411            match req {
412                fsys::LifecycleControllerRequest::UnresolveInstance {
413                    moniker, responder, ..
414                } => {
415                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
416                    responder.send(Ok(())).unwrap();
417                }
418                _ => panic!("Unexpected Lifecycle Controller request"),
419            }
420        })
421        .detach();
422        lifecycle_controller
423    }
424
425    fn lifecycle_create_fail(error: fsys::CreateError) -> fsys::LifecycleControllerProxy {
426        let (lifecycle_controller, mut stream) =
427            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
428        fuchsia_async::Task::local(async move {
429            let req = stream.try_next().await.unwrap().unwrap();
430            match req {
431                fsys::LifecycleControllerRequest::CreateInstance { responder, .. } => {
432                    responder.send(Err(error)).unwrap();
433                }
434                _ => panic!("Unexpected Lifecycle Controller request"),
435            }
436        })
437        .detach();
438        lifecycle_controller
439    }
440
441    fn lifecycle_start_fail(error: fsys::StartError) -> fsys::LifecycleControllerProxy {
442        let (lifecycle_controller, mut stream) =
443            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
444        fuchsia_async::Task::local(async move {
445            let req = stream.try_next().await.unwrap().unwrap();
446            match req {
447                fsys::LifecycleControllerRequest::StartInstanceWithArgs { responder, .. } => {
448                    responder.send(Err(error)).unwrap();
449                }
450                _ => panic!("Unexpected Lifecycle Controller request"),
451            }
452        })
453        .detach();
454        lifecycle_controller
455    }
456
457    #[fuchsia_async::run_singlethreaded(test)]
458    async fn test_create_child() {
459        let parent = Moniker::parse_str("core").unwrap();
460        let url =
461            AbsoluteComponentUrl::parse("fuchsia-pkg://fuchsia.com/test#meta/test.cm").unwrap();
462        let lc = lifecycle_create_instance(
463            "/core",
464            "foo",
465            "bar",
466            "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
467            0,
468        );
469        create_instance_in_collection(
470            &lc,
471            &parent,
472            BorrowedName::new("foo").unwrap(),
473            BorrowedLongName::new("bar").unwrap(),
474            &url,
475            vec![],
476            None,
477        )
478        .await
479        .unwrap();
480    }
481
482    #[fuchsia_async::run_singlethreaded(test)]
483    async fn test_create_child_with_numbered_handles() {
484        let parent = Moniker::parse_str("core").unwrap();
485        let url =
486            AbsoluteComponentUrl::parse("fuchsia-pkg://fuchsia.com/test#meta/test.cm").unwrap();
487        let lc = lifecycle_create_instance(
488            "/core",
489            "foo",
490            "bar",
491            "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
492            2,
493        );
494        let (left, right) = fidl::Socket::create_stream();
495        let child_args = fcomponent::CreateChildArgs {
496            numbered_handles: Some(vec![
497                fprocess::HandleInfo { handle: left.into_handle(), id: 0x10 },
498                fprocess::HandleInfo { handle: right.into_handle(), id: 0x11 },
499            ]),
500            ..Default::default()
501        };
502        create_instance_in_collection(
503            &lc,
504            &parent,
505            BorrowedName::new("foo").unwrap(),
506            BorrowedLongName::new("bar").unwrap(),
507            &url,
508            vec![],
509            Some(child_args),
510        )
511        .await
512        .unwrap();
513    }
514
515    #[fuchsia_async::run_singlethreaded(test)]
516    async fn test_create_already_exists() {
517        let parent = Moniker::parse_str("core").unwrap();
518        let url =
519            AbsoluteComponentUrl::parse("fuchsia-pkg://fuchsia.com/test#meta/test.cm").unwrap();
520        let lc = lifecycle_create_fail(fsys::CreateError::InstanceAlreadyExists);
521        let err = create_instance_in_collection(
522            &lc,
523            &parent,
524            BorrowedName::new("foo").unwrap(),
525            BorrowedLongName::new("bar").unwrap(),
526            &url,
527            vec![],
528            None,
529        )
530        .await
531        .unwrap_err();
532        assert_matches!(err, CreateError::InstanceAlreadyExists);
533    }
534
535    #[fuchsia_async::run_singlethreaded(test)]
536    async fn test_destroy_child() {
537        let parent = Moniker::parse_str("core").unwrap();
538        let lc = lifecycle_destroy_instance("core", "foo", "bar");
539        destroy_instance_in_collection(
540            &lc,
541            &parent,
542            BorrowedName::new("foo").unwrap(),
543            BorrowedLongName::new("bar").unwrap(),
544        )
545        .await
546        .unwrap();
547    }
548
549    #[fuchsia_async::run_singlethreaded(test)]
550    async fn test_start() {
551        let moniker = Moniker::parse_str("core/foo").unwrap();
552        let lc = lifecycle_start("core/foo");
553        let _ = start_instance_with_args(&lc, &moniker, fcomponent::StartChildArgs::default())
554            .await
555            .unwrap();
556    }
557
558    #[fuchsia_async::run_singlethreaded(test)]
559    async fn test_stop() {
560        let moniker = Moniker::parse_str("core/foo").unwrap();
561        let lc = lifecycle_stop("core/foo");
562        stop_instance(&lc, &moniker).await.unwrap();
563    }
564
565    #[fuchsia_async::run_singlethreaded(test)]
566    async fn test_resolve() {
567        let moniker = Moniker::parse_str("core/foo").unwrap();
568        let lc = lifecycle_resolve("core/foo");
569        resolve_instance(&lc, &moniker).await.unwrap();
570    }
571
572    #[fuchsia_async::run_singlethreaded(test)]
573    async fn test_unresolve() {
574        let moniker = Moniker::parse_str("core/foo").unwrap();
575        let lc = lifecycle_unresolve("core/foo");
576        unresolve_instance(&lc, &moniker).await.unwrap();
577    }
578
579    #[fuchsia_async::run_singlethreaded(test)]
580    async fn test_instance_not_found() {
581        let moniker = Moniker::parse_str("core/foo").unwrap();
582        let lc = lifecycle_start_fail(fsys::StartError::InstanceNotFound);
583        match start_instance_with_args(&lc, &moniker, fcomponent::StartChildArgs::default()).await {
584            Ok(_) => panic!("start shouldn't succeed"),
585            Err(StartError::ActionError(ActionError::InstanceNotFound)) => {}
586            Err(e) => panic!("start failed unexpectedly: {}", e),
587        }
588    }
589}