From 1994260a2ce7660e88974629ff83d4b682667fb5 Mon Sep 17 00:00:00 2001 From: Pavel Kirienko Date: Mon, 4 May 2015 16:44:04 +0300 Subject: [PATCH] Persistent storage implementation and tests --- .../dynamic_node_id_allocation_server.hpp | 7 +- .../uc_dynamic_node_id_allocation_server.cpp | 88 +++++++-- .../dynamic_node_id_allocation_server.cpp | 184 ++++++++++++++++++ 3 files changed, 266 insertions(+), 13 deletions(-) diff --git a/libuavcan/include/uavcan/protocol/dynamic_node_id_allocation_server.hpp b/libuavcan/include/uavcan/protocol/dynamic_node_id_allocation_server.hpp index 48e4be3709..cdd9d6c1bb 100644 --- a/libuavcan/include/uavcan/protocol/dynamic_node_id_allocation_server.hpp +++ b/libuavcan/include/uavcan/protocol/dynamic_node_id_allocation_server.hpp @@ -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(); diff --git a/libuavcan/src/protocol/uc_dynamic_node_id_allocation_server.cpp b/libuavcan/src/protocol/uc_dynamic_node_id_allocation_server.cpp index bf6bb13b11..68be3636f4 100644 --- a/libuavcan/src/protocol/uc_dynamic_node_id_allocation_server.cpp +++ b/libuavcan/src/protocol/uc_dynamic_node_id_allocation_server.cpp @@ -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(); diff --git a/libuavcan/test/protocol/dynamic_node_id_allocation_server.cpp b/libuavcan/test/protocol/dynamic_node_id_allocation_server.cpp index bc6bd0b6ef..4dfffa1ddf 100644 --- a/libuavcan/test/protocol/dynamic_node_id_allocation_server.cpp +++ b/libuavcan/test/protocol/dynamic_node_id_allocation_server.cpp @@ -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 +}