blob: 735751f76100fdacc1a02b963dedb44f2404a8d3 [file] [log] [blame]
// 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/check.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