| // Copyright 2018 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. |
| |
| #![feature(async_await, await_macro, futures_api)] |
| |
| use failure::{format_err, Error, ResultExt}; |
| |
| use std::fs; |
| use std::io; |
| use std::path::Path; |
| |
| use fidl; |
| use fidl::endpoints::ServiceMarker; |
| use fidl_fuchsia_netemul_bus::BusManagerMarker; |
| use fidl_fuchsia_netemul_environment::{ |
| EnvironmentOptions, LaunchService, ManagedEnvironmentMarker, VirtualDevice, |
| }; |
| use fidl_fuchsia_netemul_network::{ |
| DeviceProxy_Marker, EndpointBacking, EndpointConfig, EndpointManagerMarker, |
| NetworkContextMarker, |
| }; |
| use fidl_fuchsia_netstack::NetstackMarker; |
| use fidl_fuchsia_sys::{ |
| ComponentControllerEvent, ComponentControllerEventStream, ComponentControllerMarker, |
| LaunchInfo, LauncherMarker, TerminationReason, |
| }; |
| |
| use fuchsia_app::client; |
| use fuchsia_async as fasync; |
| use futures::TryStreamExt; |
| use structopt::StructOpt; |
| |
| const EP_NAME: &str = "ep0"; |
| const EP_MOUNT: &str = "class/ethernet/ep0"; |
| const MY_PACKAGE: &str = "fuchsia-pkg://fuchsia.com/netemul_sandbox_test#meta/svc_list_run.cmx"; |
| const NETSTACK_URL: &str = "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx"; |
| const SKIP_DIRS: &'static [&str] = &["/data", "/pkg"]; |
| const FAKE_SVC_NAME: &str = "fuchsia.some.fake.Service"; |
| const FAKE_SVC_URL: &str = "fuchsia-pkg://fuchsia.com/fake#meta/fake.cmx"; |
| |
| fn visit_dirs(dir: &Path) -> io::Result<()> { |
| let strpath = dir.to_str().unwrap(); |
| if SKIP_DIRS.contains(&strpath) { |
| // skip some of the entries to avoid clogging the logs |
| println!("{}/[...]", strpath); |
| } else if dir.is_dir() { |
| for entry in fs::read_dir(dir)? { |
| let entry = entry?; |
| let path = entry.path(); |
| let str = path.to_str().expect("paths need strings?"); |
| println!("{}", str); |
| visit_dirs(dir.join(path).as_path())?; |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn wait_for_component( |
| component_events: &mut ComponentControllerEventStream, |
| ) -> Result<(), Error> { |
| // wait for child to exit and mimic the result code |
| let result = loop { |
| let event = await!(component_events.try_next()) |
| .context("wait for child component to exit")? |
| .ok_or_else(|| format_err!("Child didn't exit cleanly"))?; |
| |
| match event { |
| ComponentControllerEvent::OnTerminated { |
| return_code: code, |
| termination_reason: reason, |
| } => { |
| println!("Child exited with code {}, reason {}", code, reason as u32); |
| if code != 0 || reason != TerminationReason::Exited { |
| break Err(format_err!( |
| "Child exited with code {}, reason {}", |
| code, |
| reason as u32 |
| )); |
| } else { |
| break Ok(()); |
| } |
| } |
| _ => { |
| continue; |
| } |
| } |
| }; |
| result |
| } |
| |
| // this is the main body of our test, which |
| // runs in an executor |
| async fn run_test() -> Result<(), Error> { |
| // connect to NetworkContext and ManagedEnvironment services |
| let netctx = client::connect_to_service::<NetworkContextMarker>()?; |
| let env = client::connect_to_service::<ManagedEnvironmentMarker>()?; |
| |
| // get the endpoint manager |
| let (epm, epmch) = fidl::endpoints::create_proxy::<EndpointManagerMarker>()?; |
| netctx.get_endpoint_manager(epmch)?; |
| |
| let mut cfg = EndpointConfig { |
| backing: EndpointBacking::Ethertap, |
| mac: None, |
| mtu: 1500, |
| }; |
| |
| // create a network endpoint |
| let (_, ep) = await!(epm.create_endpoint(EP_NAME, &mut cfg))?; |
| let ep = ep.unwrap().into_proxy()?; |
| |
| // get the endpoint proxy to pass to child environment |
| let (ep_proxy_client, ep_proxy_server) = |
| fidl::endpoints::create_endpoints::<DeviceProxy_Marker>()?; |
| ep.get_proxy_(ep_proxy_server)?; |
| |
| // prepare a child managed environment |
| let (child_env, child_env_server) = |
| fidl::endpoints::create_proxy::<ManagedEnvironmentMarker>()?; |
| |
| let mut env_options = EnvironmentOptions { |
| name: String::from("child_env"), |
| services: vec![LaunchService { |
| name: String::from(NetstackMarker::NAME), |
| url: String::from(NETSTACK_URL), |
| arguments: None, |
| }], |
| // pass the endpoint's proxy to create a virtual device |
| devices: vec![VirtualDevice { |
| path: String::from(EP_MOUNT), |
| device: ep_proxy_client, |
| }], |
| inherit_parent_launch_services: false, |
| }; |
| // launch the child env |
| env.create_child_environment(child_env_server, &mut env_options)?; |
| |
| // launch as a process in the created environment. |
| let (launcher, launcher_req) = fidl::endpoints::create_proxy::<LauncherMarker>()?; |
| child_env.get_launcher(launcher_req)?; |
| |
| // launch info is our own package |
| // plus the command line argument to run the child proc |
| let mut linfo = LaunchInfo { |
| url: String::from(MY_PACKAGE), |
| arguments: Some(vec![String::from("-c")]), |
| additional_services: None, |
| directory_request: None, |
| err: None, |
| out: None, |
| flat_namespace: None, |
| }; |
| |
| let (comp_controller, comp_controller_req) = |
| fidl::endpoints::create_proxy::<ComponentControllerMarker>()?; |
| let mut component_events = comp_controller.take_event_stream(); |
| launcher.create_component(&mut linfo, Some(comp_controller_req))?; |
| |
| await!(wait_for_component(&mut component_events)) |
| } |
| |
| fn check_service(service: &str) -> Result<(), Error> { |
| let fs_path = format!("/svc/{}", service); |
| let p = Path::new(&fs_path); |
| if p.exists() { |
| Ok(()) |
| } else { |
| Err(format_err!("Service {} does not exist", service)) |
| } |
| } |
| |
| fn check_virtual_device(vdev: &str) -> Result<(), Error> { |
| let fs_path = &format!("/vdev/{}", vdev); |
| let p = Path::new(fs_path); |
| if p.exists() { |
| Ok(()) |
| } else { |
| Err(format_err!("Virtual device {} does not exist", vdev)) |
| } |
| } |
| |
| fn check_vdata() -> Result<(), Error> { |
| if Path::new("/vdata/.THIS_IS_A_VIRTUAL_FS").exists() { |
| Ok(()) |
| } else { |
| Err(format_err!("/vdata does not exist")) |
| } |
| } |
| |
| async fn launch_grandchild() -> Result<(), Error> { |
| let env = client::connect_to_service::<ManagedEnvironmentMarker>()?; |
| |
| let mut env_options = EnvironmentOptions { |
| name: String::from("grandchild_env"), |
| // add some arbitrary service to the grandchild environment |
| services: vec![LaunchService { |
| name: String::from(FAKE_SVC_NAME), |
| url: String::from(FAKE_SVC_URL), |
| arguments: None, |
| }], |
| devices: vec![], |
| // inherit parent configuration to check if netstack flows through |
| // this won't be the same netstack *instance*, though. But it should be |
| // launched with the same url as the "child" environment |
| inherit_parent_launch_services: true, |
| }; |
| |
| let (child_env, child_env_server) = |
| fidl::endpoints::create_proxy::<ManagedEnvironmentMarker>()?; |
| |
| // launch the grandchild env |
| env.create_child_environment(child_env_server, &mut env_options)?; |
| |
| // launch info is our own package |
| // plus the command line argument to run the grandchild proc |
| let mut linfo = LaunchInfo { |
| url: String::from(MY_PACKAGE), |
| arguments: Some(vec![String::from("-g")]), |
| additional_services: None, |
| directory_request: None, |
| err: None, |
| out: None, |
| flat_namespace: None, |
| }; |
| |
| // launch myself as a process in the created environment. |
| let (launcher, launcher_req) = fidl::endpoints::create_proxy::<LauncherMarker>()?; |
| child_env.get_launcher(launcher_req)?; |
| |
| let (comp_controller, comp_controller_req) = |
| fidl::endpoints::create_proxy::<ComponentControllerMarker>()?; |
| let mut component_events = comp_controller.take_event_stream(); |
| launcher.create_component(&mut linfo, Some(comp_controller_req))?; |
| |
| await!(wait_for_component(&mut component_events)) |
| } |
| |
| fn main() -> Result<(), Error> { |
| // make sure all services exist! |
| check_vdata()?; |
| check_service(NetworkContextMarker::NAME)?; |
| check_service(ManagedEnvironmentMarker::NAME)?; |
| check_service(BusManagerMarker::NAME)?; |
| |
| #[derive(StructOpt, Debug)] |
| struct Opt { |
| #[structopt(short = "c")] |
| is_child: bool, |
| #[structopt(short = "g")] |
| is_grandchild: bool, |
| } |
| |
| let opt = Opt::from_args(); |
| // the same binary is used for the root test |
| // and the test in child envs |
| // a flag is passed on the command line to change |
| // the code path |
| if opt.is_child { |
| let mut executor = fasync::Executor::new().context("Error creating executor")?; |
| println!("Running as child"); |
| // print whole namespace to console (for manual testing) |
| visit_dirs(Path::new("/"))?; |
| // check that the virtual ethernet device is there |
| check_virtual_device(EP_MOUNT)?; |
| // check that netstack was served |
| check_service(NetstackMarker::NAME)?; |
| // launch grandchild service to test environment inheritance |
| executor.run_singlethreaded(launch_grandchild()) |
| } else if opt.is_grandchild { |
| println!("Running as grandchild"); |
| // assert that netstack was served (should be present due to inheritance) |
| check_service(NetstackMarker::NAME)?; |
| // and the fake service: |
| check_service(FAKE_SVC_NAME)?; |
| Ok(()) |
| } else { |
| let mut executor = fasync::Executor::new().context("Error creating executor")?; |
| executor.run_singlethreaded(run_test()) |
| } |
| } |