realm_management/
lib.rs

1// Copyright 2019 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 fidl::endpoints::ServerEnd;
6use {
7    fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
8    fidl_fuchsia_io as fio,
9};
10
11/// Creates a (lazy) child in the specified `Realm`.
12///
13/// # Parameters
14/// - `child_name`: The name of the child to be added.
15/// - `child_url`: The component URL of the child to add.
16/// - `collection_name`: The name of the collection to which the child will be added.
17/// - `create_child_args`: Extra arguments passed to `Realm.CreateChild`.
18/// - `realm`: The `Realm` to which the child will be added.
19///
20/// # Returns
21/// `Ok` if the child is created successfully.
22pub async fn create_child_component(
23    child_name: &str,
24    child_url: &str,
25    collection_name: &str,
26    create_child_args: fcomponent::CreateChildArgs,
27    realm: &fcomponent::RealmProxy,
28) -> Result<(), fcomponent::Error> {
29    let collection_ref = fdecl::CollectionRef { name: collection_name.to_string() };
30    let child_decl = fdecl::Child {
31        name: Some(child_name.to_string()),
32        url: Some(child_url.to_string()),
33        startup: Some(fdecl::StartupMode::Lazy),
34        environment: None,
35        ..Default::default()
36    };
37
38    realm
39        .create_child(&collection_ref, &child_decl, create_child_args)
40        .await
41        .map_err(|_| fcomponent::Error::Internal)?
42}
43
44/// Opens the exposed directory of a child in the specified `Realm`. This call
45/// is expected to follow a matching call to `create_child`.
46///
47/// # Parameters
48/// - `child_name`: The name of the child to bind.
49/// - `collection_name`: The name of collection in which the child was created.
50/// - `realm`: The `Realm` the child will bound in.
51/// - `exposed_dir`: The server end on which to serve the exposed directory.
52///
53/// # Returns
54/// `Ok` Result with a DirectoryProxy bound to the component's `exposed_dir`. This directory
55/// contains the capabilities that the child exposed to its realm (as declared, for instance, in the
56/// `expose` declaration of the component's `.cml` file).
57pub async fn open_child_component_exposed_dir(
58    child_name: &str,
59    collection_name: &str,
60    realm: &fcomponent::RealmProxy,
61    exposed_dir: ServerEnd<fio::DirectoryMarker>,
62) -> Result<(), fcomponent::Error> {
63    let child_ref = fdecl::ChildRef {
64        name: child_name.to_string(),
65        collection: Some(collection_name.to_string()),
66    };
67
68    realm
69        .open_exposed_dir(&child_ref, exposed_dir)
70        .await
71        .map_err(|_| fcomponent::Error::Internal)?
72}
73
74/// Destroys a child in the specified `Realm`. This call is expects a matching call to have been
75/// made to `create_child`.
76///
77/// # Parameters
78/// - `child_name`: The name of the child to destroy.
79/// - `collection_name`: The name of collection in which the child was created.
80/// - `realm`: The `Realm` the child will bound in.
81///
82/// # Errors
83/// Returns an error if the child was not destroyed in the realm.
84pub async fn destroy_child_component(
85    child_name: &str,
86    collection_name: &str,
87    realm: &fcomponent::RealmProxy,
88) -> Result<(), fcomponent::Error> {
89    let child_ref = fdecl::ChildRef {
90        name: child_name.to_string(),
91        collection: Some(collection_name.to_string()),
92    };
93
94    realm.destroy_child(&child_ref).await.map_err(|_| fcomponent::Error::Internal)?
95}
96
97#[cfg(test)]
98mod tests {
99    use super::{
100        create_child_component, destroy_child_component, open_child_component_exposed_dir,
101    };
102    use fidl::endpoints::{create_endpoints, create_proxy};
103    use fidl_test_util::spawn_stream_handler;
104    use session_testing::spawn_directory_server;
105    use std::sync::LazyLock;
106    use test_util::Counter;
107    use {fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio};
108
109    /// Tests that creating a child results in the appropriate call to the `RealmProxy`.
110    #[fuchsia::test]
111    async fn create_child_parameters() {
112        let child_name = "test_child";
113        let child_url = "test_url";
114        let child_collection = "test_collection";
115
116        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
117            match realm_request {
118                fcomponent::RealmRequest::CreateChild { collection, decl, args: _, responder } => {
119                    assert_eq!(decl.name.unwrap(), child_name);
120                    assert_eq!(decl.url.unwrap(), child_url);
121                    assert_eq!(&collection.name, child_collection);
122
123                    let _ = responder.send(Ok(()));
124                }
125                _ => panic!("Realm handler received an unexpected request"),
126            }
127        });
128
129        assert!(
130            create_child_component(
131                child_name,
132                child_url,
133                child_collection,
134                Default::default(),
135                &realm_proxy
136            )
137            .await
138            .is_ok()
139        );
140    }
141
142    /// Tests that a success received when creating a child results in an appropriate result from
143    /// `create_child`.
144    #[fuchsia::test]
145    async fn create_child_success() {
146        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
147            match realm_request {
148                fcomponent::RealmRequest::CreateChild {
149                    collection: _,
150                    decl: _,
151                    args: _,
152                    responder,
153                } => {
154                    let _ = responder.send(Ok(()));
155                }
156                _ => panic!("Realm handler received an unexpected request"),
157            }
158        });
159
160        assert!(create_child_component("", "", "", Default::default(), &realm_proxy).await.is_ok());
161    }
162
163    /// Tests that an error received when creating a child results in an appropriate error from
164    /// `create_child`.
165    #[fuchsia::test]
166    async fn create_child_error() {
167        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
168            match realm_request {
169                fcomponent::RealmRequest::CreateChild {
170                    collection: _,
171                    decl: _,
172                    args: _,
173                    responder,
174                } => {
175                    let _ = responder.send(Err(fcomponent::Error::Internal));
176                }
177                _ => panic!("Realm handler received an unexpected request"),
178            }
179        });
180
181        assert!(
182            create_child_component("", "", "", Default::default(), &realm_proxy).await.is_err()
183        );
184    }
185
186    /// Tests that `open_child_component_exposed_dir` results in the appropriate call to `RealmProxy`.
187    #[fuchsia::test]
188    async fn open_child_component_exposed_dir_parameters() {
189        let child_name = "test_child";
190        let child_collection = "test_collection";
191
192        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
193            match realm_request {
194                fcomponent::RealmRequest::OpenExposedDir { child, exposed_dir: _, responder } => {
195                    assert_eq!(child.name, child_name);
196                    assert_eq!(child.collection, Some(child_collection.to_string()));
197
198                    let _ = responder.send(Ok(()));
199                }
200                _ => panic!("Realm handler received an unexpected request"),
201            }
202        });
203
204        let (_exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
205        assert!(
206            open_child_component_exposed_dir(
207                child_name,
208                child_collection,
209                &realm_proxy,
210                exposed_dir_server_end
211            )
212            .await
213            .is_ok()
214        );
215    }
216
217    /// Tests that a success received when opening a child's exposed directory
218    /// results in an appropriate result from `open_child_component_exposed_dir`.
219    #[fuchsia::test]
220    async fn open_child_component_exposed_dir_success() {
221        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
222            match realm_request {
223                fcomponent::RealmRequest::OpenExposedDir {
224                    child: _,
225                    exposed_dir: _,
226                    responder,
227                } => {
228                    let _ = responder.send(Ok(()));
229                }
230                _ => panic!("Realm handler received an unexpected request"),
231            }
232        });
233
234        let (_exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
235        assert!(
236            open_child_component_exposed_dir("", "", &realm_proxy, exposed_dir_server_end)
237                .await
238                .is_ok()
239        );
240    }
241
242    /// Tests that opening a child's exposed directory returns successfully.
243    #[fuchsia::test]
244    async fn open_child_exposed_dir_success() {
245        // Make a static call counter to avoid unneeded complexity with cloned Arc<Mutex>.
246        static CALL_COUNT: LazyLock<Counter> = LazyLock::new(|| Counter::new(0));
247
248        let directory_request_handler = |directory_request| match directory_request {
249            fio::DirectoryRequest::Open { path: fake_capability_path, .. } => {
250                CALL_COUNT.inc();
251                assert_eq!(fake_capability_path, "fake_capability_path");
252            }
253            _ => panic!("Directory handler received an unexpected request"),
254        };
255
256        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
257            match realm_request {
258                fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
259                    CALL_COUNT.inc();
260                    spawn_directory_server(exposed_dir, directory_request_handler);
261                    let _ = responder.send(Ok(()));
262                }
263                _ => panic!("Realm handler received an unexpected request"),
264            }
265        });
266
267        let (exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
268        open_child_component_exposed_dir("", "", &realm_proxy, exposed_dir_server_end)
269            .await
270            .unwrap();
271
272        // Create a proxy of any FIDL protocol, with any `await`-able method.
273        // (`fio::DirectoryMarker` here is arbitrary.)
274        let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
275
276        // Connect should succeed, but it is still an asynchronous operation.
277        // The `directory_request_handler` is not called yet.
278        assert!(
279            fdio::service_connect_at(
280                &exposed_dir.into_channel(),
281                "fake_capability_path",
282                server_end.into_channel()
283            )
284            .is_ok()
285        );
286
287        // Attempting to invoke and await an arbitrary method to ensure the
288        // `directory_request_handler` responds to the Open() method and increment
289        // the DIRECTORY_OPEN_CALL_COUNT.
290        //
291        // Since this is a fake capability (of any arbitrary type), it should fail.
292        assert!(proxy.rewind().await.is_err());
293
294        // Calls to Realm::OpenExposedDir and Directory::Open should have happened.
295        assert_eq!(CALL_COUNT.get(), 2);
296    }
297
298    /// Tests that an error received when opening a child's exposed directory
299    /// results in an appropriate error from `open_exposed_dir`.
300    #[fuchsia::test]
301    async fn open_child_component_exposed_dir_error() {
302        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
303            match realm_request {
304                fcomponent::RealmRequest::OpenExposedDir {
305                    child: _,
306                    exposed_dir: _,
307                    responder,
308                } => {
309                    let _ = responder.send(Err(fcomponent::Error::Internal));
310                }
311                _ => panic!("Realm handler received an unexpected request"),
312            }
313        });
314
315        let (_exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
316        assert!(
317            open_child_component_exposed_dir("", "", &realm_proxy, exposed_dir_server_end)
318                .await
319                .is_err()
320        );
321    }
322
323    /// Tests that `destroy_child` results in the appropriate call to `RealmProxy`.
324    #[fuchsia::test]
325    async fn destroy_child_parameters() {
326        let child_name = "test_child";
327        let child_collection = "test_collection";
328
329        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
330            match realm_request {
331                fcomponent::RealmRequest::DestroyChild { child, responder } => {
332                    assert_eq!(child.name, child_name);
333                    assert_eq!(child.collection, Some(child_collection.to_string()));
334
335                    let _ = responder.send(Ok(()));
336                }
337                _ => panic!("Realm handler received an unexpected request"),
338            }
339        });
340
341        assert!(destroy_child_component(child_name, child_collection, &realm_proxy).await.is_ok());
342    }
343}