| // Copyright 2018 The Chromium OS 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 "vm_tools/cicerone/container.h" |
| |
| #include <arpa/inet.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include <base/bind.h> |
| #include <base/guid.h> |
| #include <base/logging.h> |
| #include <base/memory/ptr_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <google/protobuf/repeated_field.h> |
| #include <grpcpp/grpcpp.h> |
| #include <vm_protos/proto_bindings/container_guest.grpc.pb.h> |
| #include <chromeos/constants/vm_tools.h> |
| |
| using std::string; |
| |
| namespace vm_tools { |
| namespace cicerone { |
| namespace { |
| |
| // How long to wait before timing out on regular RPCs. |
| constexpr int64_t kDefaultTimeoutSeconds = 60; |
| |
| } // namespace |
| |
| Container::Container(const std::string& name, |
| const std::string& token, |
| base::WeakPtr<VirtualMachine> vm) |
| : name_(name), token_(token), vm_(vm) {} |
| |
| // Sets the container's IPv4 address. |
| void Container::set_ipv4_address(uint32_t ipv4_address) { |
| ipv4_address_ = ipv4_address; |
| } |
| |
| void Container::set_drivefs_mount_path(std::string drivefs_mount_path) { |
| drivefs_mount_path_ = drivefs_mount_path; |
| } |
| |
| void Container::set_homedir(const std::string& homedir) { |
| homedir_ = homedir; |
| } |
| |
| void Container::set_listening_tcp4_ports(std::vector<uint16_t> ports) { |
| listening_tcp4_ports_ = std::move(ports); |
| } |
| |
| void Container::ConnectToGarcon(const std::string& addr) { |
| garcon_stub_ = std::make_unique<vm_tools::container::Garcon::Stub>( |
| grpc::CreateChannel(addr, grpc::InsecureChannelCredentials())); |
| } |
| |
| bool Container::LaunchContainerApplication( |
| const std::string& desktop_file_id, |
| std::vector<std::string> files, |
| vm_tools::container::LaunchApplicationRequest::DisplayScaling |
| display_scaling, |
| std::string* out_error) { |
| CHECK(out_error); |
| vm_tools::container::LaunchApplicationRequest container_request; |
| vm_tools::container::LaunchApplicationResponse container_response; |
| container_request.set_desktop_file_id(desktop_file_id); |
| std::copy(std::make_move_iterator(files.begin()), |
| std::make_move_iterator(files.end()), |
| google::protobuf::RepeatedFieldBackInserter( |
| container_request.mutable_files())); |
| container_request.set_display_scaling(display_scaling); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->LaunchApplication(&ctx, container_request, |
| &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to launch application " << desktop_file_id |
| << " in container " << name_ << ": " << status.error_message(); |
| out_error->assign("gRPC failure launching application: " + |
| status.error_message()); |
| return false; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.success(); |
| } |
| |
| bool Container::LaunchVshd(uint32_t port, std::string* out_error) { |
| vm_tools::container::LaunchVshdRequest container_request; |
| vm_tools::container::LaunchVshdResponse container_response; |
| container_request.set_port(port); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = |
| garcon_stub_->LaunchVshd(&ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to launch vshd in container " << name_ << ": " |
| << status.error_message() << " code: " << status.error_code(); |
| out_error->assign("gRPC failure launching vshd in container: " + |
| status.error_message()); |
| return false; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.success(); |
| } |
| |
| bool Container::ConnectChunnel(uint32_t chunneld_port, |
| uint32_t tcp4_port, |
| std::string* out_error) { |
| vm_tools::container::ConnectChunnelRequest container_request; |
| vm_tools::container::ConnectChunnelResponse container_response; |
| container_request.set_chunneld_port(chunneld_port); |
| container_request.set_target_tcp4_port(tcp4_port); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->ConnectChunnel(&ctx, container_request, |
| &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to connect chunnel in container " << name_ << ": " |
| << status.error_message() << " code: " << status.error_code(); |
| out_error->assign("gRPC failure connecting chunnel in container: " + |
| status.error_message()); |
| return false; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.success(); |
| } |
| |
| bool Container::GetDebugInformation(std::string* out) { |
| vm_tools::container::GetDebugInformationRequest container_request; |
| vm_tools::container::GetDebugInformationResponse container_response; |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->GetDebugInformation( |
| &ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to get debug information in container " << name_ |
| << ": " << status.error_message() |
| << " code: " << status.error_code(); |
| out->assign("gRPC failure to get debug information in container: " + |
| status.error_message()); |
| return false; |
| } |
| out->assign(container_response.debug_information()); |
| return true; |
| } |
| |
| bool Container::GetContainerAppIcon(std::vector<std::string> desktop_file_ids, |
| uint32_t icon_size, |
| uint32_t scale, |
| std::vector<Icon>* icons) { |
| CHECK(icons); |
| |
| vm_tools::container::IconRequest container_request; |
| vm_tools::container::IconResponse container_response; |
| |
| std::copy(std::make_move_iterator(desktop_file_ids.begin()), |
| std::make_move_iterator(desktop_file_ids.end()), |
| google::protobuf::RepeatedFieldBackInserter( |
| container_request.mutable_desktop_file_ids())); |
| container_request.set_icon_size(icon_size); |
| container_request.set_scale(scale); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = |
| garcon_stub_->GetIcon(&ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to get icons in container " << name_ << ": " |
| << status.error_message(); |
| return false; |
| } |
| |
| for (auto& icon : *container_response.mutable_desktop_icons()) { |
| icons->emplace_back( |
| Icon{.desktop_file_id = std::move(*icon.mutable_desktop_file_id()), |
| .content = std::move(*icon.mutable_icon())}); |
| } |
| return true; |
| } |
| |
| bool Container::GetLinuxPackageInfo(const std::string& file_path, |
| const std::string& package_name, |
| LinuxPackageInfo* out_pkg_info, |
| std::string* out_error) { |
| CHECK(out_pkg_info); |
| vm_tools::container::LinuxPackageInfoRequest container_request; |
| vm_tools::container::LinuxPackageInfoResponse container_response; |
| container_request.set_file_path(file_path); |
| container_request.set_package_name(package_name); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->GetLinuxPackageInfo( |
| &ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to get Linux package info from container " << name_ |
| << ": " << status.error_message() |
| << " code: " << status.error_code(); |
| out_error->assign( |
| "gRPC failure getting Linux package info from container: " + |
| status.error_message()); |
| return false; |
| } |
| out_error->assign(container_response.failure_reason()); |
| out_pkg_info->package_id = std::move(container_response.package_id()); |
| out_pkg_info->license = std::move(container_response.license()); |
| out_pkg_info->description = std::move(container_response.description()); |
| out_pkg_info->project_url = std::move(container_response.project_url()); |
| out_pkg_info->size = container_response.size(); |
| out_pkg_info->summary = std::move(container_response.summary()); |
| return container_response.success(); |
| } |
| |
| vm_tools::container::InstallLinuxPackageResponse::Status |
| Container::InstallLinuxPackage(const std::string& file_path, |
| const std::string& package_id, |
| const std::string& command_uuid, |
| std::string* out_error) { |
| vm_tools::container::InstallLinuxPackageRequest container_request; |
| vm_tools::container::InstallLinuxPackageResponse container_response; |
| container_request.set_file_path(file_path); |
| container_request.set_package_id(package_id); |
| container_request.set_command_uuid(command_uuid); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->InstallLinuxPackage( |
| &ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to install Linux package in container " << name_ |
| << ": " << status.error_message() |
| << " code: " << status.error_code(); |
| out_error->assign("gRPC failure installing Linux package in container: " + |
| status.error_message()); |
| return vm_tools::container::InstallLinuxPackageResponse::FAILED; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.status(); |
| } |
| |
| vm_tools::container::UninstallPackageOwningFileResponse::Status |
| Container::UninstallPackageOwningFile(const std::string& desktop_file_id, |
| std::string* out_error) { |
| vm_tools::container::UninstallPackageOwningFileRequest container_request; |
| vm_tools::container::UninstallPackageOwningFileResponse container_response; |
| container_request.set_desktop_file_id(desktop_file_id); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->UninstallPackageOwningFile( |
| &ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to uninstall package in container " << name_ << ": " |
| << status.error_message() << " code: " << status.error_code(); |
| out_error->assign("gRPC failure uninstalling package in container: " + |
| status.error_message()); |
| return vm_tools::container::UninstallPackageOwningFileResponse::FAILED; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.status(); |
| } |
| |
| vm_tools::container::ApplyAnsiblePlaybookResponse::Status |
| Container::ApplyAnsiblePlaybook(const std::string& playbook, |
| std::string* out_error) { |
| vm_tools::container::ApplyAnsiblePlaybookRequest container_request; |
| vm_tools::container::ApplyAnsiblePlaybookResponse container_response; |
| container_request.set_playbook(playbook); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->ApplyAnsiblePlaybook( |
| &ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to apply Ansible playbook to container " << name_ |
| << ": " << status.error_message() |
| << " code: " << status.error_code(); |
| out_error->assign("gRPC failure applying Ansible playbook to container: " + |
| status.error_message()); |
| return vm_tools::container::ApplyAnsiblePlaybookResponse::FAILED; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.status(); |
| } |
| |
| vm_tools::container::ConfigureForArcSideloadResponse::Status |
| Container::ConfigureForArcSideload(std::string* out_error) { |
| vm_tools::container::ConfigureForArcSideloadRequest container_request; |
| vm_tools::container::ConfigureForArcSideloadResponse container_response; |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->ConfigureForArcSideload( |
| &ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to configure for arc sideloading: " |
| << status.error_message() << " code: " << status.error_code(); |
| out_error->assign("gRPC failure configuring container for arc sideload: " + |
| status.error_message()); |
| return vm_tools::container::ConfigureForArcSideloadResponse::FAILED; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.status(); |
| } |
| |
| bool Container::AddFileWatch(const std::string& path, std::string* out_error) { |
| vm_tools::container::AddFileWatchRequest container_request; |
| vm_tools::container::AddFileWatchResponse container_response; |
| container_request.set_path(path); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = |
| garcon_stub_->AddFileWatch(&ctx, container_request, &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to add file watch: " << status.error_message() |
| << " code: " << status.error_code(); |
| out_error->assign("gRPC failure adding file watch: " + |
| status.error_message()); |
| return false; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.status() == |
| vm_tools::container::AddFileWatchResponse::SUCCEEDED; |
| } |
| |
| bool Container::RemoveFileWatch(const std::string& path, |
| std::string* out_error) { |
| vm_tools::container::RemoveFileWatchRequest container_request; |
| vm_tools::container::RemoveFileWatchResponse container_response; |
| container_request.set_path(path); |
| |
| grpc::ClientContext ctx; |
| ctx.set_deadline(gpr_time_add( |
| gpr_now(GPR_CLOCK_MONOTONIC), |
| gpr_time_from_seconds(kDefaultTimeoutSeconds, GPR_TIMESPAN))); |
| |
| grpc::Status status = garcon_stub_->RemoveFileWatch(&ctx, container_request, |
| &container_response); |
| if (!status.ok()) { |
| LOG(ERROR) << "Failed to remove file watch: " << status.error_message() |
| << " code: " << status.error_code(); |
| out_error->assign("gRPC failure removing file watch: " + |
| status.error_message()); |
| return false; |
| } |
| out_error->assign(container_response.failure_reason()); |
| return container_response.status() == |
| vm_tools::container::RemoveFileWatchResponse::SUCCEEDED; |
| } |
| |
| void Container::RegisterVshSession(int32_t host_vsh_pid, |
| int32_t container_shell_pid) { |
| if (container_shell_pid == 0) { |
| vsh_pids_.erase(host_vsh_pid); |
| } else { |
| vsh_pids_[host_vsh_pid] = container_shell_pid; |
| } |
| } |
| |
| int32_t Container::GetVshSession(int32_t host_vsh_pid) { |
| auto it = vsh_pids_.find(host_vsh_pid); |
| return it != vsh_pids_.end() ? it->second : 0; |
| } |
| |
| } // namespace cicerone |
| } // namespace vm_tools |