| // 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. |
| |
| #include <fuchsia/netstack/cpp/fidl.h> |
| #include <lib/fxl/logging.h> |
| #include <lib/fxl/strings/string_printf.h> |
| |
| #include "garnet/bin/guest/integration/enclosed_guest.h" |
| |
| static constexpr char kGuestManagerUrl[] = |
| "fuchsia-pkg://fuchsia.com/guest_manager#meta/guest_manager.cmx"; |
| static constexpr char kRealm[] = "realmguestintegrationtest"; |
| // TODO(MAC-229): Use consistent naming for the test utils here. |
| static constexpr char kFuchsiaTestUtilsUrl[] = |
| "fuchsia-pkg://fuchsia.com/guest_integration_tests_utils"; |
| static constexpr char kLinuxTestUtilDir[] = "/test_utils"; |
| static constexpr zx::duration kLoopTimeout = zx::sec(5); |
| static constexpr zx::duration kLoopConditionStep = zx::msec(10); |
| static constexpr size_t kNumRetries = 40; |
| static constexpr zx::duration kRetryStep = zx::msec(200); |
| |
| static bool RunLoopUntil(async::Loop* loop, fit::function<bool()> condition) { |
| const zx::time deadline = zx::deadline_after(kLoopTimeout); |
| while (zx::clock::get_monotonic() < deadline) { |
| if (condition()) { |
| return true; |
| } |
| loop->Run(zx::deadline_after(kLoopConditionStep)); |
| loop->ResetQuit(); |
| } |
| return condition(); |
| } |
| |
| zx_status_t EnclosedGuest::Start() { |
| real_services_->ConnectToService(real_env_.NewRequest()); |
| auto services = component::testing::EnvironmentServices::Create( |
| real_env_, loop_.dispatcher()); |
| |
| fuchsia::sys::LaunchInfo launch_info; |
| launch_info.url = kGuestManagerUrl; |
| zx_status_t status = services->AddServiceWithLaunchInfo( |
| std::move(launch_info), fuchsia::guest::EnvironmentManager::Name_); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = services->AddService(mock_netstack_.GetHandler(), |
| fuchsia::netstack::Netstack::Name_); |
| |
| enclosing_environment_ = component::testing::EnclosingEnvironment::Create( |
| kRealm, real_env_, std::move(services)); |
| bool environment_running = RunLoopUntil( |
| &loop_, [this] { return enclosing_environment_->is_running(); }); |
| if (!environment_running) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| fuchsia::guest::LaunchInfo guest_launch_info; |
| status = LaunchInfo(&guest_launch_info); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| enclosing_environment_->ConnectToService(environment_manager_.NewRequest()); |
| environment_manager_->Create(guest_launch_info.url, |
| environment_controller_.NewRequest()); |
| |
| environment_controller_->LaunchInstance(std::move(guest_launch_info), |
| instance_controller_.NewRequest(), |
| [this](uint32_t cid) { |
| guest_cid_ = cid; |
| loop_.Quit(); |
| }); |
| loop_.Run(); |
| |
| zx::socket socket; |
| instance_controller_->GetSerial( |
| [&socket](zx::socket s) { socket = std::move(s); }); |
| bool socket_valid = RunLoopUntil(&loop_, [&] { return socket.is_valid(); }); |
| if (!socket_valid) { |
| return ZX_ERR_BAD_STATE; |
| } |
| status = serial_.Start(std::move(socket)); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = WaitForSystemReady(); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| ready_ = true; |
| return ZX_OK; |
| } |
| |
| zx_status_t ZirconEnclosedGuest::LaunchInfo( |
| fuchsia::guest::LaunchInfo* launch_info) { |
| launch_info->url = kZirconGuestUrl; |
| launch_info->args.push_back("--virtio-gpu=false"); |
| launch_info->args.push_back("--cmdline-add=kernel.serial=none"); |
| return ZX_OK; |
| } |
| |
| zx_status_t ZirconEnclosedGuest::WaitForSystemReady() { |
| for (size_t i = 0; i != kNumRetries; ++i) { |
| std::string ps; |
| zx_status_t status = Execute("ps", &ps); |
| if (status != ZX_OK) { |
| continue; |
| } |
| auto appmgr = ps.find("appmgr"); |
| if (appmgr == std::string::npos) { |
| zx::nanosleep(zx::deadline_after(kRetryStep)); |
| continue; |
| } |
| return ZX_OK; |
| } |
| FXL_LOG(ERROR) << "Failed to wait for appmgr"; |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| zx_status_t ZirconEnclosedGuest::RunUtil(const std::string& util, |
| const std::string& args, |
| std::string* result) { |
| std::string cmd = |
| fxl::StringPrintf("/bin/run %s#meta/%s.cmx %s", kFuchsiaTestUtilsUrl, |
| util.c_str(), args.c_str()); |
| return Execute(cmd, result); |
| } |
| |
| zx_status_t LinuxEnclosedGuest::LaunchInfo( |
| fuchsia::guest::LaunchInfo* launch_info) { |
| launch_info->url = kLinuxGuestUrl; |
| launch_info->args.push_back("--virtio-gpu=false"); |
| launch_info->args.push_back( |
| "--cmdline=loglevel=0 console=hvc0 root=/dev/vda rw"); |
| return ZX_OK; |
| } |
| |
| zx_status_t LinuxEnclosedGuest::WaitForSystemReady() { |
| for (size_t i = 0; i != kNumRetries; ++i) { |
| std::string response; |
| zx_status_t status = Execute("echo guest ready", &response); |
| if (status != ZX_OK) { |
| continue; |
| } |
| auto ready = response.find("guest ready"); |
| if (ready == std::string::npos) { |
| zx::nanosleep(zx::deadline_after(kRetryStep)); |
| continue; |
| } |
| return ZX_OK; |
| } |
| FXL_LOG(ERROR) << "Failed to wait for shell"; |
| return ZX_ERR_TIMED_OUT; |
| } |
| |
| zx_status_t LinuxEnclosedGuest::RunUtil(const std::string& util, |
| const std::string& args, |
| std::string* result) { |
| std::string cmd = fxl::StringPrintf("%s/%s %s", kLinuxTestUtilDir, |
| util.c_str(), args.c_str()); |
| return Execute(cmd, result); |
| } |