Commit bcf45bcb authored by David Trainor's avatar David Trainor Committed by Commit Bot

Add recovery method to the Model

Add the way for the DownloadService Model to attempt a hard recovery.
This will be used along with other component CLs to allow the
DownloadService to attempt to reset itself.

The recovery method for the Model happens via the following steps:
1. Clears the Model of all Entries.
2. Has the Store destroy itself.
3. The Store attempts to re-initialize.
4. Return the result back to the Model::Client.

BUG=736222

Change-Id: I4e0a256d0b241ef627d457d9c2276691d61da8ce
Reviewed-on: https://chromium-review.googlesource.com/568712
Commit-Queue: David Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarTommy Nyquist <nyquist@chromium.org>
Reviewed-by: default avatarXing Liu <xingliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#487160}
parent e42561bd
......@@ -395,6 +395,10 @@ void ControllerImpl::OnModelReady(bool success) {
AttemptToFinalizeSetup();
}
void ControllerImpl::OnHardRecoverComplete(bool success) {
// TODO(dtrainor): Support recovery.
}
void ControllerImpl::OnItemAdded(bool success,
DownloadClient client,
const std::string& guid) {
......
......@@ -80,6 +80,7 @@ class ControllerImpl : public Controller,
// Model::Client implementation.
void OnModelReady(bool success) override;
void OnHardRecoverComplete(bool success) override;
void OnItemAdded(bool success,
DownloadClient client,
const std::string& guid) override;
......
......@@ -45,6 +45,12 @@ void DownloadStore::Initialize(InitCallback callback) {
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void DownloadStore::HardRecover(StoreCallback callback) {
is_initialized_ = false;
db_->Destroy(base::BindOnce(&DownloadStore::OnDatabaseDestroyed,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void DownloadStore::OnDatabaseInited(InitCallback callback, bool success) {
if (!success) {
std::move(callback).Run(success, base::MakeUnique<std::vector<Entry>>());
......@@ -69,6 +75,24 @@ void DownloadStore::OnDatabaseLoaded(InitCallback callback,
std::move(callback).Run(success, std::move(entries));
}
void DownloadStore::OnDatabaseDestroyed(StoreCallback callback, bool success) {
if (!success) {
std::move(callback).Run(success);
return;
}
db_->InitWithOptions(
kDatabaseClientName, leveldb_proto::Options(database_dir_),
base::BindOnce(&DownloadStore::OnDatabaseInitedAfterDestroy,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void DownloadStore::OnDatabaseInitedAfterDestroy(StoreCallback callback,
bool success) {
is_initialized_ = success;
std::move(callback).Run(success);
}
void DownloadStore::Update(const Entry& entry, StoreCallback callback) {
DCHECK(IsInitialized());
auto entries_to_save = base::MakeUnique<KeyProtoEntryVector>();
......
......@@ -34,6 +34,7 @@ class DownloadStore : public Store {
// Store implementation.
bool IsInitialized() override;
void Initialize(InitCallback callback) override;
void HardRecover(StoreCallback callback) override;
void Update(const Entry& entry, StoreCallback callback) override;
void Remove(const std::string& guid, StoreCallback callback) override;
......@@ -42,6 +43,8 @@ class DownloadStore : public Store {
void OnDatabaseLoaded(InitCallback callback,
bool success,
std::unique_ptr<std::vector<protodb::Entry>> protos);
void OnDatabaseDestroyed(StoreCallback callback, bool success);
void OnDatabaseInitedAfterDestroy(StoreCallback callback, bool success);
std::unique_ptr<leveldb_proto::ProtoDatabase<protodb::Entry>> db_;
base::FilePath database_dir_;
......
......@@ -10,6 +10,7 @@
#include "base/callback.h"
#include "base/guid.h"
#include "base/memory/ptr_util.h"
#include "base/optional.h"
#include "components/download/internal/entry.h"
#include "components/download/internal/proto/entry.pb.h"
#include "components/download/internal/proto_conversions.h"
......@@ -48,6 +49,8 @@ class DownloadStoreTest : public testing::Test {
loaded_entries->swap(*entries);
}
void RecoverCallback(bool success) { hard_recover_result_ = success; }
MOCK_METHOD1(StoreCallback, void(bool));
void PrepopulateSampleEntries() {
......@@ -63,6 +66,7 @@ class DownloadStoreTest : public testing::Test {
std::map<std::string, protodb::Entry> db_entries_;
leveldb_proto::test::FakeDB<protodb::Entry>* db_;
std::unique_ptr<DownloadStore> store_;
base::Optional<bool> hard_recover_result_;
DISALLOW_COPY_AND_ASSIGN(DownloadStoreTest);
};
......@@ -82,6 +86,86 @@ TEST_F(DownloadStoreTest, Initialize) {
ASSERT_EQ(2u, preloaded_entries.size());
}
TEST_F(DownloadStoreTest, HardRecover) {
PrepopulateSampleEntries();
CreateDatabase();
ASSERT_FALSE(store_->IsInitialized());
std::vector<Entry> preloaded_entries;
store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
base::Unretained(this), &preloaded_entries));
db_->InitCallback(true);
db_->LoadCallback(true);
ASSERT_TRUE(store_->IsInitialized());
ASSERT_EQ(2u, preloaded_entries.size());
store_->HardRecover(
base::Bind(&DownloadStoreTest::RecoverCallback, base::Unretained(this)));
ASSERT_FALSE(store_->IsInitialized());
db_->DestroyCallback(true);
db_->InitCallback(true);
ASSERT_TRUE(store_->IsInitialized());
ASSERT_TRUE(hard_recover_result_.has_value());
ASSERT_TRUE(hard_recover_result_.value());
}
TEST_F(DownloadStoreTest, HardRecoverDestroyFails) {
PrepopulateSampleEntries();
CreateDatabase();
ASSERT_FALSE(store_->IsInitialized());
std::vector<Entry> preloaded_entries;
store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
base::Unretained(this), &preloaded_entries));
db_->InitCallback(true);
db_->LoadCallback(true);
ASSERT_TRUE(store_->IsInitialized());
ASSERT_EQ(2u, preloaded_entries.size());
store_->HardRecover(
base::Bind(&DownloadStoreTest::RecoverCallback, base::Unretained(this)));
ASSERT_FALSE(store_->IsInitialized());
db_->DestroyCallback(false);
ASSERT_FALSE(store_->IsInitialized());
ASSERT_TRUE(hard_recover_result_.has_value());
ASSERT_FALSE(hard_recover_result_.value());
}
TEST_F(DownloadStoreTest, HardRecoverInitFails) {
PrepopulateSampleEntries();
CreateDatabase();
ASSERT_FALSE(store_->IsInitialized());
std::vector<Entry> preloaded_entries;
store_->Initialize(base::Bind(&DownloadStoreTest::InitCallback,
base::Unretained(this), &preloaded_entries));
db_->InitCallback(true);
db_->LoadCallback(true);
ASSERT_TRUE(store_->IsInitialized());
ASSERT_EQ(2u, preloaded_entries.size());
store_->HardRecover(
base::Bind(&DownloadStoreTest::RecoverCallback, base::Unretained(this)));
ASSERT_FALSE(store_->IsInitialized());
db_->DestroyCallback(true);
db_->InitCallback(false);
ASSERT_FALSE(store_->IsInitialized());
ASSERT_TRUE(hard_recover_result_.has_value());
ASSERT_FALSE(hard_recover_result_.value());
}
TEST_F(DownloadStoreTest, Update) {
PrepopulateSampleEntries();
CreateDatabase();
......
......@@ -32,6 +32,11 @@ class Model {
// callback. If |success| is true it can be accessed now.
virtual void OnModelReady(bool success) = 0;
// Called asynchronously in response to a Model::HardRecover call. If
// |success| is |false|, recovery of the Model and/or the underlying Store
// failed. After this call there should be no entries stored in this Model.
virtual void OnHardRecoverComplete(bool success) = 0;
// Called when an Entry addition is complete. |success| determines whether
// or not the entry has been successfully persisted to the Store.
virtual void OnItemAdded(bool success,
......@@ -59,6 +64,10 @@ class Model {
// The Model can be used after that call.
virtual void Initialize(Client* client) = 0;
// Deletes and attempts to re-initialize the Store.
// Client::OnHardRecoveryComplete() will be called in response asynchronously.
virtual void HardRecover() = 0;
// Adds |entry| to this Model and attempts to write |entry| to the Store.
// Client::OnItemAdded() will be called in response asynchronously.
virtual void Add(const Entry& entry) = 0;
......
......@@ -28,6 +28,13 @@ void ModelImpl::Initialize(Client* client) {
weak_ptr_factory_.GetWeakPtr()));
}
void ModelImpl::HardRecover() {
entries_.clear();
store_->HardRecover(base::BindOnce(&ModelImpl::OnHardRecoverFinished,
weak_ptr_factory_.GetWeakPtr()));
}
void ModelImpl::Add(const Entry& entry) {
DCHECK(store_->IsInitialized());
DCHECK(entries_.find(entry.guid) == entries_.end());
......@@ -96,6 +103,10 @@ void ModelImpl::OnInitializedFinished(
client_->OnModelReady(true);
}
void ModelImpl::OnHardRecoverFinished(bool success) {
client_->OnHardRecoverComplete(success);
}
void ModelImpl::OnAddFinished(DownloadClient client,
const std::string& guid,
bool success) {
......
......@@ -28,6 +28,7 @@ class ModelImpl : public Model {
// Model implementation.
void Initialize(Client* client) override;
void HardRecover() override;
void Add(const Entry& entry) override;
void Update(const Entry& entry) override;
void Remove(const std::string& guid) override;
......@@ -39,6 +40,7 @@ class ModelImpl : public Model {
void OnInitializedFinished(bool success,
std::unique_ptr<std::vector<Entry>> entries);
void OnHardRecoverFinished(bool success);
void OnAddFinished(DownloadClient client,
const std::string& guid,
bool success);
......
......@@ -82,6 +82,70 @@ TEST_F(DownloadServiceModelImplTest, BadInit) {
store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
}
TEST_F(DownloadServiceModelImplTest, HardRecoverGoodModel) {
Entry entry1 = test::BuildBasicEntry();
Entry entry2 = test::BuildBasicEntry();
std::vector<Entry> entries = {entry1, entry2};
EXPECT_CALL(client_, OnModelReady(true)).Times(1);
model_->Initialize(&client_);
EXPECT_TRUE(store_->init_called());
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
EXPECT_CALL(client_, OnHardRecoverComplete(true));
model_->HardRecover();
store_->TriggerHardRecover(true);
EXPECT_TRUE(model_->PeekEntries().empty());
}
TEST_F(DownloadServiceModelImplTest, HardRecoverBadModel) {
EXPECT_CALL(client_, OnModelReady(false)).Times(1);
model_->Initialize(&client_);
EXPECT_TRUE(store_->init_called());
store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
EXPECT_CALL(client_, OnHardRecoverComplete(true));
model_->HardRecover();
store_->TriggerHardRecover(true);
EXPECT_TRUE(model_->PeekEntries().empty());
}
TEST_F(DownloadServiceModelImplTest, HardRecoverFailsGoodModel) {
Entry entry1 = test::BuildBasicEntry();
Entry entry2 = test::BuildBasicEntry();
std::vector<Entry> entries = {entry1, entry2};
EXPECT_CALL(client_, OnModelReady(true)).Times(1);
model_->Initialize(&client_);
EXPECT_TRUE(store_->init_called());
store_->TriggerInit(true, base::MakeUnique<std::vector<Entry>>(entries));
EXPECT_CALL(client_, OnHardRecoverComplete(false));
model_->HardRecover();
store_->TriggerHardRecover(false);
EXPECT_TRUE(model_->PeekEntries().empty());
}
TEST_F(DownloadServiceModelImplTest, HardRecoverFailsBadModel) {
EXPECT_CALL(client_, OnModelReady(false)).Times(1);
model_->Initialize(&client_);
EXPECT_TRUE(store_->init_called());
store_->TriggerInit(false, base::MakeUnique<std::vector<Entry>>());
EXPECT_CALL(client_, OnHardRecoverComplete(false));
model_->HardRecover();
store_->TriggerHardRecover(false);
EXPECT_TRUE(model_->PeekEntries().empty());
}
TEST_F(DownloadServiceModelImplTest, Add) {
Entry entry1 = test::BuildBasicEntry();
Entry entry2 = test::BuildBasicEntry();
......
......@@ -34,6 +34,9 @@ class Store {
// Store.
virtual void Initialize(InitCallback callback) = 0;
// Destroys the underlying store and attempts to re-initialize.
virtual void HardRecover(StoreCallback callback) = 0;
// Adds or updates |entry| in this Store asynchronously and returns whether or
// not that was successful.
virtual void Update(const Entry& entry, StoreCallback callback) = 0;
......
......@@ -20,6 +20,7 @@ class MockModelClient : public Model::Client {
// Model::Client implementation.
MOCK_METHOD1(OnModelReady, void(bool));
MOCK_METHOD1(OnHardRecoverComplete, void(bool));
MOCK_METHOD3(OnItemAdded, void(bool, DownloadClient, const std::string&));
MOCK_METHOD3(OnItemUpdated, void(bool, DownloadClient, const std::string&));
MOCK_METHOD3(OnItemRemoved, void(bool, DownloadClient, const std::string&));
......
......@@ -27,6 +27,12 @@ void NoopStore::Initialize(InitCallback callback) {
std::move(callback)));
}
void NoopStore::HardRecover(StoreCallback callback) {
initialized_ = true;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true));
}
void NoopStore::Update(const Entry& entry, StoreCallback callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true /** success */));
......
......@@ -25,6 +25,7 @@ class NoopStore : public Store {
// Store implementation.
bool IsInitialized() override;
void Initialize(InitCallback callback) override;
void HardRecover(StoreCallback callback) override;
void Update(const Entry& entry, StoreCallback callback) override;
void Remove(const std::string& guid, StoreCallback callback) override;
......
......@@ -27,6 +27,13 @@ void TestStore::Initialize(InitCallback callback) {
base::MakeUnique<std::vector<Entry>>());
}
void TestStore::HardRecover(StoreCallback callback) {
hard_recover_callback_ = std::move(callback);
if (automatic_callback_response_.has_value())
TriggerHardRecover(automatic_callback_response_.value());
}
void TestStore::Update(const Entry& entry, StoreCallback callback) {
updated_entries_.push_back(entry);
update_callback_ = std::move(callback);
......@@ -54,6 +61,11 @@ void TestStore::TriggerInit(bool success,
std::move(init_callback_).Run(success, std::move(entries));
}
void TestStore::TriggerHardRecover(bool success) {
DCHECK(hard_recover_callback_);
std::move(hard_recover_callback_).Run(success);
}
void TestStore::TriggerUpdate(bool success) {
DCHECK(update_callback_);
std::move(update_callback_).Run(success);
......
......@@ -27,12 +27,14 @@ class TestStore : public Store {
// Store implementation.
bool IsInitialized() override;
void Initialize(InitCallback callback) override;
void HardRecover(StoreCallback callback) override;
void Update(const Entry& entry, StoreCallback callback) override;
void Remove(const std::string& guid, StoreCallback callback) override;
// Callback trigger methods.
void AutomaticallyTriggerAllFutureCallbacks(bool success);
void TriggerInit(bool success, std::unique_ptr<std::vector<Entry>> entries);
void TriggerHardRecover(bool success);
void TriggerUpdate(bool success);
void TriggerRemove(bool success);
......@@ -55,6 +57,7 @@ class TestStore : public Store {
base::Optional<bool> automatic_callback_response_;
InitCallback init_callback_;
StoreCallback hard_recover_callback_;
StoreCallback update_callback_;
StoreCallback remove_callback_;
......
......@@ -57,6 +57,8 @@ class FakeDB : public ProtoDatabase<T> {
void UpdateCallback(bool success);
void DestroyCallback(bool success);
static base::FilePath DirectoryForTestDB();
private:
......@@ -81,6 +83,7 @@ class FakeDB : public ProtoDatabase<T> {
Callback load_keys_callback_;
Callback get_callback_;
Callback update_callback_;
Callback destroy_callback_;
};
template <typename T>
......@@ -147,7 +150,10 @@ void FakeDB<T>::GetEntry(const std::string& key,
}
template <typename T>
void FakeDB<T>::Destroy(typename ProtoDatabase<T>::DestroyCallback callback) {}
void FakeDB<T>::Destroy(typename ProtoDatabase<T>::DestroyCallback callback) {
db_->clear();
destroy_callback_ = std::move(callback);
}
template <typename T>
base::FilePath& FakeDB<T>::GetDirectory() {
......@@ -179,6 +185,11 @@ void FakeDB<T>::UpdateCallback(bool success) {
std::move(update_callback_).Run(success);
}
template <typename T>
void FakeDB<T>::DestroyCallback(bool success) {
std::move(destroy_callback_).Run(success);
}
// static
template <typename T>
void FakeDB<T>::RunLoadCallback(
......
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