-
Notifications
You must be signed in to change notification settings - Fork 92
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/add error history module (#447)
* Add module ErrorHistory * 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]> * use branches in dependencies.yaml * use build-kit-alpine tag feature/refactor-error-classes Signed-off-by: Andreas Heinrich <[email protected]> * Bum ev-cli version Signed-off-by: Andreas Heinrich <[email protected]> * Move error.hpp back Signed-off-by: Andreas Heinrich <[email protected]> * Use latest build-kit Signed-off-by: Andreas Heinrich <[email protected]> * use 0.0.24 again Signed-off-by: Andreas Heinrich <[email protected]> * Add ev_define_dependency for module ErrorHistory Signed-off-by: Kai-Uwe Hermann <[email protected]> * Make sqlite_cpp dependency conditional Signed-off-by: Kai-Uwe Hermann <[email protected]> * Skip all configs that start with "config-test-" in integration tests These are tested with different tests Signed-off-by: Kai-Uwe Hermann <[email protected]> * Rename test target Signed-off-by: Andreas Heinrich <[email protected]> * Fix cmaken condition Signed-off-by: Andreas Heinrich <[email protected]> * Update modules/ErrorHistory/tests/CMakeLists.txt Signed-off-by: Andreas Heinrich <[email protected]> --------- Signed-off-by: Andreas Heinrich <[email protected]> Signed-off-by: Kai-Uwe Hermann <[email protected]> Co-authored-by: Kai-Uwe Hermann <[email protected]>
- Loading branch information
1 parent
a5e4173
commit d90d925
Showing
21 changed files
with
1,596 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
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(EVEREST_CORE_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 |
Oops, something went wrong.