Persistent storage implementation and tests

This commit is contained in:
Pavel Kirienko
2015-05-04 16:44:04 +03:00
parent 6a8135fedf
commit 1994260a2c
3 changed files with 266 additions and 13 deletions
@@ -152,7 +152,12 @@ public:
{ }
/**
* This method invokes storage IO.
* Initialization is performed as follows (every step may fail and return an error):
* 1. Log is restored or initialized.
* 2. Current term is restored. If there was no current term stored and the log is empty, it will be initialized
* with zero.
* 3. VotedFor value is restored. If there was no VotedFor value stored, the log is empty, and the current term is
* zero, the value will be initialized with zero.
*/
int init();
@@ -269,6 +269,7 @@ int Log::init()
else
{
// There's some data in the storage, but it cannot be parsed - reporting an error
UAVCAN_TRACE("dynamic_node_id_server_impl::Log", "Failed to read last index");
return -ErrFailure;
}
}
@@ -285,6 +286,8 @@ int Log::init()
const int result = readEntryFromStorage(index, entries_[index]);
if (result < 0)
{
UAVCAN_TRACE("dynamic_node_id_server_impl::Log", "Failed to read entry at index %u: %d",
unsigned(index), result);
return result;
}
}
@@ -378,10 +381,13 @@ bool Log::isOtherLogUpToDate(Log::Index other_last_index, Term other_last_term)
*/
int PersistentState::init()
{
// Reading log
/*
* Reading log
*/
int res = log_.init();
if (res < 0)
{
UAVCAN_TRACE("dynamic_node_id_server_impl::PersistentState", "Log init failed: %d", res);
return res;
}
@@ -392,13 +398,37 @@ int PersistentState::init()
return -ErrLogic;
}
const bool log_is_empty = (log_.getLastIndex() == 0) && (last_entry->term == 0);
MarshallingStorageDecorator io(storage_);
// Reading current term
res = io.get(getCurrentTermKey(), current_term_);
if (res < 0)
/*
* Reading currentTerm
*/
if (storage_.get(getCurrentTermKey()).empty() && log_is_empty)
{
return res;
// First initialization
current_term_ = 0;
res = io.setAndGetBack(getCurrentTermKey(), current_term_);
if (res < 0)
{
UAVCAN_TRACE("dynamic_node_id_server_impl::PersistentState", "Failed to init current term: %d", res);
return res;
}
if (current_term_ != 0)
{
return -ErrFailure;
}
}
else
{
// Restoring
res = io.get(getCurrentTermKey(), current_term_);
if (res < 0)
{
UAVCAN_TRACE("dynamic_node_id_server_impl::PersistentState", "Failed to read current term: %d", res);
return res;
}
}
if (current_term_ < last_entry->term)
@@ -409,15 +439,39 @@ int PersistentState::init()
return -ErrLogic;
}
// Reading voted for
uint32_t stored_voted_for = 0;
res = io.get(getVotedForKey(), stored_voted_for);
if ((res < 0) || (stored_voted_for > NodeID::Max))
/*
* Reading votedFor
*/
if (storage_.get(getVotedForKey()).empty() && log_is_empty && (current_term_ == 0))
{
return -ErrFailure;
// First initialization
voted_for_ = NodeID(0);
uint32_t stored_voted_for = 0;
res = io.setAndGetBack(getVotedForKey(), stored_voted_for);
if (res < 0)
{
UAVCAN_TRACE("dynamic_node_id_server_impl::PersistentState", "Failed to init votedFor: %d", res);
return res;
}
if (stored_voted_for != 0)
{
return -ErrFailure;
}
}
else
{
// Restoring
uint32_t stored_voted_for = 0;
res = io.get(getVotedForKey(), stored_voted_for);
if (res < 0)
{
UAVCAN_TRACE("dynamic_node_id_server_impl::PersistentState", "Failed to read votedFor: %d", res);
return res;
}
if (stored_voted_for > NodeID::Max)
{
return -ErrFailure;
}
voted_for_ = NodeID(uint8_t(stored_voted_for));
}
@@ -426,7 +480,12 @@ int PersistentState::init()
int PersistentState::setCurrentTerm(const Term term)
{
UAVCAN_ASSERT(current_term_ <= term);
if (term < current_term_)
{
UAVCAN_ASSERT(0);
return -ErrInvalidParam;
}
MarshallingStorageDecorator io(storage_);
Term tmp = term;
@@ -447,7 +506,12 @@ int PersistentState::setCurrentTerm(const Term term)
int PersistentState::setVotedFor(const NodeID node_id)
{
voted_for_ = node_id;
if (!node_id.isValid())
{
UAVCAN_ASSERT(0);
return -ErrInvalidParam;
}
MarshallingStorageDecorator io(storage_);
uint32_t tmp = node_id.get();
@@ -355,3 +355,187 @@ TEST(DynamicNodeIDAllocationServer, LogRemove)
storage.print();
}
TEST(DynamicNodeIDAllocationServer, PersistentStorageInitialization)
{
/*
* First initialization
*/
{
StorageBackend storage;
uavcan::dynamic_node_id_server_impl::PersistentState pers(storage);
ASSERT_EQ(0, storage.getNumKeys());
ASSERT_LE(0, pers.init());
ASSERT_LE(3, storage.getNumKeys());
ASSERT_EQ("0", storage.get("log_last_index"));
ASSERT_EQ("0", storage.get("current_term"));
ASSERT_EQ("0", storage.get("voted_for"));
}
/*
* Partial recovery - only empty log is recovered
*/
{
StorageBackend storage;
{
// This log is used to initialize the storage
uavcan::dynamic_node_id_server_impl::Log log(storage);
ASSERT_LE(0, log.init());
}
ASSERT_LE(1, storage.getNumKeys());
uavcan::dynamic_node_id_server_impl::PersistentState pers(storage);
ASSERT_LE(0, pers.init());
ASSERT_LE(3, storage.getNumKeys());
ASSERT_EQ("0", storage.get("log_last_index"));
ASSERT_EQ("0", storage.get("current_term"));
ASSERT_EQ("0", storage.get("voted_for"));
}
/*
* Partial recovery - log and current term are recovered
*/
{
StorageBackend storage;
{
// This log is used to initialize the storage
uavcan::dynamic_node_id_server_impl::Log log(storage);
ASSERT_LE(0, log.init());
}
ASSERT_LE(1, storage.getNumKeys());
storage.set("current_term", "1");
uavcan::dynamic_node_id_server_impl::PersistentState pers(storage);
ASSERT_GT(0, pers.init()); // Fails because current term is not zero
storage.set("current_term", "0");
ASSERT_LE(0, pers.init()); // OK now
ASSERT_LE(3, storage.getNumKeys());
ASSERT_EQ("0", storage.get("log_last_index"));
ASSERT_EQ("0", storage.get("current_term"));
ASSERT_EQ("0", storage.get("voted_for"));
}
/*
* Full recovery
*/
{
StorageBackend storage;
{
// This log is used to initialize the storage
uavcan::dynamic_node_id_server_impl::Log log(storage);
ASSERT_LE(0, log.init());
uavcan::protocol::dynamic_node_id::server::Entry entry;
entry.term = 1;
entry.node_id = 1;
entry.unique_id[0] = 1;
ASSERT_LE(0, log.append(entry));
}
ASSERT_LE(4, storage.getNumKeys());
uavcan::dynamic_node_id_server_impl::PersistentState pers(storage);
ASSERT_GT(0, pers.init()); // Fails because log is not empty
storage.set("current_term", "0");
storage.set("voted_for", "0");
ASSERT_GT(0, pers.init()); // Fails because of bad currentTerm
storage.set("current_term", "1"); // OK
storage.set("voted_for", "128"); // Invalid value
ASSERT_GT(0, pers.init()); // Fails because of bad votedFor
storage.set("voted_for", "0"); // OK now
ASSERT_LE(0, pers.init());
ASSERT_LE(3, storage.getNumKeys());
ASSERT_EQ("1", storage.get("log_last_index"));
ASSERT_EQ("1", storage.get("current_term"));
ASSERT_EQ("0", storage.get("voted_for"));
}
}
TEST(DynamicNodeIDAllocationServer, PersistentStorage)
{
StorageBackend storage;
uavcan::dynamic_node_id_server_impl::PersistentState pers(storage);
/*
* Initializing
*/
ASSERT_LE(0, pers.init());
ASSERT_EQ("0", storage.get("log_last_index"));
ASSERT_EQ("0", storage.get("current_term"));
ASSERT_EQ("0", storage.get("voted_for"));
/*
* Inserting some log entries
*/
uavcan::protocol::dynamic_node_id::server::Entry entry;
entry.term = 1;
entry.node_id = 1;
entry.unique_id[0] = 1;
ASSERT_LE(0, pers.getLog().append(entry));
ASSERT_EQ("1", storage.get("log_last_index"));
ASSERT_EQ("0", storage.get("current_term"));
ASSERT_EQ("0", storage.get("voted_for"));
/*
* Changing current term
*/
ASSERT_EQ(0, pers.getCurrentTerm());
ASSERT_LE(0, pers.setCurrentTerm(2));
ASSERT_EQ(2, pers.getCurrentTerm());
ASSERT_EQ("1", storage.get("log_last_index"));
ASSERT_EQ("2", storage.get("current_term"));
ASSERT_EQ("0", storage.get("voted_for"));
/*
* Changing votedFor
*/
ASSERT_EQ(0, pers.getVotedFor().get());
ASSERT_LE(0, pers.setVotedFor(0));
ASSERT_EQ(0, pers.getVotedFor().get());
ASSERT_LE(0, pers.setVotedFor(45));
ASSERT_EQ(45, pers.getVotedFor().get());
ASSERT_EQ("1", storage.get("log_last_index"));
ASSERT_EQ("2", storage.get("current_term"));
ASSERT_EQ("45", storage.get("voted_for"));
/*
* Handling errors
*/
storage.failOnSetCalls(true);
ASSERT_EQ(2, pers.getCurrentTerm());
ASSERT_GT(0, pers.setCurrentTerm(7893));
ASSERT_EQ(2, pers.getCurrentTerm());
ASSERT_EQ(45, pers.getVotedFor().get());
ASSERT_GT(0, pers.setVotedFor(78));
ASSERT_EQ(45, pers.getVotedFor().get());
ASSERT_EQ("1", storage.get("log_last_index"));
ASSERT_EQ("2", storage.get("current_term"));
ASSERT_EQ("45", storage.get("voted_for"));
/*
* Final checks
*/
ASSERT_GT(10, storage.getNumKeys()); // Making sure there's some sane number of keys in the storage
}