Commit 9ec0dfc9 authored by Nicholas Verne's avatar Nicholas Verne Committed by Commit Bot

Fixes a crash when the user's install flow fails and we press Cancel.

Verified fix using a BrowserTest.

Verifying the fix properly meant fleshing out FakeConciergeClient with
settable response protos for all methods.

Found an invalid DCHECK of |state_| in CrostiniInstallerView when testing.

Guard CrostiniRestarterService::Abort.
There used to be a DCHECK, but it shouldn't be. It is a legitimate possibility
to cancel the install flow right when the the container startup was complete.

Bug: 850281
Change-Id: I0cf6bcef1f4f043d9c7e40f3a8baa458fb6f6ba7
Reviewed-on: https://chromium-review.googlesource.com/1089600Reviewed-by: default avatarRyo Hashimoto <hashimoto@chromium.org>
Reviewed-by: default avatarTimothy Loh <timloh@chromium.org>
Commit-Queue: Nicholas Verne <nverne@chromium.org>
Cr-Commit-Position: refs/heads/master@{#565603}
parent 1bb7bf2f
...@@ -130,8 +130,9 @@ class CrostiniRestarter : public base::RefCountedThreadSafe<CrostiniRestarter> { ...@@ -130,8 +130,9 @@ class CrostiniRestarter : public base::RefCountedThreadSafe<CrostiniRestarter> {
if (is_aborted_) if (is_aborted_)
return; return;
CrostiniManager* crostini_manager = CrostiniManager::GetInstance();
// Finish Restart immediately if testing. // Finish Restart immediately if testing.
if (CrostiniManager::GetInstance()->skip_restart_for_testing()) { if (crostini_manager->skip_restart_for_testing()) {
content::BrowserThread::PostTask( content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, content::BrowserThread::UI, FROM_HERE,
base::BindOnce(&CrostiniRestarter::FinishRestart, base::BindOnce(&CrostiniRestarter::FinishRestart,
...@@ -140,10 +141,8 @@ class CrostiniRestarter : public base::RefCountedThreadSafe<CrostiniRestarter> { ...@@ -140,10 +141,8 @@ class CrostiniRestarter : public base::RefCountedThreadSafe<CrostiniRestarter> {
return; return;
} }
auto* cros_component_manager = if (crostini_manager->is_testing()) {
g_browser_process->platform_part()->cros_component_manager(); // Running in test. We still PostTask to prevent races between
if (!cros_component_manager) {
// Running in unittest. We still PostTask to prevent races between
// observers aborting. // observers aborting.
content::BrowserThread::PostTask( content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, content::BrowserThread::UI, FROM_HERE,
...@@ -154,7 +153,9 @@ class CrostiniRestarter : public base::RefCountedThreadSafe<CrostiniRestarter> { ...@@ -154,7 +153,9 @@ class CrostiniRestarter : public base::RefCountedThreadSafe<CrostiniRestarter> {
base::FilePath())); base::FilePath()));
return; return;
} }
auto* cros_component_manager =
g_browser_process->platform_part()->cros_component_manager();
DCHECK(cros_component_manager);
cros_component_manager->Load( cros_component_manager->Load(
"cros-termina", "cros-termina",
component_updater::CrOSComponentManager::MountPolicy::kMount, component_updater::CrOSComponentManager::MountPolicy::kMount,
...@@ -358,9 +359,12 @@ void CrostiniRestarterService::RunPendingCallbacks( ...@@ -358,9 +359,12 @@ void CrostiniRestarterService::RunPendingCallbacks(
void CrostiniRestarterService::Abort(CrostiniManager::RestartId restart_id) { void CrostiniRestarterService::Abort(CrostiniManager::RestartId restart_id) {
auto it = restarter_map_.find(restart_id); auto it = restarter_map_.find(restart_id);
DCHECK(it != restarter_map_.end()) if (it == restarter_map_.end()) {
<< "Aborting a restarter that already finished"; // This can happen if a user cancels the install flow at the exact right
// moment, for example.
LOG(ERROR) << "Aborting a restarter that already finished";
return;
}
it->second->Abort(); it->second->Abort();
ErasePending(it->second.get()); ErasePending(it->second.get());
// Erasing |it| also invalidates |it|, so make a key from |it| now. // Erasing |it| also invalidates |it|, so make a key from |it| now.
......
...@@ -229,6 +229,9 @@ class CrostiniManager : public chromeos::ConciergeClient::Observer, ...@@ -229,6 +229,9 @@ class CrostiniManager : public chromeos::ConciergeClient::Observer,
void set_skip_restart_for_testing() { skip_restart_for_testing_ = true; } void set_skip_restart_for_testing() { skip_restart_for_testing_ = true; }
bool skip_restart_for_testing() { return skip_restart_for_testing_; } bool skip_restart_for_testing() { return skip_restart_for_testing_; }
void set_is_testing(bool is_testing) { is_testing_ = is_testing; }
bool is_testing() { return is_testing_; }
// Adds a callback to receive notification of container shutdown. // Adds a callback to receive notification of container shutdown.
void AddShutdownContainerCallback( void AddShutdownContainerCallback(
Profile* profile, Profile* profile,
...@@ -340,6 +343,7 @@ class CrostiniManager : public chromeos::ConciergeClient::Observer, ...@@ -340,6 +343,7 @@ class CrostiniManager : public chromeos::ConciergeClient::Observer,
start_container_callbacks_; start_container_callbacks_;
bool skip_restart_for_testing_ = false; bool skip_restart_for_testing_ = false;
bool is_testing_ = false;
// Pending ShutdownContainer callbacks are keyed by <owner_id, vm_name, // Pending ShutdownContainer callbacks are keyed by <owner_id, vm_name,
// container_name> string tuples. // container_name> string tuples.
......
...@@ -120,6 +120,7 @@ class CrostiniManagerTest : public testing::Test { ...@@ -120,6 +120,7 @@ class CrostiniManagerTest : public testing::Test {
chromeos::DBusThreadManager::GetSetterForTesting()->SetConciergeClient( chromeos::DBusThreadManager::GetSetterForTesting()->SetConciergeClient(
base::WrapUnique(fake_concierge_client_)); base::WrapUnique(fake_concierge_client_));
chromeos::DBusThreadManager::Initialize(); chromeos::DBusThreadManager::Initialize();
crostini::CrostiniManager::GetInstance()->set_is_testing(true);
} }
~CrostiniManagerTest() override { chromeos::DBusThreadManager::Shutdown(); } ~CrostiniManagerTest() override { chromeos::DBusThreadManager::Shutdown(); }
......
...@@ -172,7 +172,7 @@ void CrostiniInstallerView::OnConciergeStarted(ConciergeClientResult result) { ...@@ -172,7 +172,7 @@ void CrostiniInstallerView::OnConciergeStarted(ConciergeClientResult result) {
} }
void CrostiniInstallerView::OnDiskImageCreated(ConciergeClientResult result) { void CrostiniInstallerView::OnDiskImageCreated(ConciergeClientResult result) {
DCHECK_EQ(state_, State::INSTALL_IMAGE_LOADER); DCHECK_EQ(state_, State::START_CONCIERGE);
state_ = State::CREATE_DISK_IMAGE; state_ = State::CREATE_DISK_IMAGE;
if (result != ConciergeClientResult::SUCCESS) { if (result != ConciergeClientResult::SUCCESS) {
LOG(ERROR) << "Failed to create disk imagewith error code: " LOG(ERROR) << "Failed to create disk imagewith error code: "
...@@ -246,6 +246,7 @@ void CrostiniInstallerView::HandleError(const base::string16& error_message, ...@@ -246,6 +246,7 @@ void CrostiniInstallerView::HandleError(const base::string16& error_message,
return; return;
RecordSetupResultHistogram(result); RecordSetupResultHistogram(result);
restart_id_ = crostini::CrostiniManager::kUninitializedRestartId;
state_ = State::ERROR; state_ = State::ERROR;
message_label_->SetVisible(true); message_label_->SetVisible(true);
message_label_->SetText(error_message); message_label_->SetText(error_message);
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h" #include "base/test/scoped_feature_list.h"
#include "chrome/browser/chromeos/crostini/crostini_manager.h"
#include "chrome/browser/chromeos/crostini/crostini_pref_names.h" #include "chrome/browser/chromeos/crostini/crostini_pref_names.h"
#include "chrome/browser/chromeos/crostini/crostini_util.h" #include "chrome/browser/chromeos/crostini/crostini_util.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
...@@ -18,6 +19,8 @@ ...@@ -18,6 +19,8 @@
#include "chrome/browser/ui/test/test_browser_dialog.h" #include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/common/chrome_features.h" #include "chrome/common/chrome_features.h"
#include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_concierge_client.h"
#include "components/crx_file/id_util.h" #include "components/crx_file/id_util.h"
#include "components/prefs/pref_service.h" #include "components/prefs/pref_service.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -25,7 +28,12 @@ ...@@ -25,7 +28,12 @@
class CrostiniInstallerViewBrowserTest : public DialogBrowserTest { class CrostiniInstallerViewBrowserTest : public DialogBrowserTest {
public: public:
CrostiniInstallerViewBrowserTest() {} CrostiniInstallerViewBrowserTest()
: fake_concierge_client_(new chromeos::FakeConciergeClient()) {
chromeos::DBusThreadManager::GetSetterForTesting()->SetConciergeClient(
base::WrapUnique(fake_concierge_client_));
crostini::CrostiniManager::GetInstance()->set_is_testing(true);
}
// DialogBrowserTest: // DialogBrowserTest:
void ShowUi(const std::string& name) override { void ShowUi(const std::string& name) override {
...@@ -55,6 +63,10 @@ class CrostiniInstallerViewBrowserTest : public DialogBrowserTest { ...@@ -55,6 +63,10 @@ class CrostiniInstallerViewBrowserTest : public DialogBrowserTest {
return ActiveView()->GetDialogClientView()->cancel_button() != nullptr; return ActiveView()->GetDialogClientView()->cancel_button() != nullptr;
} }
protected:
// Owned by chromeos::DBusThreadManager
chromeos::FakeConciergeClient* fake_concierge_client_;
private: private:
base::test::ScopedFeatureList scoped_feature_list_; base::test::ScopedFeatureList scoped_feature_list_;
...@@ -97,3 +109,25 @@ IN_PROC_BROWSER_TEST_F(CrostiniInstallerViewBrowserTest, Cancel) { ...@@ -97,3 +109,25 @@ IN_PROC_BROWSER_TEST_F(CrostiniInstallerViewBrowserTest, Cancel) {
CrostiniInstallerView::SetupResult::kNotStarted), CrostiniInstallerView::SetupResult::kNotStarted),
1); 1);
} }
IN_PROC_BROWSER_TEST_F(CrostiniInstallerViewBrowserTest, ErrorThenCancel) {
base::HistogramTester histogram_tester;
ShowUi("default");
EXPECT_NE(nullptr, ActiveView());
vm_tools::concierge::StartVmResponse response;
response.set_success(false);
fake_concierge_client_->set_start_vm_response(std::move(response));
ActiveView()->GetDialogClientView()->AcceptWindow();
EXPECT_FALSE(ActiveView()->GetWidget()->IsClosed());
base::RunLoop().RunUntilIdle();
ActiveView()->GetDialogClientView()->CancelWindow();
base::RunLoop().RunUntilIdle();
EXPECT_EQ(nullptr, ActiveView());
histogram_tester.ExpectBucketCount(
"Crostini.SetupResult",
static_cast<base::HistogramBase::Sample>(
CrostiniInstallerView::SetupResult::kErrorStartingTermina),
1);
}
...@@ -10,7 +10,9 @@ ...@@ -10,7 +10,9 @@
namespace chromeos { namespace chromeos {
FakeConciergeClient::FakeConciergeClient() {} FakeConciergeClient::FakeConciergeClient() {
InitializeProtoResponses();
}
FakeConciergeClient::~FakeConciergeClient() = default; FakeConciergeClient::~FakeConciergeClient() = default;
// ConciergeClient override. // ConciergeClient override.
...@@ -37,11 +39,9 @@ void FakeConciergeClient::CreateDiskImage( ...@@ -37,11 +39,9 @@ void FakeConciergeClient::CreateDiskImage(
const vm_tools::concierge::CreateDiskImageRequest& request, const vm_tools::concierge::CreateDiskImageRequest& request,
DBusMethodCallback<vm_tools::concierge::CreateDiskImageResponse> callback) { DBusMethodCallback<vm_tools::concierge::CreateDiskImageResponse> callback) {
create_disk_image_called_ = true; create_disk_image_called_ = true;
vm_tools::concierge::CreateDiskImageResponse response;
response.set_status(vm_tools::concierge::DISK_STATUS_CREATED);
response.set_disk_path("foo");
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE,
base::BindOnce(std::move(callback), create_disk_image_response_));
} }
void FakeConciergeClient::DestroyDiskImage( void FakeConciergeClient::DestroyDiskImage(
...@@ -49,69 +49,62 @@ void FakeConciergeClient::DestroyDiskImage( ...@@ -49,69 +49,62 @@ void FakeConciergeClient::DestroyDiskImage(
DBusMethodCallback<vm_tools::concierge::DestroyDiskImageResponse> DBusMethodCallback<vm_tools::concierge::DestroyDiskImageResponse>
callback) { callback) {
destroy_disk_image_called_ = true; destroy_disk_image_called_ = true;
vm_tools::concierge::DestroyDiskImageResponse response;
response.set_status(vm_tools::concierge::DISK_STATUS_DESTROYED);
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE,
base::BindOnce(std::move(callback), destroy_disk_image_response_));
} }
void FakeConciergeClient::ListVmDisks( void FakeConciergeClient::ListVmDisks(
const vm_tools::concierge::ListVmDisksRequest& request, const vm_tools::concierge::ListVmDisksRequest& request,
DBusMethodCallback<vm_tools::concierge::ListVmDisksResponse> callback) { DBusMethodCallback<vm_tools::concierge::ListVmDisksResponse> callback) {
list_vm_disks_called_ = true; list_vm_disks_called_ = true;
vm_tools::concierge::ListVmDisksResponse response;
response.set_success(true);
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE, base::BindOnce(std::move(callback), list_vm_disks_response_));
} }
void FakeConciergeClient::StartTerminaVm( void FakeConciergeClient::StartTerminaVm(
const vm_tools::concierge::StartVmRequest& request, const vm_tools::concierge::StartVmRequest& request,
DBusMethodCallback<vm_tools::concierge::StartVmResponse> callback) { DBusMethodCallback<vm_tools::concierge::StartVmResponse> callback) {
start_termina_vm_called_ = true; start_termina_vm_called_ = true;
vm_tools::concierge::StartVmResponse response;
response.set_success(true);
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE, base::BindOnce(std::move(callback), start_vm_response_));
} }
void FakeConciergeClient::StopVm( void FakeConciergeClient::StopVm(
const vm_tools::concierge::StopVmRequest& request, const vm_tools::concierge::StopVmRequest& request,
DBusMethodCallback<vm_tools::concierge::StopVmResponse> callback) { DBusMethodCallback<vm_tools::concierge::StopVmResponse> callback) {
stop_vm_called_ = true; stop_vm_called_ = true;
vm_tools::concierge::StopVmResponse response;
response.set_success(true);
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE, base::BindOnce(std::move(callback), stop_vm_response_));
} }
void FakeConciergeClient::StartContainer( void FakeConciergeClient::StartContainer(
const vm_tools::concierge::StartContainerRequest& request, const vm_tools::concierge::StartContainerRequest& request,
DBusMethodCallback<vm_tools::concierge::StartContainerResponse> callback) { DBusMethodCallback<vm_tools::concierge::StartContainerResponse> callback) {
start_container_called_ = true; start_container_called_ = true;
vm_tools::concierge::StartContainerResponse response;
response.set_status(vm_tools::concierge::CONTAINER_STATUS_RUNNING);
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE,
base::BindOnce(std::move(callback), start_container_response_));
} }
void FakeConciergeClient::LaunchContainerApplication( void FakeConciergeClient::LaunchContainerApplication(
const vm_tools::concierge::LaunchContainerApplicationRequest& request, const vm_tools::concierge::LaunchContainerApplicationRequest& request,
DBusMethodCallback<vm_tools::concierge::LaunchContainerApplicationResponse> DBusMethodCallback<vm_tools::concierge::LaunchContainerApplicationResponse>
callback) { callback) {
vm_tools::concierge::LaunchContainerApplicationResponse response;
response.set_success(true);
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE, base::BindOnce(std::move(callback),
launch_container_application_response_));
} }
void FakeConciergeClient::GetContainerAppIcons( void FakeConciergeClient::GetContainerAppIcons(
const vm_tools::concierge::ContainerAppIconRequest& request, const vm_tools::concierge::ContainerAppIconRequest& request,
DBusMethodCallback<vm_tools::concierge::ContainerAppIconResponse> DBusMethodCallback<vm_tools::concierge::ContainerAppIconResponse>
callback) { callback) {
vm_tools::concierge::ContainerAppIconResponse response;
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE,
base::BindOnce(std::move(callback), container_app_icon_response_));
} }
void FakeConciergeClient::WaitForServiceToBeAvailable( void FakeConciergeClient::WaitForServiceToBeAvailable(
...@@ -124,9 +117,39 @@ void FakeConciergeClient::GetContainerSshKeys( ...@@ -124,9 +117,39 @@ void FakeConciergeClient::GetContainerSshKeys(
const vm_tools::concierge::ContainerSshKeysRequest& request, const vm_tools::concierge::ContainerSshKeysRequest& request,
DBusMethodCallback<vm_tools::concierge::ContainerSshKeysResponse> DBusMethodCallback<vm_tools::concierge::ContainerSshKeysResponse>
callback) { callback) {
vm_tools::concierge::ContainerSshKeysResponse response;
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(response))); FROM_HERE,
base::BindOnce(std::move(callback), container_ssh_keys_response_));
}
void FakeConciergeClient::InitializeProtoResponses() {
create_disk_image_response_.Clear();
create_disk_image_response_.set_status(
vm_tools::concierge::DISK_STATUS_CREATED);
create_disk_image_response_.set_disk_path("foo");
destroy_disk_image_response_.Clear();
destroy_disk_image_response_.set_status(
vm_tools::concierge::DISK_STATUS_DESTROYED);
list_vm_disks_response_.Clear();
list_vm_disks_response_.set_success(true);
start_vm_response_.Clear();
start_vm_response_.set_success(true);
stop_vm_response_.Clear();
stop_vm_response_.set_success(true);
start_container_response_.Clear();
start_container_response_.set_status(
vm_tools::concierge::CONTAINER_STATUS_RUNNING);
launch_container_application_response_.Clear();
launch_container_application_response_.set_success(true);
container_app_icon_response_.Clear();
container_ssh_keys_response_.Clear();
} }
} // namespace chromeos } // namespace chromeos
...@@ -123,10 +123,56 @@ class CHROMEOS_EXPORT FakeConciergeClient : public ConciergeClient { ...@@ -123,10 +123,56 @@ class CHROMEOS_EXPORT FakeConciergeClient : public ConciergeClient {
is_container_startup_failed_signal_connected_ = connected; is_container_startup_failed_signal_connected_ = connected;
} }
void set_create_disk_image_response(
const vm_tools::concierge::CreateDiskImageResponse&
create_disk_image_response) {
create_disk_image_response_ = create_disk_image_response;
}
void set_destroy_disk_image_response(
const vm_tools::concierge::DestroyDiskImageResponse&
destroy_disk_image_response) {
destroy_disk_image_response_ = destroy_disk_image_response;
}
void set_list_vm_disks_response(
const vm_tools::concierge::ListVmDisksResponse& list_vm_disks_response) {
list_vm_disks_response_ = list_vm_disks_response;
}
void set_start_vm_response(
const vm_tools::concierge::StartVmResponse& start_vm_response) {
start_vm_response_ = start_vm_response;
}
void set_stop_vm_response(
const vm_tools::concierge::StopVmResponse& stop_vm_response) {
stop_vm_response_ = stop_vm_response;
}
void set_start_container_response(
const vm_tools::concierge::StartContainerResponse&
start_container_response) {
start_container_response_ = start_container_response;
}
void set_launch_container_application_response(
const vm_tools::concierge::LaunchContainerApplicationResponse&
launch_container_application_response) {
launch_container_application_response_ =
launch_container_application_response;
}
void set_container_app_icon_response(
const vm_tools::concierge::ContainerAppIconResponse&
container_app_icon_response) {
container_app_icon_response_ = container_app_icon_response;
}
void set_container_ssh_keys_response(
const vm_tools::concierge::ContainerSshKeysResponse&
container_ssh_keys_response) {
container_ssh_keys_response_ = container_ssh_keys_response;
}
protected: protected:
void Init(dbus::Bus* bus) override {} void Init(dbus::Bus* bus) override {}
private: private:
void InitializeProtoResponses();
bool create_disk_image_called_ = false; bool create_disk_image_called_ = false;
bool destroy_disk_image_called_ = false; bool destroy_disk_image_called_ = false;
bool list_vm_disks_called_ = false; bool list_vm_disks_called_ = false;
...@@ -135,6 +181,18 @@ class CHROMEOS_EXPORT FakeConciergeClient : public ConciergeClient { ...@@ -135,6 +181,18 @@ class CHROMEOS_EXPORT FakeConciergeClient : public ConciergeClient {
bool start_container_called_ = false; bool start_container_called_ = false;
bool is_container_started_signal_connected_ = true; bool is_container_started_signal_connected_ = true;
bool is_container_startup_failed_signal_connected_ = true; bool is_container_startup_failed_signal_connected_ = true;
vm_tools::concierge::CreateDiskImageResponse create_disk_image_response_;
vm_tools::concierge::DestroyDiskImageResponse destroy_disk_image_response_;
vm_tools::concierge::ListVmDisksResponse list_vm_disks_response_;
vm_tools::concierge::StartVmResponse start_vm_response_;
vm_tools::concierge::StopVmResponse stop_vm_response_;
vm_tools::concierge::StartContainerResponse start_container_response_;
vm_tools::concierge::LaunchContainerApplicationResponse
launch_container_application_response_;
vm_tools::concierge::ContainerAppIconResponse container_app_icon_response_;
vm_tools::concierge::ContainerSshKeysResponse container_ssh_keys_response_;
base::ObserverList<Observer> observer_list_; base::ObserverList<Observer> observer_list_;
DISALLOW_COPY_AND_ASSIGN(FakeConciergeClient); DISALLOW_COPY_AND_ASSIGN(FakeConciergeClient);
......
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