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 fuchsia_url::AbsoluteComponentUrl;
7use futures::future::BoxFuture;
8use futures::{FutureExt, StreamExt};
9use moniker::Moniker;
10use thiserror::Error;
11use {
12    fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
13    fidl_fuchsia_sys2 as fsys,
14};
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: &Name,
79    child_name: &LongName,
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.to_string(),
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: &Name,
122    child_name: &LongName,
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.to_string(), &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) = fidl::endpoints::create_proxy::<fcomponent::BinderMarker>();
155    lifecycle_controller
156        .start_instance(&moniker.to_string(), 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) = fidl::endpoints::create_proxy::<fcomponent::BinderMarker>();
188    lifecycle_controller
189        .start_instance_with_args(&moniker.to_string(), 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.to_string())
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.to_string())
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.to_string())
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 fidl::HandleBased;
276    use fidl_fuchsia_process as fprocess;
277    use futures::TryStreamExt;
278
279    fn lifecycle_create_instance(
280        expected_moniker: &'static str,
281        expected_collection: &'static str,
282        expected_name: &'static str,
283        expected_url: &'static str,
284        expected_numbered_handle_count: usize,
285    ) -> fsys::LifecycleControllerProxy {
286        let (lifecycle_controller, mut stream) =
287            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
288        fuchsia_async::Task::local(async move {
289            let req = stream.try_next().await.unwrap().unwrap();
290            match req {
291                fsys::LifecycleControllerRequest::CreateInstance {
292                    parent_moniker,
293                    collection,
294                    decl,
295                    args,
296                    responder,
297                    ..
298                } => {
299                    assert_eq!(
300                        Moniker::parse_str(expected_moniker),
301                        Moniker::parse_str(&parent_moniker)
302                    );
303                    assert_eq!(expected_collection, collection.name);
304                    assert_eq!(expected_name, decl.name.unwrap());
305                    assert_eq!(expected_url, decl.url.unwrap());
306                    assert_eq!(
307                        expected_numbered_handle_count,
308                        args.numbered_handles.unwrap_or(vec![]).len()
309                    );
310                    responder.send(Ok(())).unwrap();
311                }
312                _ => panic!("Unexpected Lifecycle Controller request"),
313            }
314        })
315        .detach();
316        lifecycle_controller
317    }
318
319    fn lifecycle_destroy_instance(
320        expected_moniker: &'static str,
321        expected_collection: &'static str,
322        expected_name: &'static str,
323    ) -> fsys::LifecycleControllerProxy {
324        let (lifecycle_controller, mut stream) =
325            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
326        fuchsia_async::Task::local(async move {
327            let req = stream.try_next().await.unwrap().unwrap();
328            match req {
329                fsys::LifecycleControllerRequest::DestroyInstance {
330                    parent_moniker,
331                    child,
332                    responder,
333                    ..
334                } => {
335                    assert_eq!(
336                        Moniker::parse_str(expected_moniker),
337                        Moniker::parse_str(&parent_moniker)
338                    );
339                    assert_eq!(expected_name, child.name);
340                    assert_eq!(expected_collection, child.collection.unwrap());
341                    responder.send(Ok(())).unwrap();
342                }
343                _ => panic!("Unexpected Lifecycle Controller request"),
344            }
345        })
346        .detach();
347        lifecycle_controller
348    }
349
350    fn lifecycle_start(expected_moniker: &'static str) -> fsys::LifecycleControllerProxy {
351        let (lifecycle_controller, mut stream) =
352            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
353        fuchsia_async::Task::local(async move {
354            let req = stream.try_next().await.unwrap().unwrap();
355            match req {
356                fsys::LifecycleControllerRequest::StartInstanceWithArgs {
357                    moniker,
358                    responder,
359                    ..
360                } => {
361                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
362                    responder.send(Ok(())).unwrap();
363                }
364                _ => panic!("Unexpected Lifecycle Controller request"),
365            }
366        })
367        .detach();
368        lifecycle_controller
369    }
370
371    fn lifecycle_stop(expected_moniker: &'static str) -> fsys::LifecycleControllerProxy {
372        let (lifecycle_controller, mut stream) =
373            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
374        fuchsia_async::Task::local(async move {
375            let req = stream.try_next().await.unwrap().unwrap();
376            match req {
377                fsys::LifecycleControllerRequest::StopInstance { moniker, responder, .. } => {
378                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
379                    responder.send(Ok(())).unwrap();
380                }
381                _ => panic!("Unexpected Lifecycle Controller request"),
382            }
383        })
384        .detach();
385        lifecycle_controller
386    }
387
388    fn lifecycle_resolve(expected_moniker: &'static str) -> fsys::LifecycleControllerProxy {
389        let (lifecycle_controller, mut stream) =
390            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
391        fuchsia_async::Task::local(async move {
392            let req = stream.try_next().await.unwrap().unwrap();
393            match req {
394                fsys::LifecycleControllerRequest::ResolveInstance {
395                    moniker, responder, ..
396                } => {
397                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
398                    responder.send(Ok(())).unwrap();
399                }
400                _ => panic!("Unexpected Lifecycle Controller request"),
401            }
402        })
403        .detach();
404        lifecycle_controller
405    }
406
407    fn lifecycle_unresolve(expected_moniker: &'static str) -> fsys::LifecycleControllerProxy {
408        let (lifecycle_controller, mut stream) =
409            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
410        fuchsia_async::Task::local(async move {
411            let req = stream.try_next().await.unwrap().unwrap();
412            match req {
413                fsys::LifecycleControllerRequest::UnresolveInstance {
414                    moniker, responder, ..
415                } => {
416                    assert_eq!(Moniker::parse_str(expected_moniker), Moniker::parse_str(&moniker));
417                    responder.send(Ok(())).unwrap();
418                }
419                _ => panic!("Unexpected Lifecycle Controller request"),
420            }
421        })
422        .detach();
423        lifecycle_controller
424    }
425
426    fn lifecycle_create_fail(error: fsys::CreateError) -> fsys::LifecycleControllerProxy {
427        let (lifecycle_controller, mut stream) =
428            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
429        fuchsia_async::Task::local(async move {
430            let req = stream.try_next().await.unwrap().unwrap();
431            match req {
432                fsys::LifecycleControllerRequest::CreateInstance { responder, .. } => {
433                    responder.send(Err(error)).unwrap();
434                }
435                _ => panic!("Unexpected Lifecycle Controller request"),
436            }
437        })
438        .detach();
439        lifecycle_controller
440    }
441
442    fn lifecycle_start_fail(error: fsys::StartError) -> fsys::LifecycleControllerProxy {
443        let (lifecycle_controller, mut stream) =
444            create_proxy_and_stream::<fsys::LifecycleControllerMarker>();
445        fuchsia_async::Task::local(async move {
446            let req = stream.try_next().await.unwrap().unwrap();
447            match req {
448                fsys::LifecycleControllerRequest::StartInstanceWithArgs { responder, .. } => {
449                    responder.send(Err(error)).unwrap();
450                }
451                _ => panic!("Unexpected Lifecycle Controller request"),
452            }
453        })
454        .detach();
455        lifecycle_controller
456    }
457
458    #[fuchsia_async::run_singlethreaded(test)]
459    async fn test_create_child() {
460        let parent = Moniker::parse_str("core").unwrap();
461        let url =
462            AbsoluteComponentUrl::parse("fuchsia-pkg://fuchsia.com/test#meta/test.cm").unwrap();
463        let lc = lifecycle_create_instance(
464            "/core",
465            "foo",
466            "bar",
467            "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
468            0,
469        );
470        create_instance_in_collection(
471            &lc,
472            &parent,
473            &"foo".parse().unwrap(),
474            &"bar".parse().unwrap(),
475            &url,
476            vec![],
477            None,
478        )
479        .await
480        .unwrap();
481    }
482
483    #[fuchsia_async::run_singlethreaded(test)]
484    async fn test_create_child_with_numbered_handles() {
485        let parent = Moniker::parse_str("core").unwrap();
486        let url =
487            AbsoluteComponentUrl::parse("fuchsia-pkg://fuchsia.com/test#meta/test.cm").unwrap();
488        let lc = lifecycle_create_instance(
489            "/core",
490            "foo",
491            "bar",
492            "fuchsia-pkg://fuchsia.com/test#meta/test.cm",
493            2,
494        );
495        let (left, right) = fidl::Socket::create_stream();
496        let child_args = fcomponent::CreateChildArgs {
497            numbered_handles: Some(vec![
498                fprocess::HandleInfo { handle: left.into_handle(), id: 0x10 },
499                fprocess::HandleInfo { handle: right.into_handle(), id: 0x11 },
500            ]),
501            ..Default::default()
502        };
503        create_instance_in_collection(
504            &lc,
505            &parent,
506            &"foo".parse().unwrap(),
507            &"bar".parse().unwrap(),
508            &url,
509            vec![],
510            Some(child_args),
511        )
512        .await
513        .unwrap();
514    }
515
516    #[fuchsia_async::run_singlethreaded(test)]
517    async fn test_create_already_exists() {
518        let parent = Moniker::parse_str("core").unwrap();
519        let url =
520            AbsoluteComponentUrl::parse("fuchsia-pkg://fuchsia.com/test#meta/test.cm").unwrap();
521        let lc = lifecycle_create_fail(fsys::CreateError::InstanceAlreadyExists);
522        let err = create_instance_in_collection(
523            &lc,
524            &parent,
525            &"foo".parse().unwrap(),
526            &"bar".parse().unwrap(),
527            &url,
528            vec![],
529            None,
530        )
531        .await
532        .unwrap_err();
533        assert_matches!(err, CreateError::InstanceAlreadyExists);
534    }
535
536    #[fuchsia_async::run_singlethreaded(test)]
537    async fn test_destroy_child() {
538        let parent = Moniker::parse_str("core").unwrap();
539        let lc = lifecycle_destroy_instance("core", "foo", "bar");
540        destroy_instance_in_collection(
541            &lc,
542            &parent,
543            &"foo".parse().unwrap(),
544            &"bar".parse().unwrap(),
545        )
546        .await
547        .unwrap();
548    }
549
550    #[fuchsia_async::run_singlethreaded(test)]
551    async fn test_start() {
552        let moniker = Moniker::parse_str("core/foo").unwrap();
553        let lc = lifecycle_start("core/foo");
554        let _ = start_instance_with_args(&lc, &moniker, fcomponent::StartChildArgs::default())
555            .await
556            .unwrap();
557    }
558
559    #[fuchsia_async::run_singlethreaded(test)]
560    async fn test_stop() {
561        let moniker = Moniker::parse_str("core/foo").unwrap();
562        let lc = lifecycle_stop("core/foo");
563        stop_instance(&lc, &moniker).await.unwrap();
564    }
565
566    #[fuchsia_async::run_singlethreaded(test)]
567    async fn test_resolve() {
568        let moniker = Moniker::parse_str("core/foo").unwrap();
569        let lc = lifecycle_resolve("core/foo");
570        resolve_instance(&lc, &moniker).await.unwrap();
571    }
572
573    #[fuchsia_async::run_singlethreaded(test)]
574    async fn test_unresolve() {
575        let moniker = Moniker::parse_str("core/foo").unwrap();
576        let lc = lifecycle_unresolve("core/foo");
577        unresolve_instance(&lc, &moniker).await.unwrap();
578    }
579
580    #[fuchsia_async::run_singlethreaded(test)]
581    async fn test_instance_not_found() {
582        let moniker = Moniker::parse_str("core/foo").unwrap();
583        let lc = lifecycle_start_fail(fsys::StartError::InstanceNotFound);
584        match start_instance_with_args(&lc, &moniker, fcomponent::StartChildArgs::default()).await {
585            Ok(_) => panic!("start shouldn't succeed"),
586            Err(StartError::ActionError(ActionError::InstanceNotFound)) => {}
587            Err(e) => panic!("start failed unexpectedly: {}", e),
588        }
589    }
590}