[netemul] Handling environment names

- Fix issues where sandboxes could hang if set up with duplicate
environment names
- Sandbox service creates a shell environment around each sandbox to
loosen naming restrictions.

Change-Id: I4f5b551bf636eabf2a26f8540fafec3d9a79899e
diff --git a/sdk/lib/sys/cpp/testing/enclosing_environment.cc b/sdk/lib/sys/cpp/testing/enclosing_environment.cc
index 95dfe0e..68609f1 100644
--- a/sdk/lib/sys/cpp/testing/enclosing_environment.cc
+++ b/sdk/lib/sys/cpp/testing/enclosing_environment.cc
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <lib/sys/cpp/testing/enclosing_environment.h>
-
 #include <fuchsia/debugdata/cpp/fidl.h>
 #include <fuchsia/io/cpp/fidl.h>
 #include <fuchsia/sys/cpp/fidl.h>
@@ -12,8 +10,10 @@
 #include <lib/fidl/cpp/interface_request.h>
 #include <lib/fit/function.h>
 #include <lib/sys/cpp/service_directory.h>
+#include <lib/sys/cpp/testing/enclosing_environment.h>
 #include <lib/vfs/cpp/pseudo_dir.h>
 #include <zircon/assert.h>
+
 #include <memory>
 
 namespace sys {
@@ -238,11 +238,9 @@
 }
 
 void EnclosingEnvironment::SetRunning(bool running) {
-  if (running_ != running) {
-    running_ = running;
-    if (running_changed_callback_) {
-      running_changed_callback_(running_);
-    }
+  running_ = running;
+  if (running_changed_callback_) {
+    running_changed_callback_(running_);
   }
 }
 
diff --git a/src/connectivity/network/testing/netemul/runner/managed_environment.cc b/src/connectivity/network/testing/netemul/runner/managed_environment.cc
index 088fe7e..e9e4e75 100644
--- a/src/connectivity/network/testing/netemul/runner/managed_environment.cc
+++ b/src/connectivity/network/testing/netemul/runner/managed_environment.cc
@@ -30,7 +30,8 @@
 }
 
 ManagedEnvironment::ManagedEnvironment(const SandboxEnv::Ptr& sandbox_env)
-    : sandbox_env_(sandbox_env) {}
+    : sandbox_env_(sandbox_env), ready_(false) {}
+
 sys::testing::EnclosingEnvironment& ManagedEnvironment::environment() {
   return *env_;
 }
@@ -154,10 +155,23 @@
                                       std::move(services), sub_options);
 
   env_->SetRunningChangedCallback([this](bool running) {
-    if (running && running_callback_) {
-      running_callback_();
-    } else if (!running) {
+    ready_ = true;
+    if (running) {
+      for (auto& r : pending_requests_) {
+        Bind(std::move(r));
+      }
+      pending_requests_.clear();
+      if (running_callback_) {
+        running_callback_();
+      }
+    } else {
       FXL_LOG(ERROR) << "Underlying enclosed Environment stopped running";
+      running_callback_ = nullptr;
+      children_.clear();
+      pending_requests_.clear();
+      env_ = nullptr;
+      launcher_ = nullptr;
+      bindings_.CloseAll();
     }
   });
 
@@ -181,7 +195,13 @@
 
 void ManagedEnvironment::Bind(
     fidl::InterfaceRequest<ManagedEnvironment::FManagedEnvironment> req) {
-  bindings_.AddBinding(this, std::move(req));
+  if (ready_) {
+    bindings_.AddBinding(this, std::move(req));
+  } else if (env_) {
+    pending_requests_.push_back(std::move(req));
+  } else {
+    req.Close(ZX_ERR_INTERNAL);
+  }
 }
 
 ManagedLoggerCollection& ManagedEnvironment::loggers() {
diff --git a/src/connectivity/network/testing/netemul/runner/managed_environment.h b/src/connectivity/network/testing/netemul/runner/managed_environment.h
index 0ad926a..7afe438 100644
--- a/src/connectivity/network/testing/netemul/runner/managed_environment.h
+++ b/src/connectivity/network/testing/netemul/runner/managed_environment.h
@@ -76,6 +76,8 @@
   std::vector<LaunchService> service_config_;
   VirtualDevices virtual_devices_;
   std::unique_ptr<VirtualData> virtual_data_;
+  std::vector<fidl::InterfaceRequest<FManagedEnvironment>> pending_requests_;
+  bool ready_;
 
   FXL_DISALLOW_COPY_AND_ASSIGN(ManagedEnvironment);
 };
diff --git a/src/connectivity/network/testing/netemul/runner/sandbox.cc b/src/connectivity/network/testing/netemul/runner/sandbox.cc
index 5fd998f..5bdbcfa0 100644
--- a/src/connectivity/network/testing/netemul/runner/sandbox.cc
+++ b/src/connectivity/network/testing/netemul/runner/sandbox.cc
@@ -474,19 +474,25 @@
   linfo.arguments->insert(linfo.arguments->end(), arguments.begin(),
                           arguments.end());
 
-  auto& proc = procs_.emplace_back();
   auto ticket = procs_.size();
+  auto& proc = procs_.emplace_back();
 
   if (is_test) {
     RegisterTest(ticket);
   }
 
+  proc.set_error_handler([this](zx_status_t status) {
+    PostTerminate(TerminationReason::INTERNAL_ERROR);
+  });
+
   // 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: "
                   << sys::HumanReadableTerminationReason(reason);
+    // remove the error handler:
+    procs_[ticket].set_error_handler(nullptr);
     if (is_test) {
       if (code != 0 || reason != TerminationReason::EXITED) {
         // test failed, early bail
@@ -519,6 +525,7 @@
   linfo.arguments->insert(linfo.arguments->end(), arguments.begin(),
                           arguments.end());
 
+  auto ticket = procs_.size();
   auto& proc = procs_.emplace_back();
 
   if ((*launcher)->CreateComponent(std::move(linfo), proc.NewRequest()) !=
@@ -526,13 +533,19 @@
     FXL_LOG(ERROR) << "couldn't launch setup: " << url;
     bridge.completer.complete_error();
   } else {
+    proc.set_error_handler([this](zx_status_t status) {
+      PostTerminate(TerminationReason::INTERNAL_ERROR);
+    });
+
     // we observe test processes return code
     proc.events().OnTerminated =
-        [url, this, completer = std::move(bridge.completer)](
+        [url, this, ticket, completer = std::move(bridge.completer)](
             int64_t code, TerminationReason reason) mutable {
           FXL_LOG(INFO) << "Setup " << url << " terminated with (" << code
                         << ") reason: "
                         << sys::HumanReadableTerminationReason(reason);
+          // remove the error handler:
+          procs_[ticket].set_error_handler(nullptr);
           if (code == 0 && reason == TerminationReason::EXITED) {
             completer.complete_ok();
           } else {
diff --git a/src/connectivity/network/testing/netemul/runner/sandbox_service.cc b/src/connectivity/network/testing/netemul/runner/sandbox_service.cc
index 1a52780..38b6442 100644
--- a/src/connectivity/network/testing/netemul/runner/sandbox_service.cc
+++ b/src/connectivity/network/testing/netemul/runner/sandbox_service.cc
@@ -6,6 +6,9 @@
 
 #include <lib/async/task.h>
 #include <lib/sys/cpp/service_directory.h>
+#include <src/lib/fxl/strings/string_printf.h>
+
+#include <random>
 
 namespace netemul {
 
@@ -15,21 +18,36 @@
   using OnDestroyedCallback = fit::function<void(const SandboxBinding*)>;
 
   SandboxBinding(fidl::InterfaceRequest<FSandbox> req,
+                 fuchsia::sys::EnvironmentControllerPtr env_controller,
+                 fuchsia::sys::EnvironmentPtr environment,
                  std::unique_ptr<async::Loop> loop, SandboxService* parent)
       : loop_(std::move(loop)),
-        binding_(this, std::move(req), loop_->dispatcher()),
+        binding_(this),
+        parent_env_(std::move(environment)),
+        parent_env_ctlr_(std::move(env_controller)),
         parent_(parent) {
     binding_.set_error_handler([this](zx_status_t err) {
       environments_.clear();
       parent_->BindingClosed(this);
     });
 
-    auto services = sys::ServiceDirectory::CreateFromNamespace();
-    services->Connect(parent_env_.NewRequest(loop_->dispatcher()));
+    parent_env_ctlr_.set_error_handler([this](zx_status_t err) {
+      FXL_LOG(ERROR) << "Lost connection to parent environment";
+      environments_.clear();
+      parent_->BindingClosed(this);
+    });
+
     parent_env_.set_error_handler([this](zx_status_t err) {
       FXL_LOG(ERROR) << "Lost connection to parent environment";
+      environments_.clear();
+      parent_->BindingClosed(this);
     });
 
+    parent_env_ctlr_.events().OnCreated = [this,
+                                           req = std::move(req)]() mutable {
+      binding_.Bind(std::move(req), loop_->dispatcher());
+    };
+
     if (loop_->StartThread("sandbox-thread") != ZX_OK) {
       FXL_LOG(ERROR) << "Failed to start thread for sandbox";
       parent_->BindingClosed(this);
@@ -84,6 +102,7 @@
   fidl::Binding<FSandbox> binding_;
   std::vector<std::unique_ptr<ManagedEnvironment>> environments_;
   fuchsia::sys::EnvironmentPtr parent_env_;
+  fuchsia::sys::EnvironmentControllerPtr parent_env_ctlr_;
   // Pointer to parent SandboxService. Not owned.
   SandboxService* parent_;
 
@@ -103,23 +122,44 @@
 
 fidl::InterfaceRequestHandler<fuchsia::netemul::sandbox::Sandbox>
 SandboxService::GetHandler() {
-  return
-      [this](fidl::InterfaceRequest<fuchsia::netemul::sandbox::Sandbox> req) {
-        // Create each SandboxBinding in its own thread.
-        // A common usage pattern for SandboxService is to connect to the
-        // service in each test in a rust create test suite. Rust crate tests
-        // run in parallel, so enclosing each binding in its own thread will
-        // makes a bit more sense to service everything independently.
-        auto loop =
-            std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread);
+  return [this](
+             fidl::InterfaceRequest<fuchsia::netemul::sandbox::Sandbox> req) {
+    // Create each SandboxBinding in its own thread.
+    // A common usage pattern for SandboxService is to connect to the
+    // service in each test in a rust create test suite. Rust crate tests
+    // run in parallel, so enclosing each binding in its own thread will
+    // makes a bit more sense to service everything independently.
+    auto loop =
+        std::make_unique<async::Loop>(&kAsyncLoopConfigNoAttachToThread);
 
-        bindings_.push_back(std::make_unique<SandboxBinding>(
-            std::move(req), std::move(loop), this));
-      };
+    fuchsia::sys::EnvironmentPtr env;
+    fuchsia::sys::EnvironmentControllerPtr env_ctlr;
+    parent_env_->CreateNestedEnvironment(
+        env.NewRequest(loop->dispatcher()),
+        env_ctlr.NewRequest(loop->dispatcher()),
+        fxl::StringPrintf("netemul-%08X-%08X", random_, counter_++), nullptr,
+        fuchsia::sys::EnvironmentOptions{
+            .delete_storage_on_death = false,
+            .kill_on_oom = true,
+            .inherit_parent_services = true,
+            .allow_parent_runners = true,
+        });
+
+    bindings_.push_back(std::make_unique<SandboxBinding>(
+        std::move(req), std::move(env_ctlr), std::move(env), std::move(loop),
+        this));
+  };
 }
 
 SandboxService::SandboxService(async_dispatcher_t* dispatcher)
-    : dispatcher_(dispatcher) {}
+    : dispatcher_(dispatcher), counter_(0) {
+  std::random_device dev;
+  std::uniform_int_distribution<uint32_t> rd(0, 0xFFFFFFFF);
+  random_ = rd(dev);
+
+  auto services = sys::ServiceDirectory::CreateFromNamespace();
+  services->Connect(parent_env_.NewRequest(dispatcher));
+}
 
 SandboxService::~SandboxService() = default;
 
diff --git a/src/connectivity/network/testing/netemul/runner/sandbox_service.h b/src/connectivity/network/testing/netemul/runner/sandbox_service.h
index 7cf9a1b..23f06e3 100644
--- a/src/connectivity/network/testing/netemul/runner/sandbox_service.h
+++ b/src/connectivity/network/testing/netemul/runner/sandbox_service.h
@@ -6,6 +6,7 @@
 #define SRC_CONNECTIVITY_NETWORK_TESTING_NETEMUL_RUNNER_SANDBOX_SERVICE_H_
 
 #include <fuchsia/netemul/sandbox/cpp/fidl.h>
+#include <fuchsia/sys/cpp/fidl.h>
 
 #include "sandbox.h"
 
@@ -26,7 +27,10 @@
 
  private:
   async_dispatcher_t* dispatcher_;
+  fuchsia::sys::EnvironmentPtr parent_env_;
   std::vector<std::unique_ptr<SandboxBinding>> bindings_;
+  uint32_t counter_;
+  uint32_t random_;
 
   FXL_DISALLOW_COPY_AND_ASSIGN(SandboxService);
 };
diff --git a/src/connectivity/network/testing/netemul/runner/sandbox_unittest.cc b/src/connectivity/network/testing/netemul/runner/sandbox_unittest.cc
index 1a19d67..f584786 100644
--- a/src/connectivity/network/testing/netemul/runner/sandbox_unittest.cc
+++ b/src/connectivity/network/testing/netemul/runner/sandbox_unittest.cc
@@ -753,5 +753,24 @@
   sandbox = nullptr;
 }
 
+TEST_F(SandboxTest, EnvironmentsWithSameNameFail) {
+  SetCmx(R"(
+{
+   "environment" : {
+      "children" : [
+       {
+          "name" : "my-env",
+          "test" : [ "fuchsia-pkg://fuchsia.com/netemul_sandbox_test#meta/dummy_proc.cmx" ]
+       },
+       {
+          "name" : "my-env",
+          "test" : [ "fuchsia-pkg://fuchsia.com/netemul_sandbox_test#meta/dummy_proc.cmx" ]
+       }
+      ]
+   }
+})");
+  RunSandboxInternalError();
+}
+
 }  // namespace testing
 }  // namespace netemul
diff --git a/src/connectivity/network/testing/netemul/runner/test/sandbox_service/src/main.rs b/src/connectivity/network/testing/netemul/runner/test/sandbox_service/src/main.rs
index 7f9759d..e8460a5 100644
--- a/src/connectivity/network/testing/netemul/runner/test/sandbox_service/src/main.rs
+++ b/src/connectivity/network/testing/netemul/runner/test/sandbox_service/src/main.rs
@@ -30,7 +30,10 @@
         fuchsia_zircon as zx,
     };
 
-    fn create_env_with_netstack(sandbox: &SandboxProxy) -> Result<ManagedEnvironmentProxy, Error> {
+    fn create_named_env_with_netstack(
+        sandbox: &SandboxProxy,
+        name: Option<String>,
+    ) -> Result<ManagedEnvironmentProxy, Error> {
         let (env, env_server_end) = fidl::endpoints::create_proxy::<ManagedEnvironmentMarker>()?;
         let services = vec![LaunchService {
             name: String::from("fuchsia.netstack.Netstack"),
@@ -40,7 +43,7 @@
         sandbox.create_environment(
             env_server_end,
             EnvironmentOptions {
-                name: None, // don't care about the name, let it be created by itself
+                name: name, // don't care about the name, let it be created by itself
                 services: Some(services),
                 devices: None,
                 inherit_parent_launch_services: Some(false),
@@ -56,6 +59,10 @@
         Ok(env)
     }
 
+    fn create_env_with_netstack(sandbox: &SandboxProxy) -> Result<ManagedEnvironmentProxy, Error> {
+        create_named_env_with_netstack(sandbox, None)
+    }
+
     async fn create_network<'a>(
         env: &'a ManagedEnvironmentProxy,
         name: &'a str,
@@ -212,4 +219,42 @@
         assert!(clients_1.iter().any(|c| c == "e2"));
         assert!(clients_2.iter().any(|c| c == "e3"));
     }
+
+    #[fasync::run_singlethreaded]
+    #[test]
+    async fn same_environment_name_fails() {
+        let sandbox =
+            client::connect_to_service::<SandboxMarker>().expect("Can't connect to sandbox");
+        let env_name = Some("env_a".to_string());
+
+        // environment creation should work for both, but doing anything on
+        // env2 should fail:
+        let env1 =
+            create_named_env_with_netstack(&sandbox, env_name.clone()).expect("can't create env 1");
+        let env2 = create_named_env_with_netstack(&sandbox, env_name).expect("can't create env 2");
+
+        let _net1 =
+            await!(create_network(&env1, "network")).expect("failed to create network on env 1");
+        let _net2 = await!(create_network(&env2, "network2"))
+            .expect_err("should've failed to create network on env 2");
+    }
+
+    #[fasync::run_singlethreaded]
+    #[test]
+    async fn same_environment_name_succeeds_in_different_sandboxes() {
+        let sandbox =
+            client::connect_to_service::<SandboxMarker>().expect("Can't connect to sandbox");
+        let sandbox2 =
+            client::connect_to_service::<SandboxMarker>().expect("Can't connect to sandbox 2");
+        let env_name = Some("env_a".to_string());
+
+        let env1 =
+            create_named_env_with_netstack(&sandbox, env_name.clone()).expect("can't create env 1");
+        let env2 = create_named_env_with_netstack(&sandbox2, env_name).expect("can't create env 2");
+
+        let _net1 =
+            await!(create_network(&env1, "network")).expect("failed to create network on env 1");
+        let _net2 =
+            await!(create_network(&env2, "network2")).expect("failed to create network on env 2");
+    }
 }