blob: 7089794ff52f3df73d6e2c288fc2302207d5d8f0 [file] [log] [blame]
// Copyright 2019 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_number_conversions.h>
#include <dbus/message.h>
#include <gmock/gmock.h>
#include <grpcpp/impl/call.h>
#include <grpcpp/grpcpp.h>
#include <gtest/gtest.h>
#include <chromeos/dbus/service_constants.h>
#include <vm_applications/apps.pb.h>
#include <vm_protos/proto_bindings/container_host.grpc.pb.h>
#include <vm_protos/proto_bindings/container_host.pb.h>
#include "vm_tools/cicerone/container_listener_impl.h"
#include "vm_tools/cicerone/dbus_message_testing_helper.h"
#include "vm_tools/cicerone/service.h"
#include "vm_tools/cicerone/service_testing_helper.h"
namespace vm_tools {
namespace cicerone {
namespace {
using ::testing::_;
using ::testing::AllOf;
using ::testing::Invoke;
using ::testing::Matches;
using ::testing::UnorderedElementsAreArray;
using ::testing::Unused;
constexpr char kDefaultPluginVmContainerName[] = "penguin";
class SysinfoProviderMock : public GuestMetrics::SysinfoProvider {
public:
MOCK_METHOD(int64_t, AmountOfTotalDiskSpace, (base::FilePath), ());
MOCK_METHOD(int64_t, AmountOfFreeDiskSpace, (base::FilePath), ());
};
// Helper for testing proto-based MethodCalls sent to the dbus. Extracts the
// proto in from the MethodCall to |protobuf| so that it can be tested further.
// Returns an empty (but not NULL) dbus::Response.
//
// Does not confirm method name (a.k.a. GetMethod) or interface name; use
// HasMethodName and HasInterfaceName for that.
base::expected<std::unique_ptr<dbus::Response>, dbus::Error>
ProtoMethodCallHelper(dbus::MethodCall* method_call,
google::protobuf::MessageLite* protobuf) {
dbus::MessageReader reader(method_call);
EXPECT_TRUE(reader.PopArrayOfBytesAsProto(protobuf));
EXPECT_FALSE(reader.HasMoreData());
// MockObjectProxy will take ownership of the created Response object. See
// comments in MockObjectProxy.
return base::ok(dbus::Response::CreateEmpty());
}
// Same, but for method calls that just have a string.
base::expected<std::unique_ptr<dbus::Response>, dbus::Error>
StringMethodCallHelper(dbus::MethodCall* method_call, std::string* s) {
dbus::MessageReader reader(method_call);
EXPECT_TRUE(reader.PopString(s));
EXPECT_FALSE(reader.HasMoreData());
return base::ok(dbus::Response::CreateEmpty());
}
// Same but for signals
void ProtoSignalHelper(dbus::Signal* signal,
google::protobuf::MessageLite* protobuf) {
dbus::MessageReader reader(signal);
EXPECT_TRUE(reader.PopArrayOfBytesAsProto(protobuf));
EXPECT_FALSE(reader.HasMoreData());
}
TEST(ContainerListenerImplTest,
ValidContainerShutdownCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::ContainerShutdownInfo request;
vm_tools::EmptyMessage response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
ContainerShutdownSignal shutdown_result;
EXPECT_CALL(test_framework.get_mock_exported_object(),
SendSignal(AllOf(HasInterfaceName(kVmCiceroneInterface),
HasMethodName(kContainerShutdownSignal))))
.WillOnce(Invoke([&shutdown_result](dbus::Signal* signal) {
ProtoSignalHelper(signal, &shutdown_result);
}));
std::string unregister_hostname;
EXPECT_CALL(test_framework.get_mock_crosdns_service_proxy(),
CallMethodAndBlock(
AllOf(HasInterfaceName(crosdns::kCrosDnsInterfaceName),
HasMethodName(crosdns::kRemoveHostnameIpMappingMethod)),
_))
.WillOnce(
Invoke([&unregister_hostname](dbus::MethodCall* method_call, Unused) {
return StringMethodCallHelper(method_call, &unregister_hostname);
}));
grpc::ServerContext ctx;
grpc::Status status = test_framework.get_service()
.GetContainerListenerImpl()
->ContainerShutdown(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(shutdown_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(shutdown_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(shutdown_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
EXPECT_EQ(unregister_hostname,
ServiceTestingHelper::kDefaultContainerHostname);
}
void ValidUpdateApplicationListCallShouldProduceDBusMessageGeneric(
bool plugin_vm, const char* container_name) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
if (plugin_vm) {
test_framework.SetUpPluginVm();
} else {
test_framework.SetUpDefaultVmAndContainer();
}
test_framework.ExpectNoDBusMessages();
vm_tools::container::UpdateApplicationListRequest request;
vm_tools::EmptyMessage response;
const std::string kDesktopFileId = "nethack-x11";
const std::string kName = "Nethack";
const std::string kComment = "Get the amulet!";
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
vm_tools::container::Application* application = request.add_application();
application->set_desktop_file_id(kDesktopFileId);
application->mutable_name()->add_values()->set_value(kName);
application->mutable_comment()->add_values()->set_value(kComment);
application->set_no_display(false);
vm_tools::apps::ApplicationList dbus_result;
EXPECT_CALL(
test_framework.get_mock_vm_applications_service_proxy(),
CallMethodAndBlock(
AllOf(
HasInterfaceName(vm_tools::apps::kVmApplicationsServiceInterface),
HasMethodName(
vm_tools::apps::
kVmApplicationsServiceUpdateApplicationListMethod)),
_))
.WillOnce(Invoke([&dbus_result](dbus::MethodCall* method_call, Unused) {
return ProtoMethodCallHelper(method_call, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status = test_framework.get_service()
.GetContainerListenerImpl()
->UpdateApplicationList(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(), container_name);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
ASSERT_EQ(dbus_result.apps_size(), 1);
const vm_tools::apps::App& dbus_app = dbus_result.apps(0);
EXPECT_EQ(dbus_app.desktop_file_id(), kDesktopFileId);
const vm_tools::apps::App::LocaleString& dbus_name = dbus_app.name();
ASSERT_EQ(dbus_name.values_size(), 1);
EXPECT_EQ(dbus_name.values(0).value(), kName);
EXPECT_EQ(dbus_name.values(0).locale(), "");
const vm_tools::apps::App::LocaleString& dbus_comment = dbus_app.comment();
ASSERT_EQ(dbus_comment.values_size(), 1);
EXPECT_EQ(dbus_comment.values(0).value(), kComment);
EXPECT_EQ(dbus_comment.values(0).locale(), "");
EXPECT_FALSE(dbus_app.no_display());
EXPECT_EQ(dbus_app.mime_types_size(), 0);
EXPECT_EQ(dbus_app.startup_wm_class(), "");
EXPECT_FALSE(dbus_app.startup_notify());
EXPECT_EQ(dbus_app.keywords().values_size(), 0);
EXPECT_EQ(dbus_app.package_id(), "");
EXPECT_EQ(dbus_app.extensions_size(), 0);
}
TEST(ContainerListenerImplTest,
ValidUpdateApplicationListCallShouldProduceDBusMessageForDefaultVm) {
ValidUpdateApplicationListCallShouldProduceDBusMessageGeneric(
false /* plugin_vm */, ServiceTestingHelper::kDefaultContainerName);
}
TEST(ContainerListenerImplTest,
ValidUpdateApplicationListCallShouldProduceDBusMessageForPluginVm) {
ValidUpdateApplicationListCallShouldProduceDBusMessageGeneric(
true /* plugin_vm */, kDefaultPluginVmContainerName);
}
vm_tools::container::Application::LocalizedString MakeLocalizedString(
const std::string& field_name, int seed) {
vm_tools::container::Application::LocalizedString result;
for (int value = 0; value < seed; ++value) {
vm_tools::container::Application::LocalizedString::StringWithLocale*
locale = result.add_values();
if ((value % 5) != 0) {
locale->set_value(field_name +
":value:" + base::NumberToString(seed * 1000 + value));
}
if ((value % 4) != 0) {
locale->set_locale(
field_name + ":locale:" + base::NumberToString(seed * 1000 + value));
}
}
return result;
}
vm_tools::container::Application::LocaleStrings MakeLocaleStrings(
const std::string& field_name, int seed) {
vm_tools::container::Application::LocaleStrings result;
for (int value = 0; value < seed; ++value) {
vm_tools::container::Application::LocaleStrings::StringsWithLocale* locale =
result.add_values();
for (int sub_value = 0; sub_value < value; ++sub_value) {
locale->add_value(
field_name + ":value:" +
base::NumberToString(seed * 1000 + value * 10 + sub_value));
}
if ((value % 4) != 0) {
locale->set_locale(
field_name + ":locale:" + base::NumberToString(seed * 1000 + value));
}
}
return result;
}
// Make an UpdateApplicationListRequest with every field filled in and multiple
// entries in every repeated field.
vm_tools::container::UpdateApplicationListRequest
MakeComplexUpdateApplicationListRequest() {
vm_tools::container::UpdateApplicationListRequest request;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
for (int app_num = 0; app_num < 10; ++app_num) {
vm_tools::container::Application* application = request.add_application();
// Non-repeated fields:
application->set_desktop_file_id("desktop:" +
base::NumberToString(app_num));
application->set_no_display((app_num % 2) == 0);
application->set_startup_wm_class("startup_wm_class:" +
base::NumberToString(app_num));
application->set_startup_notify((app_num % 3) == 0);
application->set_package_id("package_id:" + base::NumberToString(app_num));
// Repeated fields:
*application->mutable_name() = MakeLocalizedString("name", app_num);
*application->mutable_comment() =
MakeLocalizedString("comment", 10 - app_num);
for (int mime_type = 0; mime_type < 5; ++mime_type) {
application->add_mime_types(
"mime:" + base::NumberToString(app_num * 1000 + mime_type));
}
*application->mutable_keywords() = MakeLocaleStrings("keyword", app_num);
for (int extension = 0; extension < 5; ++extension) {
application->add_extensions(
"extension:" + base::NumberToString(app_num * 1000 + extension));
}
}
return request;
}
void CompareLocaleString(
const vm_tools::container::Application::LocalizedString& expected,
const vm_tools::apps::App::LocaleString& actual) {
// Order is not important, only existence.
EXPECT_EQ(expected.values_size(), actual.values_size());
std::set<int> expected_indexes_seen;
for (const vm_tools::apps::App::LocaleString::Entry& actual_entry :
actual.values()) {
bool found = false;
int expected_index = 0;
while (!found && expected_index < expected.values_size()) {
if (expected.values(expected_index).locale() == actual_entry.locale() &&
expected.values(expected_index).value() == actual_entry.value() &&
expected_indexes_seen.find(expected_index) ==
expected_indexes_seen.end()) {
found = true;
} else {
++expected_index;
}
}
EXPECT_TRUE(found) << "Could not find expected.values() with locale "
<< actual_entry.locale() << " and value "
<< actual_entry.value();
expected_indexes_seen.insert(expected_index);
}
EXPECT_EQ(expected_indexes_seen.size(), actual.values_size());
}
void CompareLocaleStrings(
const vm_tools::container::Application::LocaleStrings& expected,
const vm_tools::apps::App::LocaleStrings& actual) {
// Order is not important, only existence.
EXPECT_EQ(expected.values_size(), actual.values_size());
std::set<int> expected_indexes_seen;
for (const vm_tools::apps::App::LocaleStrings::StringsWithLocale&
actual_entry : actual.values()) {
bool found = false;
int expected_index = 0;
while (!found && expected_index < expected.values_size()) {
if (expected.values(expected_index).locale() == actual_entry.locale() &&
Matches(UnorderedElementsAreArray(
expected.values(expected_index).value()))(actual_entry.value()) &&
expected_indexes_seen.find(expected_index) ==
expected_indexes_seen.end()) {
found = true;
} else {
++expected_index;
}
}
EXPECT_TRUE(found) << "Could not find expected.values() with locale "
<< actual_entry.locale() << " and "
<< actual_entry.value_size() << " values";
expected_indexes_seen.insert(expected_index);
}
EXPECT_EQ(expected_indexes_seen.size(), actual.values_size());
}
void CompareInputApplicationToOutputApp(
const vm_tools::container::Application& expected,
const vm_tools::apps::App& actual) {
// Compare non-repeated fields.
EXPECT_EQ(expected.desktop_file_id(), actual.desktop_file_id());
EXPECT_EQ(expected.no_display(), actual.no_display());
EXPECT_EQ(expected.startup_wm_class(), actual.startup_wm_class());
EXPECT_EQ(expected.startup_notify(), actual.startup_notify());
EXPECT_EQ(expected.package_id(), actual.package_id());
EXPECT_THAT(actual.mime_types(),
UnorderedElementsAreArray(expected.mime_types()));
CompareLocaleString(expected.name(), actual.name());
CompareLocaleString(expected.comment(), actual.comment());
CompareLocaleStrings(expected.keywords(), actual.keywords());
EXPECT_THAT(actual.extensions(),
UnorderedElementsAreArray(expected.extensions()));
}
// Ensures that all the entries in expected match the entries in actual. Allows
// different order of the applications and sub-fields. EXPECT fails if
// differences are found.
void CompareUpdateApplicationListRequestToApplicationList(
const vm_tools::container::UpdateApplicationListRequest& expected,
const vm_tools::apps::ApplicationList& actual) {
EXPECT_EQ(actual.apps_size(), expected.application_size());
std::set<int> expected_indexes_seen;
for (const vm_tools::apps::App& actual_app : actual.apps()) {
// Don't assume input & output order are the same, because that's not part
// of the API contract. Assume desktop_file_id is unique, though, since
// MakeComplexUpdateApplicationListRequest will do that.
bool found = false;
int expected_index = 0;
while (!found && expected_index < expected.application_size()) {
if (expected.application(expected_index).desktop_file_id() ==
actual_app.desktop_file_id()) {
found = true;
} else {
++expected_index;
}
}
ASSERT_TRUE(found)
<< "Could not find expected.application() with desktop_file_id "
<< actual_app.desktop_file_id();
EXPECT_TRUE(expected_indexes_seen.find(expected_index) ==
expected_indexes_seen.end())
<< "desktop_file_id " << actual_app.desktop_file_id()
<< " appears twice in actual";
expected_indexes_seen.insert(expected_index);
CompareInputApplicationToOutputApp(expected.application(expected_index),
actual_app);
}
EXPECT_EQ(expected_indexes_seen.size(), actual.apps_size());
}
void LongerUpdateApplicationListCallShouldProduceDBusMessageGeneric(
bool plugin_vm, const char* container_name) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
if (plugin_vm) {
test_framework.SetUpPluginVm();
} else {
test_framework.SetUpDefaultVmAndContainer();
}
test_framework.ExpectNoDBusMessages();
vm_tools::container::UpdateApplicationListRequest request(
MakeComplexUpdateApplicationListRequest());
vm_tools::EmptyMessage response;
vm_tools::apps::ApplicationList dbus_result;
EXPECT_CALL(
test_framework.get_mock_vm_applications_service_proxy(),
CallMethodAndBlock(
AllOf(
HasInterfaceName(vm_tools::apps::kVmApplicationsServiceInterface),
HasMethodName(
vm_tools::apps::
kVmApplicationsServiceUpdateApplicationListMethod)),
_))
.WillOnce(Invoke([&dbus_result](dbus::MethodCall* method_call, Unused) {
return ProtoMethodCallHelper(method_call, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status = test_framework.get_service()
.GetContainerListenerImpl()
->UpdateApplicationList(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(), container_name);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
CompareUpdateApplicationListRequestToApplicationList(request, dbus_result);
}
TEST(ContainerListenerImplTest,
LongerUpdateApplicationListCallShouldProduceDBusMessageForDefaultVm) {
LongerUpdateApplicationListCallShouldProduceDBusMessageGeneric(
false /* plugin_vm */, ServiceTestingHelper::kDefaultContainerName);
}
TEST(ContainerListenerImplTest,
LongerUpdateApplicationListCallShouldProduceDBusMessageForPluginVm) {
LongerUpdateApplicationListCallShouldProduceDBusMessageGeneric(
true /* plugin_vm */, kDefaultPluginVmContainerName);
}
void ValidOpenUrlCallShouldProduceDBusMessageGeneric(bool plugin_vm) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
if (plugin_vm) {
test_framework.SetUpPluginVm();
} else {
test_framework.SetUpDefaultVmAndContainer();
}
test_framework.ExpectNoDBusMessages();
vm_tools::container::OpenUrlRequest request;
vm_tools::EmptyMessage response;
const std::string kUrl = "whycrostiniisgreat.com";
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
request.set_url(kUrl);
std::string resulting_url;
EXPECT_CALL(
test_framework.get_mock_url_handler_service_proxy(),
CallMethodAndBlock(
AllOf(HasInterfaceName(chromeos::kUrlHandlerServiceInterface),
HasMethodName(chromeos::kUrlHandlerServiceOpenUrlMethod)),
_))
.WillOnce(Invoke([&resulting_url](dbus::MethodCall* method_call, Unused) {
return StringMethodCallHelper(method_call, &resulting_url);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->OpenUrl(
&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(resulting_url, kUrl);
}
TEST(ContainerListenerImplTest,
ValidOpenUrlCallShouldProduceDBusMessageForDefaultVm) {
ValidOpenUrlCallShouldProduceDBusMessageGeneric(false);
}
TEST(ContainerListenerImplTest,
ValidOpenUrlCallShouldProduceDBusMessageForPluginVm) {
ValidOpenUrlCallShouldProduceDBusMessageGeneric(true);
}
TEST(ContainerListenerImplTest,
ValidInstallLinuxPackageProgressCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::InstallLinuxPackageProgressInfo request;
vm_tools::EmptyMessage response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
request.set_status(
vm_tools::container::InstallLinuxPackageProgressInfo::DOWNLOADING);
request.set_progress_percent(52);
InstallLinuxPackageProgressSignal dbus_result;
EXPECT_CALL(
test_framework.get_mock_exported_object(),
SendSignal(AllOf(HasInterfaceName(kVmCiceroneInterface),
HasMethodName(kInstallLinuxPackageProgressSignal))))
.WillOnce(Invoke([&dbus_result](dbus::Signal* signal) {
ProtoSignalHelper(signal, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service()
.GetContainerListenerImpl()
->InstallLinuxPackageProgress(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
EXPECT_EQ(dbus_result.status(),
InstallLinuxPackageProgressSignal::DOWNLOADING);
EXPECT_EQ(dbus_result.progress_percent(), 52);
EXPECT_EQ(dbus_result.failure_details(), "");
}
TEST(ContainerListenerImplTest,
FailureInstallLinuxPackageProgressCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::InstallLinuxPackageProgressInfo request;
vm_tools::EmptyMessage response;
const std::string kFailureDetails = "I prefer not to.";
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
request.set_status(
vm_tools::container::InstallLinuxPackageProgressInfo::FAILED);
request.set_failure_details(kFailureDetails);
InstallLinuxPackageProgressSignal dbus_result;
EXPECT_CALL(
test_framework.get_mock_exported_object(),
SendSignal(AllOf(HasInterfaceName(kVmCiceroneInterface),
HasMethodName(kInstallLinuxPackageProgressSignal))))
.WillOnce(Invoke([&dbus_result](dbus::Signal* signal) {
ProtoSignalHelper(signal, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service()
.GetContainerListenerImpl()
->InstallLinuxPackageProgress(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
EXPECT_EQ(dbus_result.status(), InstallLinuxPackageProgressSignal::FAILED);
EXPECT_EQ(dbus_result.progress_percent(), 0);
EXPECT_EQ(dbus_result.failure_details(), kFailureDetails);
}
TEST(ContainerListenerImplTest,
ValidUninstallPackageProgressCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::UninstallPackageProgressInfo request;
vm_tools::EmptyMessage response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
request.set_status(
vm_tools::container::UninstallPackageProgressInfo::UNINSTALLING);
request.set_progress_percent(72);
UninstallPackageProgressSignal dbus_result;
EXPECT_CALL(test_framework.get_mock_exported_object(),
SendSignal(AllOf(HasInterfaceName(kVmCiceroneInterface),
HasMethodName(kUninstallPackageProgressSignal))))
.WillOnce(Invoke([&dbus_result](dbus::Signal* signal) {
ProtoSignalHelper(signal, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service()
.GetContainerListenerImpl()
->UninstallPackageProgress(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
EXPECT_EQ(dbus_result.status(), UninstallPackageProgressSignal::UNINSTALLING);
EXPECT_EQ(dbus_result.progress_percent(), 72);
EXPECT_EQ(dbus_result.failure_details(), "");
}
TEST(ContainerListenerImplTest,
FailedUninstallPackageProgressCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::UninstallPackageProgressInfo request;
vm_tools::EmptyMessage response;
const std::string kFailureDetails = "Hahaha NO";
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
request.set_status(vm_tools::container::UninstallPackageProgressInfo::FAILED);
request.set_failure_details(kFailureDetails);
UninstallPackageProgressSignal dbus_result;
EXPECT_CALL(test_framework.get_mock_exported_object(),
SendSignal(AllOf(HasInterfaceName(kVmCiceroneInterface),
HasMethodName(kUninstallPackageProgressSignal))))
.WillOnce(Invoke([&dbus_result](dbus::Signal* signal) {
ProtoSignalHelper(signal, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service()
.GetContainerListenerImpl()
->UninstallPackageProgress(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
EXPECT_EQ(dbus_result.status(), UninstallPackageProgressSignal::FAILED);
EXPECT_EQ(dbus_result.progress_percent(), 0);
EXPECT_EQ(dbus_result.failure_details(), kFailureDetails);
}
TEST(ContainerListenerImplTest, ValidOpenTerminalCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::OpenTerminalRequest request;
vm_tools::EmptyMessage response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
*request.add_params() = "-c";
*request.add_params() = "/bin/ls";
*request.add_params() = "/tmp";
vm_tools::apps::TerminalParams dbus_result;
EXPECT_CALL(
test_framework.get_mock_vm_applications_service_proxy(),
CallMethodAndBlock(
AllOf(
HasInterfaceName(vm_tools::apps::kVmApplicationsServiceInterface),
HasMethodName(
vm_tools::apps::kVmApplicationsServiceLaunchTerminalMethod)),
_))
.WillOnce(Invoke([&dbus_result](dbus::MethodCall* method_call, Unused) {
return ProtoMethodCallHelper(method_call, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->OpenTerminal(
&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
ASSERT_EQ(dbus_result.params_size(), 3);
EXPECT_EQ(dbus_result.params(0), "-c");
EXPECT_EQ(dbus_result.params(1), "/bin/ls");
EXPECT_EQ(dbus_result.params(2), "/tmp");
}
TEST(ContainerListenerImplTest,
ValidUpdateMimeTypesCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::UpdateMimeTypesRequest request;
vm_tools::EmptyMessage response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
(*request.mutable_mime_type_mappings())["aiff"] = "audio/x-aiff";
(*request.mutable_mime_type_mappings())["rar"] = "application/rar";
(*request.mutable_mime_type_mappings())["png"] = "image/png";
vm_tools::apps::MimeTypes dbus_result;
EXPECT_CALL(
test_framework.get_mock_vm_applications_service_proxy(),
CallMethodAndBlock(
AllOf(
HasInterfaceName(vm_tools::apps::kVmApplicationsServiceInterface),
HasMethodName(
vm_tools::apps::kVmApplicationsServiceUpdateMimeTypesMethod)),
_))
.WillOnce(Invoke([&dbus_result](dbus::MethodCall* method_call, Unused) {
return ProtoMethodCallHelper(method_call, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->UpdateMimeTypes(
&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
const auto& mime_type_mappings = dbus_result.mime_type_mappings();
EXPECT_EQ(mime_type_mappings.size(), 3);
auto aiff_iter = mime_type_mappings.find("aiff");
ASSERT_TRUE(aiff_iter != mime_type_mappings.end());
EXPECT_EQ(aiff_iter->second, "audio/x-aiff");
auto rar_iter = mime_type_mappings.find("rar");
ASSERT_TRUE(rar_iter != mime_type_mappings.end());
EXPECT_EQ(rar_iter->second, "application/rar");
auto png_iter = mime_type_mappings.find("png");
ASSERT_TRUE(png_iter != mime_type_mappings.end());
EXPECT_EQ(png_iter->second, "image/png");
}
TEST(ContainerListenerImplTest,
ValidApplyAnsiblePlaybookProgressCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::ApplyAnsiblePlaybookProgressInfo request;
vm_tools::EmptyMessage response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
request.set_status(
vm_tools::container::ApplyAnsiblePlaybookProgressInfo::SUCCEEDED);
ApplyAnsiblePlaybookProgressSignal dbus_result;
EXPECT_CALL(
test_framework.get_mock_exported_object(),
SendSignal(AllOf(HasInterfaceName(kVmCiceroneInterface),
HasMethodName(kApplyAnsiblePlaybookProgressSignal))))
.WillOnce(Invoke([&dbus_result](dbus::Signal* signal) {
ProtoSignalHelper(signal, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service()
.GetContainerListenerImpl()
->ApplyAnsiblePlaybookProgress(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
EXPECT_EQ(dbus_result.status(),
ApplyAnsiblePlaybookProgressSignal::SUCCEEDED);
EXPECT_EQ(dbus_result.failure_details(), "");
}
TEST(ContainerListenerImplTest,
FailedApplyAnsiblePlaybookProgressCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::ApplyAnsiblePlaybookProgressInfo request;
vm_tools::EmptyMessage response;
const std::string kFailureDetails = "Hahaha NO";
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
request.set_status(
vm_tools::container::ApplyAnsiblePlaybookProgressInfo::FAILED);
request.set_failure_details(kFailureDetails);
ApplyAnsiblePlaybookProgressSignal dbus_result;
EXPECT_CALL(
test_framework.get_mock_exported_object(),
SendSignal(AllOf(HasInterfaceName(kVmCiceroneInterface),
HasMethodName(kApplyAnsiblePlaybookProgressSignal))))
.WillOnce(Invoke([&dbus_result](dbus::Signal* signal) {
ProtoSignalHelper(signal, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service()
.GetContainerListenerImpl()
->ApplyAnsiblePlaybookProgress(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
EXPECT_EQ(dbus_result.status(), ApplyAnsiblePlaybookProgressSignal::FAILED);
EXPECT_EQ(dbus_result.failure_details(), kFailureDetails);
}
TEST(ContainerListenerImplTest,
InProgressApplyAnsiblePlaybookProgressCallShouldProduceDBusMessage) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::ApplyAnsiblePlaybookProgressInfo request;
vm_tools::EmptyMessage response;
const std::string kStatusString = "Yesh milord. More work?";
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
request.set_status(
vm_tools::container::ApplyAnsiblePlaybookProgressInfo::IN_PROGRESS);
request.add_status_string(kStatusString);
ApplyAnsiblePlaybookProgressSignal dbus_result;
EXPECT_CALL(
test_framework.get_mock_exported_object(),
SendSignal(AllOf(HasInterfaceName(kVmCiceroneInterface),
HasMethodName(kApplyAnsiblePlaybookProgressSignal))))
.WillOnce(Invoke([&dbus_result](dbus::Signal* signal) {
ProtoSignalHelper(signal, &dbus_result);
}));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service()
.GetContainerListenerImpl()
->ApplyAnsiblePlaybookProgress(&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(dbus_result.vm_name(), ServiceTestingHelper::kDefaultVmName);
EXPECT_EQ(dbus_result.container_name(),
ServiceTestingHelper::kDefaultContainerName);
EXPECT_EQ(dbus_result.owner_id(), ServiceTestingHelper::kDefaultOwnerId);
EXPECT_EQ(dbus_result.status(),
ApplyAnsiblePlaybookProgressSignal::IN_PROGRESS);
EXPECT_EQ(dbus_result.status_string(0), kStatusString);
}
TEST(ContainerListenerImplTest, ValidReportMetricsCallShouldAccumulateMetrics) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
// Fake an RPC from the guest containing two metrics.
vm_tools::container::ReportMetricsRequest request;
vm_tools::container::ReportMetricsResponse response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
vm_tools::container::Metric* m = request.add_metric();
m->set_name("borealis-swap-kb-written");
m->set_value(123456);
m = request.add_metric();
m->set_name("borealis-disk-kb-read");
m->set_value(654321);
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->ReportMetrics(
&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
// Force a call to GuestMetrics::ReportDailyMetrics, which should report all
// eight disk metrics to UMA. The two four which received data from the fake
// RPC above should contain the reported data, and the other six should be
// zero.
EXPECT_CALL(*test_framework.GetMetricsLibraryMock(), SendToUMA(_, _, _, _, _))
.Times(4)
.WillRepeatedly([](const std::string& name, int sample, int min, int max,
int nbuckets) {
if (name == "Borealis.Disk.SwapWritesDaily") {
EXPECT_EQ(sample, 123456);
} else if (name == "Borealis.Disk.StatefulReadsDaily") {
EXPECT_EQ(sample, 654321);
} else {
// Borealis.Disk.{SwapReads,StatefulWrites}Daily
EXPECT_EQ(sample, 0);
EXPECT_EQ(name.find("Crostini"), std::string::npos);
}
return true;
});
test_framework.GetGuestMetrics()->ReportMetricsImmediatelyForTesting();
}
TEST(ContainerListenerImplTest,
ValidReportMetricsCallShouldAccumulateMetricsForCrostini) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS,
"termina");
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
// Fake an RPC from the guest containing two metrics.
vm_tools::container::ReportMetricsRequest request;
vm_tools::container::ReportMetricsResponse response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
vm_tools::container::Metric* m = request.add_metric();
m->set_name("borealis-swap-kb-written");
m->set_value(123456);
m = request.add_metric();
m->set_name("borealis-disk-kb-read");
m->set_value(654321);
m = request.add_metric();
m->set_name("crostini-disk-kb-read");
m->set_value(123321);
m = request.add_metric();
m->set_name("crostini-disk-kb-written");
m->set_value(321123);
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->ReportMetrics(
&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
// Force a call to GuestMetrics::ReportDailyMetrics, which should report all
// eight disk metrics to UMA. The two four which received data from the fake
// RPC above should contain the reported data, and the other six should be
// zero.
EXPECT_CALL(*test_framework.GetMetricsLibraryMock(), SendToUMA(_, _, _, _, _))
.Times(4)
.WillRepeatedly([](const std::string& name, int sample, int min, int max,
int nbuckets) {
if (name == "Crostini.Disk.StatefulWritesDaily") {
EXPECT_EQ(sample, 321123);
} else if (name == "Crostini.Disk.StatefulReadsDaily") {
EXPECT_EQ(sample, 123321);
} else {
// Crostini.Disk.Swap{Reads,Writes}Daily
EXPECT_EQ(sample, 0);
}
return true;
});
test_framework.GetGuestMetrics()->ReportMetricsImmediatelyForTesting();
}
TEST(ContainerListenerImplTest,
ReportingInodeCountShouldGenerateAndEmitSpaceMetrics) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
// Setup a mock provider for sysinfo.
auto sysinfo_provider = std::make_unique<SysinfoProviderMock>();
SysinfoProviderMock* sysinfo_provider_reference = sysinfo_provider.get();
test_framework.GetGuestMetrics()->SetSysinfoProviderForTesting(
std::move(sysinfo_provider));
// Fake an RPC from the guest containing two metrics.
vm_tools::container::ReportMetricsRequest request;
vm_tools::container::ReportMetricsResponse response;
// Setup metrics request.
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
vm_tools::container::Metric* m = request.add_metric();
m->set_name("borealis-inode-count");
m->set_value(100);
// Replace the concierge object proxy with our mock.
EXPECT_CALL(test_framework.get_mock_bus(),
GetObjectProxy(vm_tools::concierge::kVmConciergeServiceName, _))
.WillOnce(
testing::Return(&test_framework.get_mock_concierge_service_proxy()));
// Replace the ListVmDisks request to concierge.
EXPECT_CALL(
test_framework.get_mock_concierge_service_proxy(),
DoCallMethod(
AllOf(HasInterfaceName(vm_tools::concierge::kVmConciergeInterface),
HasMethodName(vm_tools::concierge::kListVmDisksMethod)),
_, _))
.WillOnce(Invoke([](dbus::MethodCall* method_call, int timeout,
base::OnceCallback<void(dbus::Response*)>* callback) {
method_call->SetSerial(123);
std::unique_ptr<dbus::Response> dbus_response(
dbus::Response::FromMethodCall(method_call));
dbus::MessageWriter writer(dbus_response.get());
vm_tools::concierge::ListVmDisksResponse response;
// Set the response.
vm_tools::concierge::VmDiskInfo* image = response.add_images();
image->set_name("borealis");
image->set_size(1000000);
image->set_path("/mnt/stateful/borealis.img");
writer.AppendProtoAsArrayOfBytes(response);
std::move(*callback).Run(std::move(dbus_response.get()));
}));
// Set the response to AmountOfTotalDiskSpace.
EXPECT_CALL(*sysinfo_provider_reference,
AmountOfTotalDiskSpace(base::FilePath("/mnt/stateful")))
.WillOnce(testing::Return(2000000));
// Set the response to AmountOfFreeDiskSpace.
EXPECT_CALL(*sysinfo_provider_reference,
AmountOfFreeDiskSpace(base::FilePath("/mnt/stateful")))
.WillOnce(testing::Return(500000));
// InodeRatioAtStartup [KiB] =
// (1000000[image_size]/100[inode_count])/1024 = 9
EXPECT_CALL(*test_framework.GetMetricsLibraryMock(),
SendToUMA("Borealis.Disk.InodeRatioAtStartup", 9, 0, 10240, 50));
// VMUsageToTotalSpacePercentageAtStartup [%] =
// (1000000[image_size]/2000000[total_size])/100 = 50%
EXPECT_CALL(*test_framework.GetMetricsLibraryMock(),
SendPercentageToUMA(
"Borealis.Disk.VMUsageToTotalSpacePercentageAtStartup", 50));
// VMUsageToTotalUsagePercentageAtStartup [%] =
// (1000000[image_size]/(2000000[total_size] - 500000[free_space]))/100 = 66%
EXPECT_CALL(*test_framework.GetMetricsLibraryMock(),
SendPercentageToUMA(
"Borealis.Disk.VMUsageToTotalUsagePercentageAtStartup", 66));
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->ReportMetrics(
&ctx, &request, &response);
ASSERT_TRUE(status.ok()) << status.error_message();
}
TEST(ContainerListenerImplTest, ReportMetricsCallWithTooManyMetricsShouldFail) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::ReportMetricsRequest request;
vm_tools::container::ReportMetricsResponse response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
for (int i = 0; i < 40; ++i) {
vm_tools::container::Metric* m = request.add_metric();
m->set_name("a-fake-metric-whose-name-will-be-ignored");
m->set_value(123456);
}
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->ReportMetrics(
&ctx, &request, &response);
ASSERT_FALSE(status.ok()) << status.error_message();
}
TEST(ContainerListenerImplTest, ReportMetricsCallWithBadMetricNameShouldFail) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::ReportMetricsRequest request;
vm_tools::container::ReportMetricsResponse response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
vm_tools::container::Metric* m = request.add_metric();
m->set_name("This name contains forbidden characters!");
m->set_value(123456);
grpc::ServerContext ctx;
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->ReportMetrics(
&ctx, &request, &response);
ASSERT_FALSE(status.ok()) << status.error_message();
}
TEST(ContainerListenerImplTest, ReportMetricsCallsShouldBeRateLimited) {
ServiceTestingHelper test_framework(ServiceTestingHelper::NORMAL_MOCKS);
test_framework.SetUpDefaultVmAndContainer();
test_framework.ExpectNoDBusMessages();
vm_tools::container::ReportMetricsRequest request;
vm_tools::container::ReportMetricsResponse response;
request.set_token(ServiceTestingHelper::kDefaultContainerToken);
vm_tools::container::Metric* m = request.add_metric();
m->set_name("borealis-swap-kb-written");
m->set_value(123456);
grpc::ServerContext ctx;
// The first 40 calls should succeed.
for (int i = 0; i < 40; i++) {
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->ReportMetrics(
&ctx, &request, &response);
EXPECT_TRUE(status.ok()) << status.error_message();
EXPECT_EQ(response.error(), 0);
}
// 41st call should hit the rate limit and fail.
grpc::Status status =
test_framework.get_service().GetContainerListenerImpl()->ReportMetrics(
&ctx, &request, &response);
EXPECT_TRUE(status.ok()) << status.error_message();
EXPECT_NE(response.error(), 0);
}
} // namespace
} // namespace cicerone
} // namespace vm_tools