-
Notifications
You must be signed in to change notification settings - Fork 82
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add types file error_history.yaml * Add interface file error_history.yaml * Add ErrorDatabaseSQLite as subclass of error/ErrorDatabase * Implement module * Add integration tests: error history module * Add unit tests ErrorDatabaseSQlite Signed-off-by: Andreas Heinrich <[email protected]>
- Loading branch information
Showing
20 changed files
with
1,591 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
description: This interface provides access to the error history of the EVerest framework | ||
cmds: | ||
get_errors: | ||
description: Takes a list of filters and returns a list of errors | ||
arguments: | ||
filters: | ||
type: object | ||
description: Filters to apply to the list of errors | ||
$ref: /error_history#/FilterArguments | ||
result: | ||
description: List of filtered errors | ||
type: array | ||
items: | ||
$ref: /error_history#/ErrorObject |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# | ||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT | ||
# template version 3 | ||
# | ||
|
||
# module setup: | ||
# - ${MODULE_NAME}: module name | ||
ev_setup_cpp_module() | ||
|
||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 | ||
option(BUILD_TESTING "Run unit tests" OFF) | ||
set(CMAKE_PREFIX_PATH "/usr/lib/x86_64-linux-gnu" ${CMAKE_PREFIX_PATH}) | ||
|
||
find_package(SQLite3 REQUIRED) | ||
if (DISABLE_EDM) | ||
find_package(SQLiteCpp REQUIRED) | ||
endif() | ||
|
||
target_link_libraries(${MODULE_NAME} | ||
PRIVATE | ||
SQLiteCpp | ||
SQLite::SQLite3 | ||
) | ||
target_sources(${MODULE_NAME} | ||
PRIVATE | ||
"ErrorDatabaseSqlite.cpp" | ||
) | ||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1 | ||
|
||
target_sources(${MODULE_NAME} | ||
PRIVATE | ||
"error_history/error_historyImpl.cpp" | ||
) | ||
|
||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 | ||
if(BUILD_TESTING) | ||
add_subdirectory(tests) | ||
endif() | ||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Pionix GmbH and Contributors to EVerest | ||
|
||
#include "ErrorDatabaseSqlite.hpp" | ||
|
||
#include <everest/exceptions.hpp> | ||
#include <everest/logging.hpp> | ||
|
||
#include <SQLiteCpp/SQLiteCpp.h> | ||
#include <utils/date.hpp> | ||
|
||
namespace module { | ||
|
||
ErrorDatabaseSqlite::ErrorDatabaseSqlite(const fs::path& db_path_, const bool reset_) : | ||
db_path(fs::absolute(db_path_)) { | ||
BOOST_LOG_FUNCTION(); | ||
std::lock_guard<std::mutex> lock(this->db_mutex); | ||
|
||
bool reset = reset_ || !fs::exists(this->db_path); | ||
if (reset) { | ||
EVLOG_info << "Resetting database"; | ||
this->reset_database(); | ||
} else { | ||
EVLOG_info << "Using database at " << this->db_path; | ||
this->check_database(); | ||
} | ||
} | ||
|
||
void ErrorDatabaseSqlite::check_database() { | ||
BOOST_LOG_FUNCTION(); | ||
EVLOG_info << "Checking database"; | ||
std::shared_ptr<SQLite::Database> db; | ||
try { | ||
db = std::make_shared<SQLite::Database>(this->db_path.string(), SQLite::OPEN_READONLY); | ||
} catch (std::exception& e) { | ||
EVLOG_error << "Error opening database: " << e.what(); | ||
throw; | ||
} | ||
try { | ||
std::string sql = "SELECT name"; | ||
sql += " FROM sqlite_schema"; | ||
sql += " WHERE type = 'table' AND name NOT LIKE 'sqlite_%';"; | ||
SQLite::Statement stmt(*db, sql); | ||
bool has_errors_table = false; | ||
while (stmt.executeStep()) { | ||
std::string table_name = stmt.getColumn(0); | ||
if (table_name == "errors") { | ||
if (has_errors_table) { | ||
throw Everest::EverestConfigError("Database contains multiple errors tables"); | ||
} | ||
has_errors_table = true; | ||
EVLOG_debug << "Found errors table"; | ||
} else { | ||
EVLOG_warning << "Found unknown table: " << table_name; | ||
} | ||
} | ||
if (!has_errors_table) { | ||
throw Everest::EverestConfigError("Database does not contain errors table"); | ||
} | ||
} catch (std::exception& e) { | ||
EVLOG_error << "Error checking whether table 'errors' exist" << e.what(); | ||
throw; | ||
} | ||
} | ||
|
||
void ErrorDatabaseSqlite::reset_database() { | ||
BOOST_LOG_FUNCTION(); | ||
fs::path database_directory = this->db_path.parent_path(); | ||
if (!fs::exists(database_directory)) { | ||
fs::create_directories(database_directory); | ||
} | ||
if (fs::exists(this->db_path)) { | ||
fs::remove(this->db_path); | ||
} | ||
try { | ||
SQLite::Database db(this->db_path.string(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); | ||
std::string sql = "CREATE TABLE errors(" | ||
"uuid TEXT PRIMARY KEY NOT NULL," | ||
"type TEXT NOT NULL," | ||
"description TEXT NOT NULL," | ||
"message TEXT NOT NULL," | ||
"from_module TEXT NOT NULL," | ||
"from_implementation TEXT NOT NULL," | ||
"timestamp TEXT NOT NULL," | ||
"severity TEXT NOT NULL," | ||
"state TEXT NOT NULL);"; | ||
db.exec(sql); | ||
} catch (std::exception& e) { | ||
EVLOG_error << "Error creating database: " << e.what(); | ||
throw; | ||
} | ||
} | ||
|
||
void ErrorDatabaseSqlite::add_error(Everest::error::ErrorPtr error) { | ||
std::lock_guard<std::mutex> lock(this->db_mutex); | ||
this->add_error_without_mutex(error); | ||
} | ||
|
||
void ErrorDatabaseSqlite::add_error_without_mutex(Everest::error::ErrorPtr error) { | ||
BOOST_LOG_FUNCTION(); | ||
try { | ||
SQLite::Database db(this->db_path.string(), SQLite::OPEN_READWRITE); | ||
std::string sql = "INSERT INTO errors(uuid, type, description, message, from_module, from_implementation, " | ||
"timestamp, severity, state) VALUES("; | ||
sql += "?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9);"; | ||
SQLite::Statement stmt(db, sql); | ||
stmt.bind(1, error->uuid.to_string()); | ||
stmt.bind(2, error->type); | ||
stmt.bind(3, error->description); | ||
stmt.bind(4, error->message); | ||
stmt.bind(5, error->from.module_id); | ||
stmt.bind(6, error->from.implementation_id); | ||
stmt.bind(7, Everest::Date::to_rfc3339(error->timestamp)); | ||
stmt.bind(8, Everest::error::severity_to_string(error->severity)); | ||
stmt.bind(9, Everest::error::state_to_string(error->state)); | ||
stmt.exec(); | ||
} catch (std::exception& e) { | ||
EVLOG_error << "Error adding error to database: " << e.what(); | ||
throw; | ||
} | ||
} | ||
|
||
std::string ErrorDatabaseSqlite::filter_to_sql_condition(const Everest::error::ErrorFilter& filter) { | ||
std::string condition = ""; | ||
switch (filter.get_filter_type()) { | ||
case Everest::error::FilterType::State: { | ||
condition = "(state = '" + Everest::error::state_to_string(filter.get_state_filter()) + "')"; | ||
} break; | ||
case Everest::error::FilterType::Origin: { | ||
condition = "(from_module = '" + filter.get_origin_filter().module_id + "' AND " + "from_implementation = '" + | ||
filter.get_origin_filter().implementation_id + "')"; | ||
} break; | ||
case Everest::error::FilterType::Type: { | ||
condition = "(type = '" + filter.get_type_filter() + "')"; | ||
} break; | ||
case Everest::error::FilterType::Severity: { | ||
switch (filter.get_severity_filter()) { | ||
case Everest::error::SeverityFilter::LOW_GE: { | ||
condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Low) + | ||
"' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Medium) + | ||
"' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')"; | ||
} break; | ||
case Everest::error::SeverityFilter::MEDIUM_GE: { | ||
condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::Medium) + | ||
"' OR severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')"; | ||
} break; | ||
case Everest::error::SeverityFilter::HIGH_GE: { | ||
condition = "(severity = '" + Everest::error::severity_to_string(Everest::error::Severity::High) + "')"; | ||
} break; | ||
} | ||
} break; | ||
case Everest::error::FilterType::TimePeriod: { | ||
condition = "(timestamp BETWEEN '" + Everest::Date::to_rfc3339(filter.get_time_period_filter().from) + | ||
"' AND '" + Everest::Date::to_rfc3339(filter.get_time_period_filter().to) + "')"; | ||
} break; | ||
case Everest::error::FilterType::Handle: { | ||
condition = "(uuid = '" + filter.get_handle_filter().to_string() + "')"; | ||
} break; | ||
} | ||
return condition; | ||
} | ||
|
||
std::optional<std::string> | ||
ErrorDatabaseSqlite::filters_to_sql_condition(const std::list<Everest::error::ErrorFilter>& filters) { | ||
std::optional<std::string> condition = std::nullopt; | ||
if (!filters.empty()) { | ||
auto it = filters.begin(); | ||
condition = filter_to_sql_condition(*it); | ||
it++; | ||
while (it != filters.end()) { | ||
condition = condition.value() + " AND " + ErrorDatabaseSqlite::filter_to_sql_condition(*it); | ||
it++; | ||
} | ||
} | ||
return condition; | ||
} | ||
|
||
std::list<Everest::error::ErrorPtr> | ||
ErrorDatabaseSqlite::get_errors(const std::list<Everest::error::ErrorFilter>& filters) const { | ||
std::lock_guard<std::mutex> lock(this->db_mutex); | ||
return this->get_errors(ErrorDatabaseSqlite::filters_to_sql_condition(filters)); | ||
} | ||
|
||
std::list<Everest::error::ErrorPtr> ErrorDatabaseSqlite::get_errors(const std::optional<std::string>& condition) const { | ||
BOOST_LOG_FUNCTION(); | ||
std::list<Everest::error::ErrorPtr> result; | ||
try { | ||
SQLite::Database db(this->db_path.string(), SQLite::OPEN_READONLY); | ||
std::string sql = "SELECT * FROM errors"; | ||
if (condition.has_value()) { | ||
sql += " WHERE " + condition.value(); | ||
} | ||
EVLOG_debug << "Executing SQL statement: " << sql; | ||
SQLite::Statement stmt(db, sql); | ||
while (stmt.executeStep()) { | ||
const Everest::error::ErrorType err_type(stmt.getColumn("type").getText()); | ||
const std::string err_description = stmt.getColumn("description").getText(); | ||
const std::string err_msg = stmt.getColumn("message").getText(); | ||
const std::string err_from_module_id = stmt.getColumn("from_module").getText(); | ||
const std::string err_from_impl_id = stmt.getColumn("from_implementation").getText(); | ||
const ImplementationIdentifier err_from(err_from_module_id, err_from_impl_id); | ||
const Everest::error::Error::time_point err_timestamp = | ||
Everest::Date::from_rfc3339(stmt.getColumn("timestamp").getText()); | ||
const Everest::error::Severity err_severity = | ||
Everest::error::string_to_severity(stmt.getColumn("severity").getText()); | ||
const Everest::error::State err_state = Everest::error::string_to_state(stmt.getColumn("state").getText()); | ||
const Everest::error::ErrorHandle err_handle(Everest::error::ErrorHandle(stmt.getColumn("uuid").getText())); | ||
Everest::error::ErrorPtr error = std::make_shared<Everest::error::Error>( | ||
err_type, err_msg, err_description, err_from, err_severity, err_timestamp, err_handle, err_state); | ||
result.push_back(error); | ||
} | ||
} catch (std::exception& e) { | ||
EVLOG_error << "Error getting errors from database: " << e.what(); | ||
throw; | ||
} | ||
return result; | ||
} | ||
|
||
std::list<Everest::error::ErrorPtr> | ||
ErrorDatabaseSqlite::edit_errors(const std::list<Everest::error::ErrorFilter>& filters, EditErrorFunc edit_func) { | ||
std::lock_guard<std::mutex> lock(this->db_mutex); | ||
std::list<Everest::error::ErrorPtr> result = this->remove_errors_without_mutex(filters); | ||
for (Everest::error::ErrorPtr& error : result) { | ||
edit_func(error); | ||
this->add_error_without_mutex(error); | ||
} | ||
return result; | ||
} | ||
|
||
std::list<Everest::error::ErrorPtr> | ||
ErrorDatabaseSqlite::remove_errors(const std::list<Everest::error::ErrorFilter>& filters) { | ||
std::lock_guard<std::mutex> lock(this->db_mutex); | ||
return this->remove_errors_without_mutex(filters); | ||
} | ||
|
||
std::list<Everest::error::ErrorPtr> | ||
ErrorDatabaseSqlite::remove_errors_without_mutex(const std::list<Everest::error::ErrorFilter>& filters) { | ||
BOOST_LOG_FUNCTION(); | ||
std::optional<std::string> condition = ErrorDatabaseSqlite::filters_to_sql_condition(filters); | ||
std::list<Everest::error::ErrorPtr> result = this->get_errors(condition); | ||
try { | ||
SQLite::Database db(this->db_path.string(), SQLite::OPEN_READWRITE); | ||
std::string sql = "DELETE FROM errors"; | ||
if (condition.has_value()) { | ||
sql += " WHERE " + condition.value(); | ||
} | ||
db.exec(sql); | ||
} catch (std::exception& e) { | ||
EVLOG_error << "Error removing errors from database: " << e.what(); | ||
throw; | ||
} | ||
return result; | ||
} | ||
|
||
} // namespace module |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Pionix GmbH and Contributors to EVerest | ||
|
||
#ifndef ERROR_HISTORY_ERROR_DATABASE_SQLITE_HPP | ||
#define ERROR_HISTORY_ERROR_DATABASE_SQLITE_HPP | ||
|
||
#include <utils/error/error_database.hpp> | ||
|
||
#include <filesystem> | ||
|
||
namespace fs = std::filesystem; | ||
|
||
namespace module { | ||
|
||
class ErrorDatabaseSqlite : public Everest::error::ErrorDatabase { | ||
public: | ||
explicit ErrorDatabaseSqlite(const fs::path& db_path_, const bool reset_ = false); | ||
|
||
void add_error(Everest::error::ErrorPtr error) override; | ||
std::list<Everest::error::ErrorPtr> | ||
get_errors(const std::list<Everest::error::ErrorFilter>& filters) const override; | ||
std::list<Everest::error::ErrorPtr> edit_errors(const std::list<Everest::error::ErrorFilter>& filters, | ||
EditErrorFunc edit_func) override; | ||
std::list<Everest::error::ErrorPtr> remove_errors(const std::list<Everest::error::ErrorFilter>& filters) override; | ||
|
||
private: | ||
void add_error_without_mutex(Everest::error::ErrorPtr error); | ||
std::list<Everest::error::ErrorPtr> | ||
remove_errors_without_mutex(const std::list<Everest::error::ErrorFilter>& filters); | ||
std::list<Everest::error::ErrorPtr> get_errors(const std::optional<std::string>& condition) const; | ||
static std::string filter_to_sql_condition(const Everest::error::ErrorFilter& filter); | ||
static std::optional<std::string> filters_to_sql_condition(const std::list<Everest::error::ErrorFilter>& filters); | ||
|
||
void reset_database(); | ||
void check_database(); | ||
const fs::path db_path; | ||
mutable std::mutex db_mutex; | ||
}; | ||
|
||
} // namespace module | ||
|
||
#endif // ERROR_HISTORY_ERROR_DATABASE_SQLITE_HPP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright Pionix GmbH and Contributors to EVerest | ||
#include "ErrorHistory.hpp" | ||
|
||
namespace module { | ||
|
||
void ErrorHistory::init() { | ||
invoke_init(*p_error_history); | ||
} | ||
|
||
void ErrorHistory::ready() { | ||
invoke_ready(*p_error_history); | ||
} | ||
|
||
} // namespace module |
Oops, something went wrong.