Commit cf1a128d authored by Willie Koomson's avatar Willie Koomson Committed by Commit Bot

Add helper service to start Concierge on demand

ConciergeHelperService starts Concierge, and provides methods
for throttling VMs through Concierge.

Test: Run unittests
Bug: 997397
Change-Id: I76f6aba67d0f8f702789ff406a803c7c52db8bc2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1809806Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarYusuke Sato <yusukes@chromium.org>
Commit-Queue: Willie Koomson <wvk@google.com>
Cr-Commit-Position: refs/heads/master@{#700456}
parent 279f9599
......@@ -746,6 +746,8 @@ source_set("chromeos") {
"chrome_browser_main_chromeos.h",
"chrome_content_browser_client_chromeos_part.cc",
"chrome_content_browser_client_chromeos_part.h",
"concierge_helper_service.cc",
"concierge_helper_service.h",
"crostini/ansible/ansible_management_service.cc",
"crostini/ansible/ansible_management_service.h",
"crostini/ansible/ansible_management_util.cc",
......@@ -2499,6 +2501,7 @@ source_set("unit_tests") {
"child_accounts/usage_time_limit_processor_unittest.cc",
"child_accounts/usage_time_state_notifier_unittest.cc",
"chrome_content_browser_client_chromeos_part_unittest.cc",
"concierge_helper_service_unittest.cc",
"crostini/ansible/ansible_management_service_unittest.cc",
"crostini/ansible/pending_software_changes_unittest.cc",
"crostini/ansible/software_config_unittest.cc",
......
// Copyright 2019 The Chromium 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 "chrome/browser/chromeos/concierge_helper_service.h"
#include "base/bind.h"
#include "base/optional.h"
#include "chromeos/dbus/concierge/service.pb.h"
#include "chromeos/dbus/concierge_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon_client.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "content/public/browser/browser_context.h"
namespace chromeos {
namespace {
void OnStartConcierge(bool started) {
if (started)
VLOG(1) << "Concierge D-Bus service successfully started";
else
LOG(ERROR) << "Unable to start Concierge D-Bus service";
}
void OnSetVmCpuRestriction(
base::Optional<vm_tools::concierge::SetVmCpuRestrictionResponse> response) {
if (!response || !response->success()) {
LOG(ERROR) << "Failed to call SetVmCpuRestriction";
return;
}
}
// Starts Concierge DBus service through debugd. If the service is already
// running, this request will be ignored.
void StartConcierge() {
auto* client = DBusThreadManager::Get()->GetDebugDaemonClient();
if (!client) {
LOG(WARNING) << "DebugDaemonClient is not available";
OnStartConcierge(false);
return;
}
client->StartConcierge(base::BindOnce(&OnStartConcierge));
}
// Adds a callback to be run when Concierge DBus service becomes available.
// If the service is already available, runs the callback immediately.
void WaitForConciergeToBeAvailable(
dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback) {
auto* client = DBusThreadManager::Get()->GetConciergeClient();
if (!client) {
LOG(WARNING) << "ConciergeClient is not available";
std::move(callback).Run(false);
return;
}
client->WaitForServiceToBeAvailable(std::move(callback));
}
void SetVmCpuRestriction(
vm_tools::concierge::SetVmCpuRestrictionRequest request,
bool started) {
if (!started) {
LOG(ERROR) << "Unable to start Concierge to make a throttling request.";
return;
}
auto* client = DBusThreadManager::Get()->GetConciergeClient();
if (!client) {
LOG(WARNING) << "ConciergeClient is not available";
OnSetVmCpuRestriction(base::nullopt);
return;
}
client->SetVmCpuRestriction(request, base::BindOnce(&OnSetVmCpuRestriction));
}
// Make a request to throttle or unthrottle a VM cgroup (according to
// do_restrict).
void MakeRestrictionRequest(vm_tools::concierge::CpuCgroup cgroup,
bool do_restrict) {
vm_tools::concierge::SetVmCpuRestrictionRequest request;
request.set_cpu_cgroup(cgroup);
request.set_cpu_restriction_state(
do_restrict ? vm_tools::concierge::CPU_RESTRICTION_BACKGROUND
: vm_tools::concierge::CPU_RESTRICTION_FOREGROUND);
// Since ConciergeHelperService starts Concierge on construction,
// and Concierge is auto-respawned by Upstart, the service must either be
// available or restarting. We can run our throttle request as a callback to
// WaitForConciergeToBeAvailable(): if Concierge is available, the request is
// run immediately. Else, Concierge is restarting and the request will be run
// when the service becomes available.
WaitForConciergeToBeAvailable(
base::BindOnce(&SetVmCpuRestriction, std::move(request)));
}
} // namespace
// static
ConciergeHelperService* ConciergeHelperService::GetForBrowserContext(
content::BrowserContext* context) {
return ConciergeHelperServiceFactory::GetForBrowserContext(context);
}
ConciergeHelperService::ConciergeHelperService() {
StartConcierge();
}
void ConciergeHelperService::SetArcVmCpuRestriction(bool do_restrict) {
MakeRestrictionRequest(vm_tools::concierge::CPU_CGROUP_ARCVM, do_restrict);
}
void ConciergeHelperService::SetTerminaVmCpuRestriction(bool do_restrict) {
MakeRestrictionRequest(vm_tools::concierge::CPU_CGROUP_TERMINA, do_restrict);
}
void ConciergeHelperService::SetPluginVmCpuRestriction(bool do_restrict) {
MakeRestrictionRequest(vm_tools::concierge::CPU_CGROUP_PLUGINVM, do_restrict);
}
// static
ConciergeHelperServiceFactory* ConciergeHelperServiceFactory::GetInstance() {
static base::NoDestructor<ConciergeHelperServiceFactory> instance;
return instance.get();
}
// static
ConciergeHelperService* ConciergeHelperServiceFactory::GetForBrowserContext(
content::BrowserContext* context) {
return static_cast<ConciergeHelperService*>(
ConciergeHelperServiceFactory::GetInstance()->GetServiceForBrowserContext(
context, true /* create */));
}
ConciergeHelperServiceFactory::ConciergeHelperServiceFactory()
: BrowserContextKeyedServiceFactory(
"ConciergeHelperServiceFactory",
BrowserContextDependencyManager::GetInstance()) {}
KeyedService* ConciergeHelperServiceFactory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
if (context->IsOffTheRecord())
return nullptr;
return new ConciergeHelperService();
}
} // namespace chromeos
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_CHROMEOS_CONCIERGE_HELPER_SERVICE_H_
#define CHROME_BROWSER_CHROMEOS_CONCIERGE_HELPER_SERVICE_H_
#include "base/macros.h"
#include "base/no_destructor.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
#include "components/keyed_service/core/keyed_service.h"
namespace chromeos {
// This class starts Concierge on construction and provides functions to
// enable/disable throttling for various VMs (crostini, arc, plugin, etc).
class ConciergeHelperService : public KeyedService {
public:
// Return singleton instance for the given BrowserContext.
static ConciergeHelperService* GetForBrowserContext(
content::BrowserContext* context);
ConciergeHelperService();
~ConciergeHelperService() override = default;
void SetArcVmCpuRestriction(bool do_restrict);
void SetTerminaVmCpuRestriction(bool do_restrict);
void SetPluginVmCpuRestriction(bool do_restrict);
private:
DISALLOW_COPY_AND_ASSIGN(ConciergeHelperService);
};
class ConciergeHelperServiceFactory : public BrowserContextKeyedServiceFactory {
public:
static ConciergeHelperServiceFactory* GetInstance();
static ConciergeHelperService* GetForBrowserContext(
content::BrowserContext* context);
protected:
friend class base::NoDestructor<ConciergeHelperServiceFactory>;
ConciergeHelperServiceFactory();
~ConciergeHelperServiceFactory() override = default;
// BrowserContextKeyedServiceFactory:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override;
private:
DISALLOW_COPY_AND_ASSIGN(ConciergeHelperServiceFactory);
};
} // namespace chromeos
#endif // CHROME_BROWSER_CHROMEOS_CONCIERGE_HELPER_SERVICE_H_
// Copyright 2019 The Chromium 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 "chrome/browser/chromeos/concierge_helper_service.h"
#include <memory>
#include "base/macros.h"
#include "base/run_loop.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/concierge/service.pb.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_concierge_client.h"
#include "chromeos/dbus/fake_debug_daemon_client.h"
#include "content/public/test/browser_task_environment.h"
#include "dbus/bus.h"
#include "dbus/object_proxy.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
class TestDebugDaemonClient : public FakeDebugDaemonClient {
public:
TestDebugDaemonClient() = default;
~TestDebugDaemonClient() override = default;
void StartConcierge(ConciergeCallback callback) override {
++start_concierge_called_;
FakeDebugDaemonClient::StartConcierge(std::move(callback));
}
size_t start_concierge_called() const { return start_concierge_called_; }
private:
size_t start_concierge_called_{0};
DISALLOW_COPY_AND_ASSIGN(TestDebugDaemonClient);
};
class TestConciergeClient : public FakeConciergeClient {
public:
TestConciergeClient() = default;
~TestConciergeClient() override = default;
void SetVmCpuRestriction(
const vm_tools::concierge::SetVmCpuRestrictionRequest& request,
DBusMethodCallback<vm_tools::concierge::SetVmCpuRestrictionResponse>
callback) override {
requests_.push_back(request);
FakeConciergeClient::SetVmCpuRestriction(request, std::move(callback));
}
// Runs callback when service becomes available. If service is already
// available, runs callback immediately.
void WaitForServiceToBeAvailable(
dbus::ObjectProxy::WaitForServiceToBeAvailableCallback callback)
override {
if (!service_is_available_) {
callbacks_.push_back(std::move(callback));
} else {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), service_is_available_));
}
}
// Sets whether service is available or not. If service_is_available is
// true, all callbacks waiting for service to be available will be run.
void SetServiceIsAvailable(bool service_is_available) {
service_is_available_ = service_is_available;
if (service_is_available_ && !callbacks_.empty()) {
for (auto& callback : callbacks_)
WaitForServiceToBeAvailable(std::move(callback));
}
}
const std::vector<vm_tools::concierge::SetVmCpuRestrictionRequest>& requests()
const {
return requests_;
}
private:
std::vector<vm_tools::concierge::SetVmCpuRestrictionRequest> requests_;
std::vector<dbus::ObjectProxy::WaitForServiceToBeAvailableCallback>
callbacks_;
bool service_is_available_ = true;
DISALLOW_COPY_AND_ASSIGN(TestConciergeClient);
};
class ConciergeHelperServiceTest : public testing::Test {
public:
ConciergeHelperServiceTest() = default;
~ConciergeHelperServiceTest() override = default;
// testing::Test:
void SetUp() override {
auto setter = DBusThreadManager::GetSetterForTesting();
setter->SetDebugDaemonClient(std::make_unique<TestDebugDaemonClient>());
setter->SetConciergeClient(std::make_unique<TestConciergeClient>());
service_ = ConciergeHelperService::GetForBrowserContext(&profile_);
}
void TearDown() override { DBusThreadManager::Shutdown(); }
protected:
ConciergeHelperService* service() { return service_; }
TestConciergeClient* fake_concierge_client() {
return static_cast<TestConciergeClient*>(
DBusThreadManager::Get()->GetConciergeClient());
}
TestDebugDaemonClient* fake_debug_client() {
return static_cast<TestDebugDaemonClient*>(
DBusThreadManager::Get()->GetDebugDaemonClient());
}
content::BrowserTaskEnvironment* task_environment() {
return &task_environment_;
}
private:
content::BrowserTaskEnvironment task_environment_;
TestingProfile profile_;
ConciergeHelperService* service_;
DISALLOW_COPY_AND_ASSIGN(ConciergeHelperServiceTest);
};
// Tests that ConciergeHelperService can be constructed and destructed. Also,
// check that ConciergeHelperService starts Concierge on construction.
TEST_F(ConciergeHelperServiceTest, TestConstructDestruct) {
EXPECT_EQ(1U, fake_debug_client()->start_concierge_called());
}
// Tests that ConciergeHelperService makes cpu restriction requests correctly.
TEST_F(ConciergeHelperServiceTest, TestSetVmCpuRestriction) {
vm_tools::concierge::SetVmCpuRestrictionResponse response;
response.set_success(true);
fake_concierge_client()->set_set_vm_cpu_restriction_response(response);
service()->SetArcVmCpuRestriction(true);
service()->SetArcVmCpuRestriction(false);
service()->SetTerminaVmCpuRestriction(true);
service()->SetTerminaVmCpuRestriction(false);
service()->SetPluginVmCpuRestriction(true);
service()->SetPluginVmCpuRestriction(false);
task_environment()->RunUntilIdle();
std::vector<vm_tools::concierge::SetVmCpuRestrictionRequest> requests(
fake_concierge_client()->requests());
EXPECT_EQ(6U, requests.size());
EXPECT_EQ(vm_tools::concierge::CPU_CGROUP_ARCVM, requests[0].cpu_cgroup());
EXPECT_EQ(vm_tools::concierge::CPU_RESTRICTION_BACKGROUND,
requests[0].cpu_restriction_state());
EXPECT_EQ(vm_tools::concierge::CPU_CGROUP_ARCVM, requests[1].cpu_cgroup());
EXPECT_EQ(vm_tools::concierge::CPU_RESTRICTION_FOREGROUND,
requests[1].cpu_restriction_state());
EXPECT_EQ(vm_tools::concierge::CPU_CGROUP_TERMINA, requests[2].cpu_cgroup());
EXPECT_EQ(vm_tools::concierge::CPU_RESTRICTION_BACKGROUND,
requests[2].cpu_restriction_state());
EXPECT_EQ(vm_tools::concierge::CPU_CGROUP_TERMINA, requests[3].cpu_cgroup());
EXPECT_EQ(vm_tools::concierge::CPU_RESTRICTION_FOREGROUND,
requests[3].cpu_restriction_state());
EXPECT_EQ(vm_tools::concierge::CPU_CGROUP_PLUGINVM, requests[4].cpu_cgroup());
EXPECT_EQ(vm_tools::concierge::CPU_RESTRICTION_BACKGROUND,
requests[4].cpu_restriction_state());
EXPECT_EQ(vm_tools::concierge::CPU_CGROUP_PLUGINVM, requests[5].cpu_cgroup());
EXPECT_EQ(vm_tools::concierge::CPU_RESTRICTION_FOREGROUND,
requests[5].cpu_restriction_state());
}
// Tests that ConciergeHelperService requests cpu restriction immediately when
// dbus service is available, but waits until service is ready when it is not
// available.
TEST_F(ConciergeHelperServiceTest, TestWaitForServiceAvailable) {
// Service is available, so request is made immediately.
vm_tools::concierge::SetVmCpuRestrictionResponse response;
response.set_success(true);
fake_concierge_client()->set_set_vm_cpu_restriction_response(response);
fake_concierge_client()->SetServiceIsAvailable(true);
service()->SetArcVmCpuRestriction(true);
task_environment()->RunUntilIdle();
EXPECT_EQ(1U, fake_concierge_client()->requests().size());
// Service is unavailable, so request is queued until it is.
fake_concierge_client()->SetServiceIsAvailable(false);
service()->SetArcVmCpuRestriction(true);
task_environment()->RunUntilIdle();
EXPECT_EQ(1U, fake_concierge_client()->requests().size());
fake_concierge_client()->SetServiceIsAvailable(true);
task_environment()->RunUntilIdle();
EXPECT_EQ(2U, fake_concierge_client()->requests().size());
EXPECT_EQ(vm_tools::concierge::CPU_CGROUP_ARCVM,
fake_concierge_client()->requests()[0].cpu_cgroup());
EXPECT_EQ(vm_tools::concierge::CPU_CGROUP_ARCVM,
fake_concierge_client()->requests()[1].cpu_cgroup());
EXPECT_EQ(vm_tools::concierge::CPU_RESTRICTION_BACKGROUND,
fake_concierge_client()->requests()[0].cpu_restriction_state());
EXPECT_EQ(vm_tools::concierge::CPU_RESTRICTION_BACKGROUND,
fake_concierge_client()->requests()[1].cpu_restriction_state());
}
} // namespace chromeos
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment