Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add error history module #447

Merged
merged 12 commits into from
Mar 26, 2024
8 changes: 8 additions & 0 deletions dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,11 @@ gtest:
git: https://github.com/google/googletest.git
git_tag: release-1.12.1
cmake_condition: "EVEREST_CORE_BUILD_TESTING"
sqlite_cpp:
git: https://github.com/SRombauts/SQLiteCpp.git
git_tag: 3.3.1
cmake_condition: "EVEREST_DEPENDENCY_ENABLED_SQLITE_CPP"
catch2:
git: https://github.com/catchorg/Catch2.git
git_tag: v3.4.0
cmake_condition: "EVEREST_CORE_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
4 changes: 4 additions & 0 deletions module-dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ ev_define_dependency(
OUTPUT_VARIABLE_SUFFIX LIBEVSE_SECURITY
DEPENDENT_MODULES_LIST OCPP OCPP201 EvseSecurity)

ev_define_dependency(
DEPENDENCY_NAME sqlite_cpp
DEPENDENT_MODULES_LIST ErrorHistory)

if(NOT everest-gpio IN_LIST EVEREST_EXCLUDE_DEPENDENCIES)
set(EVEREST_DEPENDENCY_ENABLED_EVEREST_GPIO ON)
else()
Expand Down
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)
andistorm marked this conversation as resolved.
Show resolved Hide resolved
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
Loading
Loading