| // Copyright 2018 The ChromiumOS Authors | |
| // 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/check.h> | |
| #include <base/functional/bind.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> | |
| #include "vm_tools/cicerone/grpc_util.h" | |
| using std::string; | |
| namespace vm_tools::cicerone { | |
| // static | |
| bool Container::wait_for_channel_ = true; | |
| void Container::DisableChannelWaitForTesting() { | |
| wait_for_channel_ = false; | |
| } | |
| Container::Container(const std::string& name, | |
| const std::string& token, | |
| base::WeakPtr<VirtualMachine> vm) | |
| : name_(name), token_(token), vm_(vm) {} | |
| void Container::set_ipv4_address(const net_base::IPv4Address& 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) { | |
| auto channel = grpc::CreateChannel(addr, grpc::InsecureChannelCredentials()); | |
| if (wait_for_channel_) { | |
| if (channel->WaitForConnected(ToGprDeadline(kConnectTimeoutSeconds))) { | |
| LOG(ERROR) << "Garcon channel not connected after " | |
| << kConnectTimeoutSeconds << " seconds."; | |
| } | |
| } | |
| garcon_stub_ = std::make_unique<vm_tools::container::Garcon::Stub>(channel); | |
| } | |
| bool Container::LaunchContainerApplication( | |
| const std::string& desktop_file_id, | |
| std::vector<std::string> files, | |
| vm_tools::container::LaunchApplicationRequest::DisplayScaling | |
| display_scaling, | |
| std::vector<vm_tools::container::ContainerFeature> container_features, | |
| 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); | |
| container_request.mutable_container_features()->Add( | |
| container_features.begin(), container_features.end()); | |
| grpc::ClientContext ctx; | |
| ctx.set_deadline(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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::vector<vm_tools::container::ContainerFeature> container_features, | |
| std::string* out_error) { | |
| vm_tools::container::LaunchVshdRequest container_request; | |
| vm_tools::container::LaunchVshdResponse container_response; | |
| container_request.set_port(port); | |
| container_request.mutable_container_features()->Add( | |
| container_features.begin(), container_features.end()); | |
| grpc::ClientContext ctx; | |
| ctx.set_deadline(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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()), | |
| .format = icon.format()}); | |
| } | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| 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; | |
| } | |
| } | |
| bool Container::GetGarconSessionInfo(std::string* out_failure_reason, | |
| std::string* out_container_username, | |
| std::string* out_container_homedir, | |
| uint32_t* out_sftp_vsock_port) { | |
| vm_tools::container::GetGarconSessionInfoRequest container_request; | |
| vm_tools::container::GetGarconSessionInfoResponse container_response; | |
| grpc::ClientContext ctx; | |
| ctx.set_deadline(ToGprDeadline(kDefaultTimeoutSeconds)); | |
| grpc::Status status = garcon_stub_->GetGarconSessionInfo( | |
| &ctx, container_request, &container_response); | |
| if (!status.ok()) { | |
| LOG(ERROR) << "Failed to get garcon session info: " | |
| << status.error_message() << " code: " << status.error_code(); | |
| out_failure_reason->assign("gRPC failure getting session info: " + | |
| status.error_message()); | |
| return false; | |
| } | |
| *out_failure_reason = container_response.failure_reason(); | |
| *out_container_username = container_response.container_username(); | |
| *out_container_homedir = container_response.container_homedir(); | |
| *out_sftp_vsock_port = container_response.sftp_vsock_port(); | |
| return container_response.status() == | |
| vm_tools::container::GetGarconSessionInfoResponse::SUCCEEDED; | |
| } | |
| 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 vm_tools::cicerone |