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, spawn_stream_handler};
103    use lazy_static::lazy_static;
104    use session_testing::spawn_directory_server;
105    use test_util::Counter;
106    use {fidl_fuchsia_component as fcomponent, fidl_fuchsia_io as fio};
107
108    /// Tests that creating a child results in the appropriate call to the `RealmProxy`.
109    #[fuchsia::test]
110    async fn create_child_parameters() {
111        let child_name = "test_child";
112        let child_url = "test_url";
113        let child_collection = "test_collection";
114
115        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
116            match realm_request {
117                fcomponent::RealmRequest::CreateChild { collection, decl, args: _, responder } => {
118                    assert_eq!(decl.name.unwrap(), child_name);
119                    assert_eq!(decl.url.unwrap(), child_url);
120                    assert_eq!(&collection.name, child_collection);
121
122                    let _ = responder.send(Ok(()));
123                }
124                _ => panic!("Realm handler received an unexpected request"),
125            }
126        });
127
128        assert!(create_child_component(
129            child_name,
130            child_url,
131            child_collection,
132            Default::default(),
133            &realm_proxy
134        )
135        .await
136        .is_ok());
137    }
138
139    /// Tests that a success received when creating a child results in an appropriate result from
140    /// `create_child`.
141    #[fuchsia::test]
142    async fn create_child_success() {
143        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
144            match realm_request {
145                fcomponent::RealmRequest::CreateChild {
146                    collection: _,
147                    decl: _,
148                    args: _,
149                    responder,
150                } => {
151                    let _ = responder.send(Ok(()));
152                }
153                _ => panic!("Realm handler received an unexpected request"),
154            }
155        });
156
157        assert!(create_child_component("", "", "", Default::default(), &realm_proxy).await.is_ok());
158    }
159
160    /// Tests that an error received when creating a child results in an appropriate error from
161    /// `create_child`.
162    #[fuchsia::test]
163    async fn create_child_error() {
164        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
165            match realm_request {
166                fcomponent::RealmRequest::CreateChild {
167                    collection: _,
168                    decl: _,
169                    args: _,
170                    responder,
171                } => {
172                    let _ = responder.send(Err(fcomponent::Error::Internal));
173                }
174                _ => panic!("Realm handler received an unexpected request"),
175            }
176        });
177
178        assert!(create_child_component("", "", "", Default::default(), &realm_proxy)
179            .await
180            .is_err());
181    }
182
183    /// Tests that `open_child_component_exposed_dir` results in the appropriate call to `RealmProxy`.
184    #[fuchsia::test]
185    async fn open_child_component_exposed_dir_parameters() {
186        let child_name = "test_child";
187        let child_collection = "test_collection";
188
189        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
190            match realm_request {
191                fcomponent::RealmRequest::OpenExposedDir { child, exposed_dir: _, responder } => {
192                    assert_eq!(child.name, child_name);
193                    assert_eq!(child.collection, Some(child_collection.to_string()));
194
195                    let _ = responder.send(Ok(()));
196                }
197                _ => panic!("Realm handler received an unexpected request"),
198            }
199        });
200
201        let (_exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
202        assert!(open_child_component_exposed_dir(
203            child_name,
204            child_collection,
205            &realm_proxy,
206            exposed_dir_server_end
207        )
208        .await
209        .is_ok());
210    }
211
212    /// Tests that a success received when opening a child's exposed directory
213    /// results in an appropriate result from `open_child_component_exposed_dir`.
214    #[fuchsia::test]
215    async fn open_child_component_exposed_dir_success() {
216        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
217            match realm_request {
218                fcomponent::RealmRequest::OpenExposedDir {
219                    child: _,
220                    exposed_dir: _,
221                    responder,
222                } => {
223                    let _ = responder.send(Ok(()));
224                }
225                _ => panic!("Realm handler received an unexpected request"),
226            }
227        });
228
229        let (_exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
230        assert!(open_child_component_exposed_dir("", "", &realm_proxy, exposed_dir_server_end)
231            .await
232            .is_ok());
233    }
234
235    /// Tests that opening a child's exposed directory returns successfully.
236    #[fuchsia::test]
237    async fn open_child_exposed_dir_success() {
238        // Make a static call counter to avoid unneeded complexity with cloned Arc<Mutex>.
239        lazy_static! {
240            static ref CALL_COUNT: Counter = Counter::new(0);
241        }
242
243        let directory_request_handler = |directory_request| match directory_request {
244            fio::DirectoryRequest::DeprecatedOpen { path: fake_capability_path, .. } => {
245                CALL_COUNT.inc();
246                assert_eq!(fake_capability_path, "fake_capability_path");
247            }
248            _ => panic!("Directory handler received an unexpected request"),
249        };
250
251        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
252            match realm_request {
253                fcomponent::RealmRequest::OpenExposedDir { child: _, exposed_dir, responder } => {
254                    CALL_COUNT.inc();
255                    spawn_directory_server(exposed_dir, directory_request_handler);
256                    let _ = responder.send(Ok(()));
257                }
258                _ => panic!("Realm handler received an unexpected request"),
259            }
260        });
261
262        let (exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
263        open_child_component_exposed_dir("", "", &realm_proxy, exposed_dir_server_end)
264            .await
265            .unwrap();
266
267        // Create a proxy of any FIDL protocol, with any `await`-able method.
268        // (`fio::DirectoryMarker` here is arbitrary.)
269        let (proxy, server_end) = create_proxy::<fio::DirectoryMarker>();
270
271        // Connect should succeed, but it is still an asynchronous operation.
272        // The `directory_request_handler` is not called yet.
273        assert!(fdio::service_connect_at(
274            &exposed_dir.into_channel(),
275            "fake_capability_path",
276            server_end.into_channel()
277        )
278        .is_ok());
279
280        // Attempting to invoke and await an arbitrary method to ensure the
281        // `directory_request_handler` responds to the Open() method and increment
282        // the DIRECTORY_OPEN_CALL_COUNT.
283        //
284        // Since this is a fake capability (of any arbitrary type), it should fail.
285        assert!(proxy.rewind().await.is_err());
286
287        // Calls to Realm::OpenExposedDir and Directory::Open should have happened.
288        assert_eq!(CALL_COUNT.get(), 2);
289    }
290
291    /// Tests that an error received when opening a child's exposed directory
292    /// results in an appropriate error from `open_exposed_dir`.
293    #[fuchsia::test]
294    async fn open_child_component_exposed_dir_error() {
295        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
296            match realm_request {
297                fcomponent::RealmRequest::OpenExposedDir {
298                    child: _,
299                    exposed_dir: _,
300                    responder,
301                } => {
302                    let _ = responder.send(Err(fcomponent::Error::Internal));
303                }
304                _ => panic!("Realm handler received an unexpected request"),
305            }
306        });
307
308        let (_exposed_dir, exposed_dir_server_end) = create_endpoints::<fio::DirectoryMarker>();
309        assert!(open_child_component_exposed_dir("", "", &realm_proxy, exposed_dir_server_end)
310            .await
311            .is_err());
312    }
313
314    /// Tests that `destroy_child` results in the appropriate call to `RealmProxy`.
315    #[fuchsia::test]
316    async fn destroy_child_parameters() {
317        let child_name = "test_child";
318        let child_collection = "test_collection";
319
320        let realm_proxy = spawn_stream_handler(move |realm_request| async move {
321            match realm_request {
322                fcomponent::RealmRequest::DestroyChild { child, responder } => {
323                    assert_eq!(child.name, child_name);
324                    assert_eq!(child.collection, Some(child_collection.to_string()));
325
326                    let _ = responder.send(Ok(()));
327                }
328                _ => panic!("Realm handler received an unexpected request"),
329            }
330        });
331
332        assert!(destroy_child_component(child_name, child_collection, &realm_proxy).await.is_ok());
333    }
334}