Commit 7fa5edbf authored by Olya Kalitova's avatar Olya Kalitova Committed by Commit Bot

Add service for Crostini Ansible management

Adds keyed service to handle Crostini Ansible management so
that this feature is separated from Crostini manager and Crostini manager
does not get overloaded.

This CL also adds basic functionality for Ansible installation.

Bug: 986276
Change-Id: Ia5d0cab00f5f40ab152131f2edd2dce52a0bb524
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1712677
Commit-Queue: Olya Kalitova <okalitova@chromium.org>
Reviewed-by: default avatarTimothy Loh <timloh@chromium.org>
Reviewed-by: default avatarNic Hollingum <hollingum@google.com>
Cr-Commit-Position: refs/heads/master@{#686406}
parent 42e9af1a
......@@ -726,6 +726,8 @@ source_set("chromeos") {
"chrome_browser_main_chromeos.h",
"chrome_content_browser_client_chromeos_part.cc",
"chrome_content_browser_client_chromeos_part.h",
"crostini/crostini_ansible_management_service.cc",
"crostini/crostini_ansible_management_service.h",
"crostini/crostini_export_import.cc",
"crostini/crostini_export_import.h",
"crostini/crostini_export_import_notification.cc",
......@@ -2427,6 +2429,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",
"crostini/crostini_ansible_management_service_unittest.cc",
"crostini/crostini_export_import_unittest.cc",
"crostini/crostini_manager_unittest.cc",
"crostini/crostini_mime_types_service_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/crostini/crostini_ansible_management_service.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "chrome/browser/chromeos/crostini/crostini_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
namespace crostini {
namespace {
class CrostiniAnsibleManagementServiceFactory
: public BrowserContextKeyedServiceFactory {
public:
static CrostiniAnsibleManagementService* GetForProfile(Profile* profile) {
return static_cast<CrostiniAnsibleManagementService*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
static CrostiniAnsibleManagementServiceFactory* GetInstance() {
static base::NoDestructor<CrostiniAnsibleManagementServiceFactory> factory;
return factory.get();
}
private:
friend class base::NoDestructor<CrostiniAnsibleManagementServiceFactory>;
CrostiniAnsibleManagementServiceFactory()
: BrowserContextKeyedServiceFactory(
"CrostiniAnsibleManagementService",
BrowserContextDependencyManager::GetInstance()) {
DependsOn(CrostiniManagerFactory::GetInstance());
}
~CrostiniAnsibleManagementServiceFactory() override = default;
// BrowserContextKeyedServiceFactory:
KeyedService* BuildServiceInstanceFor(
content::BrowserContext* context) const override {
Profile* profile = Profile::FromBrowserContext(context);
return new CrostiniAnsibleManagementService(profile);
}
};
} // namespace
CrostiniAnsibleManagementService*
CrostiniAnsibleManagementService::GetForProfile(Profile* profile) {
return CrostiniAnsibleManagementServiceFactory::GetForProfile(profile);
}
CrostiniAnsibleManagementService::CrostiniAnsibleManagementService(
Profile* profile)
: profile_(profile), weak_ptr_factory_(this) {}
CrostiniAnsibleManagementService::~CrostiniAnsibleManagementService() = default;
void CrostiniAnsibleManagementService::InstallAnsibleInDefaultContainer(
base::OnceCallback<void(bool success)> callback) {
DCHECK(ansible_installation_finished_callback_.is_null());
ansible_installation_finished_callback_ = std::move(callback);
CrostiniManager::GetForProfile(profile_)->InstallLinuxPackageFromApt(
kCrostiniDefaultVmName, kCrostiniDefaultContainerName,
kCrostiniDefaultAnsibleVersion,
base::BindOnce(
&CrostiniAnsibleManagementService::OnInstallAnsibleInDefaultContainer,
weak_ptr_factory_.GetWeakPtr()));
}
void CrostiniAnsibleManagementService::OnInstallAnsibleInDefaultContainer(
CrostiniResult result) {
if (result == CrostiniResult::INSTALL_LINUX_PACKAGE_FAILED) {
LOG(ERROR) << "Ansible installation failed";
std::move(ansible_installation_finished_callback_).Run(/*success=*/false);
return;
}
DCHECK_NE(result, CrostiniResult::BLOCKING_OPERATION_ALREADY_ACTIVE);
DCHECK_EQ(result, CrostiniResult::SUCCESS);
VLOG(1) << "Ansible installation has been started successfully";
// Waiting for Ansible installation progress being reported.
}
void CrostiniAnsibleManagementService::OnInstallLinuxPackageProgress(
const std::string& vm_name,
const std::string& container_name,
InstallLinuxPackageProgressStatus status,
int progress_percent) {
DCHECK_EQ(vm_name, kCrostiniDefaultVmName);
DCHECK_EQ(container_name, kCrostiniDefaultContainerName);
switch (status) {
case InstallLinuxPackageProgressStatus::SUCCEEDED:
std::move(ansible_installation_finished_callback_).Run(/*success=*/true);
return;
case InstallLinuxPackageProgressStatus::FAILED:
std::move(ansible_installation_finished_callback_).Run(/*success=*/false);
return;
// TODO(okalitova): Report Ansible downloading/installation progress.
case InstallLinuxPackageProgressStatus::DOWNLOADING:
VLOG(1) << "Ansible downloading progress: " << progress_percent << "%";
return;
case InstallLinuxPackageProgressStatus::INSTALLING:
VLOG(1) << "Ansible installing progress: " << progress_percent << "%";
return;
default:
NOTREACHED();
}
}
void CrostiniAnsibleManagementService::OnUninstallPackageProgress(
const std::string& vm_name,
const std::string& container_name,
UninstallPackageProgressStatus status,
int progress_percent) {
NOTIMPLEMENTED();
}
void CrostiniAnsibleManagementService::ApplyAnsiblePlaybookToDefaultContainer(
const std::string& playbook) {
NOTIMPLEMENTED();
}
} // namespace crostini
// 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_CROSTINI_CROSTINI_ANSIBLE_MANAGEMENT_SERVICE_H_
#define CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_ANSIBLE_MANAGEMENT_SERVICE_H_
#include <string>
#include "base/callback.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "components/keyed_service/core/keyed_service.h"
class Profile;
namespace crostini {
// TODO(okalitova): Install Ansible from backports repo once this is feasible.
constexpr char kCrostiniDefaultAnsibleVersion[] =
"ansible;2.2.1.0-2+deb9u1;all;debian-stable-main";
// CrostiniAnsibleManagementService is responsible for Crostini default
// container management using Ansible.
class CrostiniAnsibleManagementService
: public KeyedService,
public LinuxPackageOperationProgressObserver {
public:
static CrostiniAnsibleManagementService* GetForProfile(Profile* profile);
explicit CrostiniAnsibleManagementService(Profile* profile);
~CrostiniAnsibleManagementService() override;
void InstallAnsibleInDefaultContainer(
base::OnceCallback<void(bool success)> callback);
void ApplyAnsiblePlaybookToDefaultContainer(const std::string& playbook);
// LinuxPackageOperationProgressObserver:
void OnInstallLinuxPackageProgress(const std::string& vm_name,
const std::string& container_name,
InstallLinuxPackageProgressStatus status,
int progress_percent) override;
void OnUninstallPackageProgress(const std::string& vm_name,
const std::string& container_name,
UninstallPackageProgressStatus status,
int progress_percent) override;
private:
void OnInstallAnsibleInDefaultContainer(CrostiniResult result);
Profile* profile_;
base::OnceCallback<void(bool success)>
ansible_installation_finished_callback_;
base::WeakPtrFactory<CrostiniAnsibleManagementService> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(CrostiniAnsibleManagementService);
};
} // namespace crostini
#endif // CHROME_BROWSER_CHROMEOS_CROSTINI_CROSTINI_ANSIBLE_MANAGEMENT_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/crostini/crostini_ansible_management_service.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_cicerone_client.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace crostini {
class CrostiniAnsibleManagementServiceTest : public testing::Test {
public:
CrostiniAnsibleManagementServiceTest() {
chromeos::DBusThreadManager::Initialize();
fake_cicerone_client_ = static_cast<chromeos::FakeCiceroneClient*>(
chromeos::DBusThreadManager::Get()->GetCiceroneClient());
profile_ = std::make_unique<TestingProfile>();
crostini_ansible_management_service_ =
std::make_unique<CrostiniAnsibleManagementService>(profile_.get());
}
~CrostiniAnsibleManagementServiceTest() override {
crostini_ansible_management_service_.reset();
profile_.reset();
chromeos::DBusThreadManager::Shutdown();
}
protected:
Profile* profile() { return profile_.get(); }
CrostiniAnsibleManagementService* crostini_ansible_management_service() {
return crostini_ansible_management_service_.get();
}
void SendSucceededInstallSignal() {
vm_tools::cicerone::InstallLinuxPackageProgressSignal signal;
signal.set_owner_id(CryptohomeIdForProfile(profile()));
signal.set_vm_name(kCrostiniDefaultVmName);
signal.set_container_name(kCrostiniDefaultContainerName);
signal.set_status(
vm_tools::cicerone::InstallLinuxPackageProgressSignal::SUCCEEDED);
fake_cicerone_client_->InstallLinuxPackageProgress(signal);
}
content::TestBrowserThreadBundle test_browser_thread_bundle_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<CrostiniAnsibleManagementService>
crostini_ansible_management_service_;
base::MockCallback<base::OnceCallback<void(bool)>>
ansible_installation_finished_mock_callback_;
// Owned by chromeos::DBusThreadManager
chromeos::FakeCiceroneClient* fake_cicerone_client_;
DISALLOW_COPY_AND_ASSIGN(CrostiniAnsibleManagementServiceTest);
};
TEST_F(CrostiniAnsibleManagementServiceTest,
InstallAnsibleInDefaultContainerSuccess) {
vm_tools::cicerone::InstallLinuxPackageResponse response;
response.set_status(vm_tools::cicerone::InstallLinuxPackageResponse::STARTED);
fake_cicerone_client_->set_install_linux_package_response(response);
CrostiniManager::GetForProfile(profile())
->AddLinuxPackageOperationProgressObserver(
crostini_ansible_management_service());
EXPECT_CALL(ansible_installation_finished_mock_callback_, Run(true)).Times(1);
crostini_ansible_management_service()->InstallAnsibleInDefaultContainer(
ansible_installation_finished_mock_callback_.Get());
// Actually starts installing Ansible.
base::RunLoop().RunUntilIdle();
SendSucceededInstallSignal();
}
TEST_F(CrostiniAnsibleManagementServiceTest,
InstallAnsibleInDefaultContainerFail) {
vm_tools::cicerone::InstallLinuxPackageResponse response;
response.set_status(vm_tools::cicerone::InstallLinuxPackageResponse::FAILED);
fake_cicerone_client_->set_install_linux_package_response(response);
EXPECT_CALL(ansible_installation_finished_mock_callback_, Run(false))
.Times(1);
crostini_ansible_management_service()->InstallAnsibleInDefaultContainer(
ansible_installation_finished_mock_callback_.Get());
// Actually starts installing Ansible.
base::RunLoop().RunUntilIdle();
}
} // namespace crostini
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