Skip to content

Commit

Permalink
Add module ErrorHistory
Browse files Browse the repository at this point in the history
* 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
andistorm committed Dec 6, 2023
1 parent 7b1e846 commit af1af57
Show file tree
Hide file tree
Showing 20 changed files with 1,591 additions and 1 deletion.
7 changes: 7 additions & 0 deletions dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,10 @@ everest-utils:
libevse-security:
git: https://github.com/EVerest/libevse-security.git
git_tag: v0.3.0
sqlite_cpp:
git: https://github.com/SRombauts/SQLiteCpp.git
git_tag: 3.3.1
catch2:
git: https://github.com/catchorg/Catch2.git
git_tag: v3.4.0
cmake_condition: "BUILD_TESTING"
14 changes: 14 additions & 0 deletions interfaces/error_history.yaml
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
1 change: 1 addition & 0 deletions modules/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ ev_add_module(API)
ev_add_module(Auth)
ev_add_module(EnergyManager)
ev_add_module(EnergyNode)
ev_add_module(ErrorHistory)
ev_add_module(EvseManager)
ev_add_module(EvseSecurity)
ev_add_module(EvseSlac)
Expand Down
39 changes: 39 additions & 0 deletions modules/ErrorHistory/CMakeLists.txt
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
255 changes: 255 additions & 0 deletions modules/ErrorHistory/ErrorDatabaseSqlite.cpp
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
42 changes: 42 additions & 0 deletions modules/ErrorHistory/ErrorDatabaseSqlite.hpp
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
15 changes: 15 additions & 0 deletions modules/ErrorHistory/ErrorHistory.cpp
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
Loading

0 comments on commit af1af57

Please sign in to comment.