blob: 0ea095221de5b4ba91d73741811cffb2282e5856 [file] [log] [blame] [edit]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::{anyhow, Error};
use fidl_fuchsia_component::{ChildIteratorMarker, RealmProxy};
use fidl_fuchsia_component_decl as cdecl;
use fidl_fuchsia_virtualization::{GuestError, GuestLifecycleProxy};
use fuchsia_component_test::{Capability, ChildOptions, RealmBuilder, RealmInstance, Ref, Route};
static VMM_URL: &'static str = "#meta/vmm.cm";
async fn build_test_realm(vmm_url: &'static str) -> Result<RealmInstance, Error> {
let builder = RealmBuilder::new().await?;
let vmm_launcher = builder
.add_child("vmm_launcher", "#meta/vmm_launcher.cm", ChildOptions::new().eager())
.await
.expect("Failed to add vmm_launcher child");
// Override the component_url for the vmm so that we can test both the C++ and Rust vmm
// binaries.
builder.init_mutable_config_from_package(&vmm_launcher).await.unwrap();
builder.set_config_value(&vmm_launcher, "vmm_component_url", vmm_url.into()).await.unwrap();
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.virtualization.GuestLifecycle"))
.from(&vmm_launcher)
.to(Ref::parent()),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.kernel.HypervisorResource"))
.from(Ref::parent())
.to(&vmm_launcher),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.kernel.VmexResource"))
.from(Ref::parent())
.to(&vmm_launcher),
)
.await?;
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.component.Realm"))
.from(&vmm_launcher)
.to(Ref::parent()),
)
.await?;
builder.build().await.map_err(|e| anyhow!("Failed to build realm: {}", e))
}
async fn list_realm_children(test_realm: &RealmInstance) -> Result<Vec<cdecl::ChildRef>, Error> {
let realm: RealmProxy = test_realm.root.connect_to_protocol_at_exposed_dir()?;
let collection_ref = cdecl::CollectionRef { name: "virtual_machine_managers".to_string() };
let (child_iterator, child_iterator_server_end) =
fidl::endpoints::create_proxy::<ChildIteratorMarker>();
realm.list_children(&collection_ref, child_iterator_server_end).await.unwrap().unwrap();
let mut result: Vec<cdecl::ChildRef> = Vec::new();
while let Ok(mut children) = child_iterator.next().await {
if children.is_empty() {
break;
}
result.append(&mut children);
}
Ok(result)
}
async fn start_vmm(realm: &RealmInstance) -> Result<GuestLifecycleProxy, Error> {
let lifecycle: GuestLifecycleProxy = realm.root.connect_to_protocol_at_exposed_dir()?;
// Send a run RPC to the guest. This is to ensure that the component is completely started.
let response = lifecycle.run().await.expect("Failed to connect to vmm");
assert_eq!(Err(GuestError::NotCreated), response);
Ok(lifecycle)
}
async fn vmm_launcher_launches_vmm(url: &'static str) -> Result<(), Error> {
let realm = build_test_realm(url).await.unwrap();
let _lifecycle = start_vmm(&realm).await.expect("Failed to start VMM");
Ok(())
}
#[fuchsia::test]
async fn test_vmm_launcher_launches_vmm() -> Result<(), Error> {
vmm_launcher_launches_vmm(VMM_URL).await
}
async fn spawn_component_per_connection(url: &'static str) -> Result<(), Error> {
let test_realm = build_test_realm(url).await.unwrap();
// Expect no children to start.
let children = list_realm_children(&test_realm).await.unwrap();
assert_eq!(children.len(), 0);
// Connect to lifecycle, expect a new realm child.
let _lifecycle1 = start_vmm(&test_realm).await.expect("Failed to start first vmm");
let children = list_realm_children(&test_realm).await.unwrap();
assert_eq!(children.len(), 1);
// Make a second connection, expect a new child to be created.
let _lifecycle2 = start_vmm(&test_realm).await.expect("Failed to start second vmm");
let children = list_realm_children(&test_realm).await.unwrap();
assert_eq!(children.len(), 2);
Ok(())
}
#[fuchsia::test]
async fn test_component_per_connection_vmm() -> Result<(), Error> {
spawn_component_per_connection(VMM_URL).await
}
async fn drop_guest_lifecycle_terminates_component(url: &'static str) -> Result<(), Error> {
let test_realm = build_test_realm(url).await.unwrap();
// Expect no children to start.
let children = list_realm_children(&test_realm).await.unwrap();
assert_eq!(children.len(), 0);
// Connect to lifecycle, expect a new realm child.
let lifecycle = start_vmm(&test_realm).await.expect("Failed to start first vmm");
let children = list_realm_children(&test_realm).await.unwrap();
assert_eq!(children.len(), 1);
// Drop the lifecycle channel and expect the vmm component to eventually terminate.
std::mem::drop(lifecycle);
loop {
let children = list_realm_children(&test_realm).await.unwrap();
if children.is_empty() {
break;
}
std::thread::sleep(std::time::Duration::from_millis(100));
}
Ok(())
}
#[fuchsia::test]
async fn test_drop_guest_lifecycle_terminates_vmm() -> Result<(), Error> {
drop_guest_lifecycle_terminates_component(VMM_URL).await
}