[netemul] Building environments based on cmx facet
Implements environment setup logic based on .cmx facet metadata
TEST: added env_build test to verify environment builds correctly
Change-Id: Ic6db486f07b6031121eefecd242c56f4149eda8f
diff --git a/bin/netemul_runner/BUILD.gn b/bin/netemul_runner/BUILD.gn
index 89cf1eb..186a9f5 100644
--- a/bin/netemul_runner/BUILD.gn
+++ b/bin/netemul_runner/BUILD.gn
@@ -23,6 +23,7 @@
]
deps = [
+ "//garnet/bin/netemul_runner/model",
"//garnet/lib/cmx:cmx",
"//garnet/lib/process",
"//garnet/public/fidl/fuchsia.sys",
@@ -30,6 +31,7 @@
"//garnet/public/lib/component/cpp/testing",
"//garnet/public/lib/fsl",
"//garnet/public/lib/fxl",
+ "//garnet/public/lib/json",
"//garnet/public/lib/netemul/bus:bus_service",
"//garnet/public/lib/netemul/fidl:bus",
"//garnet/public/lib/netemul/fidl:environment",
diff --git a/bin/netemul_runner/main.cc b/bin/netemul_runner/main.cc
index 03da980..d111e88 100644
--- a/bin/netemul_runner/main.cc
+++ b/bin/netemul_runner/main.cc
@@ -54,7 +54,7 @@
zx_process_exit(exit_code);
});
- sandbox.Start();
+ sandbox.Start(loop.dispatcher());
loop.Run();
return 0;
diff --git a/bin/netemul_runner/managed_environment.cc b/bin/netemul_runner/managed_environment.cc
index 9883bed..5ac452b 100644
--- a/bin/netemul_runner/managed_environment.cc
+++ b/bin/netemul_runner/managed_environment.cc
@@ -11,10 +11,8 @@
ManagedEnvironment::Ptr ManagedEnvironment::CreateRoot(
const fuchsia::sys::EnvironmentPtr& parent,
- const SandboxEnv::Ptr& sandbox_env) {
+ const SandboxEnv::Ptr& sandbox_env, Options options) {
auto ret = ManagedEnvironment::Ptr(new ManagedEnvironment(sandbox_env));
- Options options;
- options.name = "root";
ret->Create(parent, std::move(options));
return ret;
}
@@ -58,17 +56,22 @@
// prepare service configurations:
service_config_.clear();
if (options.inherit_parent_launch_services && managed_parent != nullptr) {
- service_config_.insert(service_config_.begin(),
- managed_parent->service_config_.begin(),
- managed_parent->service_config_.end());
+ for (const auto& a : managed_parent->service_config_) {
+ LaunchService clone;
+ a.Clone(&clone);
+ service_config_.push_back(std::move(clone));
+ }
}
- service_config_.insert(service_config_.begin(), options.services.begin(),
- options.services.end());
+
+ std::move(options.services.begin(), options.services.end(),
+ std::back_inserter(service_config_));
// push all the allowable launch services:
for (const auto& svc : service_config_) {
fuchsia::sys::LaunchInfo linfo;
linfo.url = svc.url;
+ linfo.arguments->insert(linfo.arguments->begin(), svc.arguments->begin(),
+ svc.arguments->end());
services->AddServiceWithLaunchInfo(std::move(linfo), svc.name);
}
@@ -82,6 +85,12 @@
.allow_parent_runners = false,
.inherit_parent_services = false};
+ // Nested environments without a name are not allowed, if empty name is
+ // provided, replace it with a default value:
+ if (options.name.empty()) {
+ options.name = "netemul-env";
+ }
+
env_ = EnclosingEnvironment::Create(options.name, parent, std::move(services),
sub_options);
@@ -105,4 +114,9 @@
return virtual_data_->GetDirectory();
}
+void ManagedEnvironment::Bind(
+ fidl::InterfaceRequest<ManagedEnvironment::FManagedEnvironment> req) {
+ bindings_.AddBinding(this, std::move(req));
+}
+
} // namespace netemul
diff --git a/bin/netemul_runner/managed_environment.h b/bin/netemul_runner/managed_environment.h
index f1e0eeb..60087dc 100644
--- a/bin/netemul_runner/managed_environment.h
+++ b/bin/netemul_runner/managed_environment.h
@@ -26,7 +26,7 @@
using FManagedEnvironment = fuchsia::netemul::environment::ManagedEnvironment;
using Ptr = std::unique_ptr<ManagedEnvironment>;
static Ptr CreateRoot(const fuchsia::sys::EnvironmentPtr& parent,
- const SandboxEnv::Ptr& sandbox_env);
+ const SandboxEnv::Ptr& sandbox_env, Options options);
const SandboxEnv::Ptr& sandbox_env() const { return sandbox_env_; }
@@ -49,6 +49,8 @@
running_callback_ = std::move(cb);
}
+ void Bind(fidl::InterfaceRequest<FManagedEnvironment> req);
+
protected:
friend ManagedLauncher;
diff --git a/bin/netemul_runner/sandbox.cc b/bin/netemul_runner/sandbox.cc
index 2658946..5c1e9ca 100644
--- a/bin/netemul_runner/sandbox.cc
+++ b/bin/netemul_runner/sandbox.cc
@@ -4,8 +4,10 @@
#include "sandbox.h"
#include <fcntl.h>
+#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/component/cpp/startup_context.h>
+#include <lib/component/cpp/termination_reason.h>
#include <lib/component/cpp/testing/test_util.h>
#include <lib/fdio/watcher.h>
#include <lib/fsl/io/fd.h>
@@ -15,17 +17,58 @@
#include <zircon/status.h>
#include "garnet/lib/cmx/cmx.h"
+using namespace fuchsia::netemul;
+
namespace netemul {
+static const char* kEndpointMountPath = "class/ethernet/";
+// give setup processes a maximum of 10s before bailing
+static const int kSetupTimeoutSecs = 10;
+
+#define STATIC_MSG_STRUCT(name, msgv) \
+ struct name { \
+ static const char* msg; \
+ }; \
+ const char* name::msg = msgv;
+
+STATIC_MSG_STRUCT(kMsgApp, "app");
+STATIC_MSG_STRUCT(kMsgTest, "test");
+STATIC_MSG_STRUCT(kMsgRoot, "root test");
+
+// Sandbox uses two threads to operate:
+// a main thread (which it's initialized with)
+// + a helper thread.
+// The macros below are used to assert that methods on
+// the sandbox class are called on the proper thread
+#define ASSERT_DISPATCHER(disp) \
+ ZX_ASSERT((disp) == async_get_default_dispatcher())
+#define ASSERT_MAIN_DISPATCHER ASSERT_DISPATCHER(main_dispatcher_)
+#define ASSERT_HELPER_DISPATCHER ASSERT_DISPATCHER(helper_loop_->dispatcher())
+
Sandbox::Sandbox(SandboxArgs args) : args_(std::move(args)) {
auto startup_context = component::StartupContext::CreateFromStartupInfo();
startup_context->ConnectToEnvironmentService(parent_env_.NewRequest());
startup_context->ConnectToEnvironmentService(loader_.NewRequest());
+ parent_env_.set_error_handler([](zx_status_t err) {
+ FXL_LOG(ERROR) << "Lost connection to parent environment";
+ });
}
-void Sandbox::Start() {
+void Sandbox::Start(async_dispatcher_t* dispatcher) {
if (!parent_env_ || !loader_) {
Terminate(TerminationReason::INTERNAL_ERROR);
+ return;
+ }
+
+ main_dispatcher_ = dispatcher;
+ setup_done_ = false;
+
+ helper_loop_ =
+ std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread);
+ if (helper_loop_->StartThread("helper-thread") != ZX_OK) {
+ FXL_LOG(ERROR) << "Can't start config thread";
+ Terminate(TerminationReason::INTERNAL_ERROR);
+ return;
}
loader_->LoadUrl(args_.package, [this](fuchsia::sys::PackagePtr package) {
@@ -40,16 +83,35 @@
}
void Sandbox::Terminate(int64_t exit_code, Sandbox::TerminationReason reason) {
+ // all processes must have been emptied to call callback
+ ASSERT_MAIN_DISPATCHER;
+ ZX_ASSERT(procs_.empty());
if (termination_callback_) {
termination_callback_(exit_code, reason);
}
}
+void Sandbox::PostTerminate(TerminationReason reason) {
+ ASSERT_HELPER_DISPATCHER;
+ PostTerminate(-1, reason);
+}
+
+void Sandbox::PostTerminate(int64_t exit_code, TerminationReason reason) {
+ ASSERT_HELPER_DISPATCHER;
+ // kill all component controllers before posting termination
+ procs_.clear();
+ async::PostTask(main_dispatcher_, [this, exit_code, reason]() {
+ Terminate(exit_code, reason);
+ });
+}
+
void Sandbox::Terminate(Sandbox::TerminationReason reason) {
+ ASSERT_MAIN_DISPATCHER;
Terminate(-1, reason);
}
void Sandbox::LoadPackage(fuchsia::sys::PackagePtr package) {
+ ASSERT_MAIN_DISPATCHER;
// package is loaded, proceed to parsing cmx and starting child env
component::FuchsiaPkgUrl pkgUrl;
if (!pkgUrl.Parse(package->resolved_url)) {
@@ -60,6 +122,7 @@
fxl::UniqueFD dirfd =
fsl::OpenChannelAsFileDescriptor(std::move(package->directory));
+
sandbox_env_ = std::make_shared<SandboxEnv>(args_.package, std::move(dirfd));
component::CmxMetadata cmx;
@@ -72,25 +135,369 @@
return;
}
- root_ = ManagedEnvironment::CreateRoot(parent_env_, sandbox_env_);
+ if (!env_config_.ParseFromJSON(cmx.GetFacet(config::Config::Facet),
+ &json_parser)) {
+ FXL_LOG(ERROR) << "netemul facet failed to parse: "
+ << json_parser.error_str();
+ Terminate(TerminationReason::INTERNAL_ERROR);
+ return;
+ }
- // TODO(brunodalbo) parameterize environment based on
- // facets in cmx file
+ async::PostTask(helper_loop_->dispatcher(), [this]() {
+ if (!ConfigureNetworks()) {
+ PostTerminate(TerminationReason::INTERNAL_ERROR);
+ return;
+ }
- root_->SetRunningCallback([this] {
- root_proc_.events().OnTerminated = [this](int64_t code,
- TerminationReason reason) {
- Terminate(code, reason); // mimic termination of root process
- };
+ ManagedEnvironment::Options root_options;
+ if (!CreateEnvironmentOptions(env_config_.environment(), &root_options)) {
+ PostTerminate(TerminationReason::INTERNAL_ERROR);
+ return;
+ }
- // start root test process:
- fuchsia::sys::LaunchInfo linfo;
- linfo.url = sandbox_env_->name();
- linfo.out = component::testing::CloneFileDescriptor(STDOUT_FILENO);
- linfo.err = component::testing::CloneFileDescriptor(STDERR_FILENO);
- root_->launcher().CreateComponent(std::move(linfo),
- root_proc_.NewRequest());
+ async::PostTask(main_dispatcher_,
+ [this, root_options = std::move(root_options)]() mutable {
+ ASSERT_MAIN_DISPATCHER;
+ root_ = ManagedEnvironment::CreateRoot(
+ parent_env_, sandbox_env_, std::move(root_options));
+ root_->SetRunningCallback([this]() {
+ // configure root environment:
+ async::PostTask(helper_loop_->dispatcher(), [this]() {
+ if (!ConfigureRootEnvironment()) {
+ PostTerminate(TerminationReason::INTERNAL_ERROR);
+ }
+ });
+ });
+ });
});
}
-} // namespace netemul
\ No newline at end of file
+// configure networks runs in an auxiliary thread, so we can use
+// synchronous calls to the fidl service
+bool Sandbox::ConfigureNetworks() {
+ ASSERT_HELPER_DISPATCHER;
+ // start by configuring the networks:
+
+ if (env_config_.networks().empty()) {
+ return true;
+ }
+
+ network::NetworkContextSyncPtr net_ctx;
+
+ auto req = net_ctx.NewRequest();
+
+ // bind to network context
+ async::PostTask(main_dispatcher_, [req = std::move(req), this]() mutable {
+ sandbox_env_->network_context().GetHandler()(std::move(req));
+ });
+
+ network::NetworkManagerSyncPtr net_manager;
+ network::EndpointManagerSyncPtr endp_manager;
+ net_ctx->GetNetworkManager(net_manager.NewRequest());
+ net_ctx->GetEndpointManager(endp_manager.NewRequest());
+
+ for (const auto& net_cfg : env_config_.networks()) {
+ zx_status_t status;
+ fidl::InterfaceHandle<network::Network> network_h;
+ if (net_manager->CreateNetwork(net_cfg.name(), network::NetworkConfig(),
+ &status, &network_h) != ZX_OK ||
+ status != ZX_OK) {
+ FXL_LOG(ERROR) << "Create network failed";
+ return false;
+ }
+
+ auto network = network_h.BindSync();
+
+ for (const auto& endp_cfg : net_cfg.endpoints()) {
+ network::EndpointConfig fidl_config;
+ fidl::InterfaceHandle<network::Endpoint> endp_h;
+
+ fidl_config.backing = network::EndpointBacking::ETHERTAP;
+ fidl_config.mtu = endp_cfg.mtu();
+ if (endp_cfg.mac()) {
+ fidl_config.mac =
+ std::make_unique<fuchsia::hardware::ethernet::MacAddress>();
+ memcpy(&fidl_config.mac->octets[0], endp_cfg.mac()->d, 6);
+ }
+
+ if (endp_manager->CreateEndpoint(endp_cfg.name(), std::move(fidl_config),
+ &status, &endp_h) != ZX_OK ||
+ status != ZX_OK) {
+ FXL_LOG(ERROR) << "Create endpoint failed";
+ return false;
+ }
+
+ auto endp = endp_h.BindSync();
+
+ if (endp_cfg.up()) {
+ if (endp->SetLinkUp(true) != ZX_OK) {
+ FXL_LOG(ERROR) << "Set endpoint up failed";
+ return false;
+ }
+ }
+
+ // add endpoint to network:
+ if (network->AttachEndpoint(endp_cfg.name(), &status) != ZX_OK ||
+ status != ZX_OK) {
+ FXL_LOG(ERROR) << "Attaching endpoint " << endp_cfg.name()
+ << " to network " << net_cfg.name() << " failed";
+ return false;
+ }
+
+ // save the endpoint handle:
+ network_handles_.emplace_back(endp.Unbind().TakeChannel());
+ }
+
+ // save the network handle:
+ network_handles_.emplace_back(network.Unbind().TakeChannel());
+ }
+
+ return true;
+}
+
+// Create environment options runs in an auxiliary thread, so we can use
+// synchronous calls to fidl services
+bool Sandbox::CreateEnvironmentOptions(const config::Environment& config,
+ ManagedEnvironment::Options* options) {
+ ASSERT_HELPER_DISPATCHER;
+ options->name = config.name();
+ options->inherit_parent_launch_services = config.inherit_services();
+
+ std::vector<environment::VirtualDevice>& devices = options->devices;
+ ;
+ if (!config.devices().empty()) {
+ network::EndpointManagerSyncPtr epm;
+ async::PostTask(main_dispatcher_, [req = epm.NewRequest(), this]() mutable {
+ sandbox_env_->network_context().endpoint_manager().Bind(std::move(req));
+ });
+ for (const auto& device : config.devices()) {
+ auto& nd = devices.emplace_back();
+ nd.path = fxl::Concatenate({std::string(kEndpointMountPath), device});
+
+ fidl::InterfaceHandle<network::Endpoint> endp_h;
+ if (epm->GetEndpoint(device, &endp_h) != ZX_OK) {
+ FXL_LOG(ERROR) << "Can't find endpoint " << device
+ << " on endpoint manager";
+ return false;
+ }
+
+ auto endp = endp_h.BindSync();
+ if (endp->GetProxy(nd.device.NewRequest()) != ZX_OK) {
+ FXL_LOG(ERROR) << "Can't get proxy on endpoint " << device;
+ return false;
+ }
+ }
+ }
+
+ std::vector<environment::LaunchService>& services = options->services;
+ for (const auto& svc : config.services()) {
+ auto& ns = services.emplace_back();
+ ns.name = svc.name();
+ ns.url = svc.launch().GetUrlOrDefault(sandbox_env_->name());
+ ns.arguments->insert(ns.arguments->end(), svc.launch().arguments().begin(),
+ svc.launch().arguments().end());
+ }
+
+ return true;
+}
+
+bool Sandbox::ConfigureRootEnvironment() {
+ ASSERT_HELPER_DISPATCHER;
+ // connect to environment:
+ environment::ManagedEnvironmentSyncPtr svc;
+ auto req = svc.NewRequest();
+
+ async::PostTask(main_dispatcher_, [this, req = std::move(req)]() mutable {
+ root_->Bind(std::move(req));
+ });
+
+ // configure with service pointer:
+ return ConfigureEnvironment(std::move(svc), env_config_.environment(), true);
+}
+
+// Configure environment runs in an auxiliary thread, so we can use synchronous
+// calls to fidl services
+bool Sandbox::ConfigureEnvironment(
+ fidl::SynchronousInterfacePtr<ManagedEnvironment::FManagedEnvironment> env,
+ const config::Environment& config, bool root) {
+ ASSERT_HELPER_DISPATCHER;
+ // iterate on children
+ for (const auto& child : config.children()) {
+ ManagedEnvironment::Options options;
+ if (!CreateEnvironmentOptions(child, &options)) {
+ return false;
+ }
+ environment::ManagedEnvironmentSyncPtr child_env;
+ if (env->CreateChildEnvironment(child_env.NewRequest(),
+ std::move(options)) != ZX_OK) {
+ FXL_LOG(ERROR) << "Creating environment \"" << child.name()
+ << "\" failed";
+ return false;
+ }
+
+ // child environment was successfully created, configure child:
+ if (!ConfigureEnvironment(std::move(child_env), child)) {
+ return false;
+ }
+ }
+
+ // get launcher
+ fuchsia::sys::LauncherSyncPtr launcher;
+ if (env->GetLauncher(launcher.NewRequest()) != ZX_OK) {
+ FXL_LOG(ERROR) << "Can't get environment launcher";
+ return false;
+ }
+
+ for (const auto& app : config.apps()) {
+ if (!LaunchProcess<kMsgApp>(&launcher,
+ app.GetUrlOrDefault(sandbox_env_->name()),
+ app.arguments(), false)) {
+ return false;
+ }
+ }
+
+ for (const auto& setup : config.setup()) {
+ if (!LaunchSetup(&launcher, setup.GetUrlOrDefault(sandbox_env_->name()),
+ setup.arguments())) {
+ return false;
+ }
+ }
+
+ for (const auto& test : config.test()) {
+ if (!LaunchProcess<kMsgTest>(&launcher,
+ test.GetUrlOrDefault(sandbox_env_->name()),
+ test.arguments(), true)) {
+ return false;
+ }
+ }
+
+ if (root) {
+ if (!LaunchProcess<kMsgRoot>(&launcher, sandbox_env_->name(), args_.args,
+ true)) {
+ return false;
+ }
+ EnableTestObservation();
+ }
+
+ return true;
+}
+
+template <typename T>
+bool Sandbox::LaunchProcess(fuchsia::sys::LauncherSyncPtr* launcher,
+ const std::string& url,
+ const std::vector<std::string>& arguments,
+ bool is_test) {
+ ASSERT_HELPER_DISPATCHER;
+
+ fuchsia::sys::LaunchInfo linfo;
+ linfo.url = url;
+ linfo.arguments->insert(linfo.arguments->end(), arguments.begin(),
+ arguments.end());
+ linfo.out = component::testing::CloneFileDescriptor(STDOUT_FILENO);
+ linfo.err = component::testing::CloneFileDescriptor(STDERR_FILENO);
+
+ auto& proc = procs_.emplace_back();
+ auto ticket = procs_.size();
+
+ if (is_test) {
+ RegisterTest(ticket);
+ }
+
+ // we observe test processes return code
+ proc.events().OnTerminated = [url, this, is_test, ticket](
+ int64_t code, TerminationReason reason) {
+ FXL_LOG(INFO) << T::msg << " " << url << " terminated with (" << code
+ << ") reason: "
+ << component::HumanReadableTerminationReason(reason);
+ if (is_test) {
+ if (code != 0 || reason != TerminationReason::EXITED) {
+ // test failed, early bail
+ PostTerminate(code, reason);
+ } else {
+ // unregister test ticket
+ UnregisterTest(ticket);
+ }
+ }
+ };
+
+ if ((*launcher)->CreateComponent(std::move(linfo), proc.NewRequest()) !=
+ ZX_OK) {
+ FXL_LOG(ERROR) << "couldn't launch " << T::msg << ": " << url;
+ return false;
+ }
+
+ return true;
+}
+
+bool Sandbox::LaunchSetup(fuchsia::sys::LauncherSyncPtr* launcher,
+ const std::string& url,
+ const std::vector<std::string>& arguments) {
+ ASSERT_HELPER_DISPATCHER;
+
+ fuchsia::sys::LaunchInfo linfo;
+ linfo.url = url;
+ linfo.arguments->insert(linfo.arguments->end(), arguments.begin(),
+ arguments.end());
+ linfo.out = component::testing::CloneFileDescriptor(STDOUT_FILENO);
+ linfo.err = component::testing::CloneFileDescriptor(STDERR_FILENO);
+
+ fuchsia::sys::ComponentControllerPtr proc;
+ bool done = false;
+ bool success = false;
+
+ // we observe test processes return code
+ proc.events().OnTerminated = [url, this, &done, &success](
+ int64_t code, TerminationReason reason) {
+ FXL_LOG(INFO) << "Setup " << url << " terminated with (" << code
+ << ") reason: "
+ << component::HumanReadableTerminationReason(reason);
+ done = true;
+ success = code == 0 && reason == TerminationReason::EXITED;
+ };
+
+ if ((*launcher)->CreateComponent(std::move(linfo), proc.NewRequest()) !=
+ ZX_OK) {
+ FXL_LOG(ERROR) << "couldn't launch setup: " << url;
+ return false;
+ }
+
+ while (!done) {
+ auto status =
+ helper_loop_->Run(zx::deadline_after(zx::sec(kSetupTimeoutSecs)), true);
+ if (status != ZX_OK) {
+ FXL_LOG(ERROR) << "Setup " << url << " run loop exited with: "
+ << zx_status_get_string(status);
+ return false;
+ }
+ }
+
+ return success;
+}
+
+void Sandbox::EnableTestObservation() {
+ ASSERT_HELPER_DISPATCHER;
+
+ setup_done_ = true;
+ if (tests_.empty()) {
+ // all tests finished successfully
+ PostTerminate(0, TerminationReason::EXITED);
+ }
+}
+
+void Sandbox::RegisterTest(size_t ticket) {
+ ASSERT_HELPER_DISPATCHER;
+
+ tests_.insert(ticket);
+}
+
+void Sandbox::UnregisterTest(size_t ticket) {
+ ASSERT_HELPER_DISPATCHER;
+
+ tests_.erase(ticket);
+ if (setup_done_ && tests_.empty()) {
+ // all tests finished successfully
+ PostTerminate(0, TerminationReason::EXITED);
+ }
+}
+
+} // namespace netemul
diff --git a/bin/netemul_runner/sandbox.h b/bin/netemul_runner/sandbox.h
index 71bd3a2..3e21b26 100644
--- a/bin/netemul_runner/sandbox.h
+++ b/bin/netemul_runner/sandbox.h
@@ -6,7 +6,10 @@
#define GARNET_BIN_NETEMUL_RUNNER_SANDBOX_H_
#include <fuchsia/sys/cpp/fidl.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <unordered_set>
#include "managed_environment.h"
+#include "model/config.h"
#include "sandbox_env.h"
namespace netemul {
@@ -28,21 +31,52 @@
termination_callback_ = std::move(callback);
}
- void Start();
+ void Start(async_dispatcher_t* dispatcher);
private:
void LoadPackage(fuchsia::sys::PackagePtr package);
void Terminate(TerminationReason reason);
void Terminate(int64_t exit_code, TerminationReason reason);
- void StartRootEnvironment();
+ void PostTerminate(TerminationReason reason);
+ void PostTerminate(int64_t exit_code, TerminationReason reason);
+ void EnableTestObservation();
+ void RegisterTest(size_t ticket);
+ void UnregisterTest(size_t ticket);
+
+ template <typename T>
+ bool LaunchProcess(fuchsia::sys::LauncherSyncPtr* launcher,
+ const std::string& url,
+ const std::vector<std::string>& arguments, bool is_test);
+
+ bool LaunchSetup(fuchsia::sys::LauncherSyncPtr* launcher,
+ const std::string& url,
+ const std::vector<std::string>& arguments);
+
+ bool CreateEnvironmentOptions(const config::Environment& config,
+ ManagedEnvironment::Options* options);
+ bool ConfigureRootEnvironment();
+ bool ConfigureEnvironment(
+ fidl::SynchronousInterfacePtr<ManagedEnvironment::FManagedEnvironment>
+ env,
+ const config::Environment& config, bool root = false);
+ bool ConfigureNetworks();
+
+ async_dispatcher_t* main_dispatcher_;
+ std::unique_ptr<async::Loop> helper_loop_;
+ config::Config env_config_;
+ bool setup_done_;
SandboxArgs args_;
SandboxEnv::Ptr sandbox_env_;
TerminationCallback termination_callback_;
fuchsia::sys::EnvironmentPtr parent_env_;
fuchsia::sys::LoaderPtr loader_;
ManagedEnvironment::Ptr root_;
- fuchsia::sys::ComponentControllerPtr root_proc_;
+ // keep network handles to keep objects alive
+ std::vector<zx::channel> network_handles_;
+ // keep component controller handles to keep processes alive
+ std::vector<fuchsia::sys::ComponentControllerPtr> procs_;
+ std::unordered_set<size_t> tests_;
};
} // namespace netemul
diff --git a/bin/netemul_runner/test/BUILD.gn b/bin/netemul_runner/test/BUILD.gn
index e9ad883..d2125a1 100644
--- a/bin/netemul_runner/test/BUILD.gn
+++ b/bin/netemul_runner/test/BUILD.gn
@@ -7,6 +7,7 @@
test_package("netemul_sandbox_test") {
deps = [
"//garnet/bin/netemul_runner/model:model_unittest",
+ "//garnet/bin/netemul_runner/test/env_build",
"//garnet/bin/netemul_runner/test/netstack_socks",
"//garnet/bin/netemul_runner/test/svc_list",
]
@@ -20,6 +21,10 @@
path = rebase_path("meta/netstack_socks_run.cmx")
dest = "netstack_socks_run.cmx"
},
+ {
+ path = rebase_path("meta/env_build_run.cmx")
+ dest = "env_build_run.cmx"
+ },
]
tests = [
@@ -30,6 +35,9 @@
name = "netstack_socks"
},
{
+ name = "env_build"
+ },
+ {
name = "model_unittest"
},
]
diff --git a/bin/netemul_runner/test/env_build/BUILD.gn b/bin/netemul_runner/test/env_build/BUILD.gn
new file mode 100644
index 0000000..29e23c8
--- /dev/null
+++ b/bin/netemul_runner/test/env_build/BUILD.gn
@@ -0,0 +1,22 @@
+# 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.
+
+import("//build/package.gni")
+import("//build/rust/rustc_binary.gni")
+
+rustc_binary("env_build") {
+ name = "env_build"
+ edition = "2018"
+ deps = [
+ "//garnet/public/lib/fidl/rust/fidl",
+ "//garnet/public/lib/netemul/fidl:bus-rustc",
+ "//garnet/public/lib/netemul/fidl:network-rustc",
+ "//garnet/public/rust/fuchsia-app",
+ "//garnet/public/rust/fuchsia-async",
+ "//garnet/public/rust/fuchsia-zircon",
+ "//third_party/rust-crates/rustc_deps:failure",
+ "//third_party/rust-crates/rustc_deps:futures-preview",
+ "//third_party/rust-crates/rustc_deps:structopt",
+ ]
+}
diff --git a/bin/netemul_runner/test/env_build/src/main.rs b/bin/netemul_runner/test/env_build/src/main.rs
new file mode 100644
index 0000000..67b6a99
--- /dev/null
+++ b/bin/netemul_runner/test/env_build/src/main.rs
@@ -0,0 +1,265 @@
+// 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},
+ fidl_fuchsia_netemul_bus::{BusManagerMarker, BusMarker, BusProxy, Event},
+ fidl_fuchsia_netemul_network::{
+ EndpointManagerMarker, NetworkContextMarker, NetworkManagerMarker,
+ },
+ fuchsia_app::client,
+ fuchsia_async::{self as fasync, TimeoutExt},
+ fuchsia_zircon::DurationNum,
+ futures::TryStreamExt,
+ std::fs,
+ std::path::Path,
+ structopt::StructOpt,
+};
+
+#[derive(StructOpt, Debug)]
+struct Opt {
+ #[structopt(short = "t")]
+ test: Option<i32>,
+ #[structopt(short = "n", default_value = "root")]
+ name: String,
+}
+
+const BUS_NAME: &str = "test-bus";
+const NETWORK_NAME: &str = "test-net";
+const EP0_NAME: &str = "ep0";
+const EP1_NAME: &str = "ep1";
+const EVENT_CODE: i32 = 1;
+const TIMEOUT_SECS: i64 = 2;
+const SETUP_FILE: &str = "/vdata/test-setup";
+const SETUP_FILE_DATA: &str = "Hello World";
+
+pub struct BusConnection {
+ bus: BusProxy,
+}
+
+impl BusConnection {
+ pub fn new(client: &str) -> Result<BusConnection, Error> {
+ let busm =
+ client::connect_to_service::<BusManagerMarker>().context("BusManager not available")?;
+ let (bus, busch) = fidl::endpoints::create_proxy::<BusMarker>()?;
+ busm.subscribe(BUS_NAME, client, busch)?;
+ Ok(BusConnection { bus })
+ }
+
+ pub fn publish_code(&self, code: i32) -> Result<(), Error> {
+ self.bus.publish(&mut Event {
+ code: code,
+ message: None,
+ arguments: None,
+ })?;
+ Ok(())
+ }
+
+ pub async fn wait_for_client(&mut self, expect: &'static str) -> Result<(), Error> {
+ let clients = await!(self.bus.get_clients())?;
+ if clients.contains(&String::from(expect)) {
+ return Ok(());
+ }
+
+ let mut stream = self
+ .bus
+ .take_event_stream()
+ .try_filter_map(|event| match event {
+ fidl_fuchsia_netemul_bus::BusEvent::OnClientAttached { client } => {
+ if client == expect {
+ futures::future::ok(Some(()))
+ } else {
+ futures::future::ok(None)
+ }
+ }
+ _ => futures::future::ok(None),
+ });
+
+ await!(stream.try_next())?;
+ Ok(())
+ }
+
+ pub async fn wait_for_event(&mut self, code: i32) -> Result<(), Error> {
+ let mut stream = self
+ .bus
+ .take_event_stream()
+ .try_filter_map(|event| match event {
+ fidl_fuchsia_netemul_bus::BusEvent::OnBusData { data } => {
+ if data.code == code {
+ futures::future::ok(Some(()))
+ } else {
+ futures::future::ok(None)
+ }
+ }
+ _ => futures::future::ok(None),
+ });
+ await!(stream.try_next())?;
+ Ok(())
+ }
+}
+
+fn service_path(name: &str) -> String {
+ format!("/svc/{}", name)
+}
+
+fn device_path(name: &str) -> String {
+ format!("/vdev/{}", name)
+}
+
+fn check_path_present(path: &str) -> Result<(), Error> {
+ if Path::new(path).exists() {
+ Ok(())
+ } else {
+ Err(format_err!(
+ "Path {} not present, expected it to be there",
+ path
+ ))
+ }
+}
+
+fn check_path_absent(path: &str) -> Result<(), Error> {
+ if Path::new(path).exists() {
+ Err(format_err!(
+ "Path {} present, expected it to be absent.",
+ path
+ ))
+ } else {
+ Ok(())
+ }
+}
+
+fn check_netemul_environment() -> Result<(), Error> {
+ let () = check_path_present(&service_path("fuchsia.netemul.bus.BusManager"))?;
+ let () = check_path_present(&service_path(
+ "fuchsia.netemul.environment.ManagedEnvironment",
+ ))?;
+ let () = check_path_present(&service_path("fuchsia.netemul.network.NetworkContext"))?;
+ Ok(())
+}
+
+async fn check_network() -> Result<(), Error> {
+ let netctx = client::connect_to_service::<NetworkContextMarker>()?;
+ let (epm, epmch) = fidl::endpoints::create_proxy::<EndpointManagerMarker>()?;
+ let () = netctx.get_endpoint_manager(epmch)?;
+ let (netm, netmch) = fidl::endpoints::create_proxy::<NetworkManagerMarker>()?;
+ let () = netctx.get_network_manager(netmch)?;
+
+ let net = await!(netm.get_network(NETWORK_NAME))?;
+ if net == None {
+ return Err(format_err!("Could not retrieve network {}.", NETWORK_NAME));
+ }
+
+ let ep0 = await!(epm.get_endpoint(EP0_NAME))?;
+ if ep0 == None {
+ return Err(format_err!("Could not retrieve endpoint {}", EP0_NAME));
+ }
+
+ let ep1 = await!(epm.get_endpoint(EP1_NAME))?;
+ if ep1 == None {
+ return Err(format_err!("Could not retrieve endpoint {}", EP1_NAME));
+ }
+
+ Ok(())
+}
+
+async fn root_wait_for_children(mut bus: BusConnection) -> Result<(), Error> {
+ // wait for three hits on the bus, representing each child test
+ for i in 0..3 {
+ let () = await!(bus
+ .wait_for_event(EVENT_CODE)
+ .on_timeout(TIMEOUT_SECS.seconds().after_now(), || Err(format_err!(
+ "timed out waiting for children procs"
+ ))))?;
+ println!("Got ping from child {}", i);
+ }
+
+ Ok(())
+}
+
+async fn child_publish_on_bus(mut bus: BusConnection) -> Result<(), Error> {
+ // wait for root to show up on the bus...
+ let () = await!(bus
+ .wait_for_client("root")
+ .on_timeout(TIMEOUT_SECS.seconds().after_now(), || Err(format_err!(
+ "Timed out waiting for root test"
+ ))))?;
+ // ... then publish an event so root knows we were spawned
+ let () = bus.publish_code(EVENT_CODE)?;
+ Ok(())
+}
+
+fn run_root(opt: &Opt) -> Result<(), Error> {
+ println!("Running main test: {}", opt.name);
+ let () = check_netemul_environment()?;
+ let () = check_path_present(&service_path("fuchsia.netstack.Netstack"))?;
+ let () = check_path_present(&service_path("fuchsia.net.SocketProvider"))?;
+ let () = check_path_present(&device_path("class/ethernet/ep0"))?;
+ let () = check_path_present(&device_path("class/ethernet/ep1"))?;
+
+ let mut executor = fasync::Executor::new().context("Error creating executor")?;
+
+ // check that network was created according to spec
+ let () = executor.run_singlethreaded(check_network())?;
+
+ // check that the setup process ran:
+ let setup_data = fs::read_to_string(SETUP_FILE).context("Can't open setup file.")?;
+ if setup_data != SETUP_FILE_DATA {
+ return Err(format_err!(
+ "Setup file contents mismatch, got {}",
+ setup_data
+ ));
+ }
+
+ // wait for children on bus
+ let bus = BusConnection::new(&opt.name)?;
+ executor.run_singlethreaded(root_wait_for_children(bus))
+}
+
+// environment 1 inherits from the root environment
+fn run_test_1(opt: &Opt) -> Result<(), Error> {
+ println!("Running test 1: {}", opt.name);
+ let () = check_netemul_environment()?;
+ let () = check_path_present(&service_path("fuchsia.netstack.Netstack"))?;
+ let () = check_path_present(&service_path("fuchsia.net.SocketProvider"))?;
+ let () = check_path_absent(&device_path("class/ethernet/ep0"))?;
+ let () = check_path_absent(&device_path("class/ethernet/ep1"))?;
+
+ let mut executor = fasync::Executor::new().context("Error creating executor")?;
+ let bus = BusConnection::new(&opt.name)?;
+ executor.run_singlethreaded(child_publish_on_bus(bus))
+}
+
+// environment 2 does NOT inherit from the root environment
+fn run_test_2(opt: &Opt) -> Result<(), Error> {
+ println!("Running test 2: {}", opt.name);
+ let () = check_netemul_environment()?;
+ let () = check_path_absent(&service_path("fuchsia.netstack.Netstack"))?;
+ let () = check_path_absent(&service_path("fuchsia.net.SocketProvider"))?;
+ let () = check_path_absent(&device_path("class/ethernet/ep0"))?;
+ let () = check_path_absent(&device_path("class/ethernet/ep1"))?;
+
+ let mut executor = fasync::Executor::new().context("Error creating executor")?;
+ let bus = BusConnection::new(&opt.name)?;
+ executor.run_singlethreaded(child_publish_on_bus(bus))
+}
+
+fn run_setup_test(opt: &Opt) -> Result<(), Error> {
+ println!("Running setup test: {}", opt.name);
+ // create a file in vdata, that will be verified by root test
+ let () = fs::write(SETUP_FILE, SETUP_FILE_DATA).context("setup can't write file")?;
+ Ok(())
+}
+
+fn main() -> Result<(), Error> {
+ let opt = Opt::from_args();
+ match opt.test {
+ None => run_root(&opt),
+ Some(1) => run_test_1(&opt),
+ Some(2) => run_test_2(&opt),
+ Some(3) => run_setup_test(&opt),
+ _ => Err(format_err!("Unrecognized test option")),
+ }
+}
diff --git a/bin/netemul_runner/test/meta/env_build.cmx b/bin/netemul_runner/test/meta/env_build.cmx
new file mode 100644
index 0000000..44f0254
--- /dev/null
+++ b/bin/netemul_runner/test/meta/env_build.cmx
@@ -0,0 +1,6 @@
+{
+ "program": {
+ "data": "meta/env_build_run.cmx"
+ },
+ "runner": "fuchsia-pkg://fuchsia.com/netemul_runner#meta/netemul_runner.cmx"
+}
diff --git a/bin/netemul_runner/test/meta/env_build_run.cmx b/bin/netemul_runner/test/meta/env_build_run.cmx
new file mode 100644
index 0000000..1311737
--- /dev/null
+++ b/bin/netemul_runner/test/meta/env_build_run.cmx
@@ -0,0 +1,99 @@
+{
+ "facets": {
+ "fuchsia.netemul": {
+ "environment": {
+ "children": [
+ {
+ "name": "child-1",
+ "test": [
+ {
+ "arguments": [
+ "-t",
+ "1",
+ "-n",
+ "child-1-url"
+ ],
+ "url": "fuchsia-pkg://fuchsia.com/netemul_sandbox_test#meta/env_build_run.cmx"
+ },
+ {
+ "arguments": [
+ "-t",
+ "1",
+ "-n",
+ "child-1-no-url"
+ ]
+ }
+ ]
+ },
+ {
+ "inherit_services": false,
+ "name": "child-2",
+ "test": [
+ {
+ "arguments": [
+ "-t",
+ "2",
+ "-n",
+ "child-2"
+ ]
+ }
+ ]
+ }
+ ],
+ "devices": [
+ "ep0",
+ "ep1"
+ ],
+ "name": "root",
+ "services": {
+ "fuchsia.net.SocketProvider": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx",
+ "fuchsia.netstack.Netstack": "fuchsia-pkg://fuchsia.com/netstack#meta/netstack.cmx"
+ },
+ "setup": [
+ {
+ "arguments": [
+ "-t",
+ "3",
+ "-n",
+ "setup"
+ ]
+ }
+ ]
+ },
+ "networks": [
+ {
+ "endpoints": [
+ {
+ "mac": "70:00:01:02:03:04",
+ "mtu": 1000,
+ "name": "ep0",
+ "up": false
+ },
+ {
+ "name": "ep1"
+ }
+ ],
+ "name": "test-net"
+ }
+ ]
+ }
+ },
+ "program": {
+ "binary": "test/env_build"
+ },
+ "sandbox": {
+ "dev": [
+ "class/ethernet"
+ ],
+ "features": [
+ "persistent-storage"
+ ],
+ "services": [
+ "fuchsia.netemul.environment.ManagedEnvironment",
+ "fuchsia.netemul.network.NetworkContext",
+ "fuchsia.netemul.bus.BusManager",
+ "fuchsia.netstack.Netstack",
+ "fuchsia.net.SocketProvider"
+ ]
+ }
+}
diff --git a/bin/netemul_runner/test/netstack_socks/src/main.rs b/bin/netemul_runner/test/netstack_socks/src/main.rs
index e5e29f4..37a6646 100644
--- a/bin/netemul_runner/test/netstack_socks/src/main.rs
+++ b/bin/netemul_runner/test/netstack_socks/src/main.rs
@@ -87,10 +87,12 @@
LaunchService {
name: String::from(NetstackMarker::NAME),
url: String::from(NETSTACK_URL),
+ arguments: None,
},
LaunchService {
name: String::from("fuchsia.net.LegacySocketProvider"),
url: String::from(NETSTACK_URL),
+ arguments: None,
},
LaunchService {
name: String::from("fuchsia.net.SocketProvider"),
diff --git a/bin/netemul_runner/test/svc_list/src/main.rs b/bin/netemul_runner/test/svc_list/src/main.rs
index 6798d6e..d173f84 100644
--- a/bin/netemul_runner/test/svc_list/src/main.rs
+++ b/bin/netemul_runner/test/svc_list/src/main.rs
@@ -124,6 +124,7 @@
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 {
@@ -196,6 +197,7 @@
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
diff --git a/public/lib/netemul/fidl/environment.fidl b/public/lib/netemul/fidl/environment.fidl
index 9829493..027c276 100644
--- a/public/lib/netemul/fidl/environment.fidl
+++ b/public/lib/netemul/fidl/environment.fidl
@@ -12,6 +12,8 @@
string name;
/// Service launch url (fuchsia component url).
string url;
+ /// Service launch arguments
+ vector<string>? arguments;
};
/// A single virtual device to make available for child processes.