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