diff --git a/exes/themis/inc/alarm_database.hpp b/exes/themis/inc/alarm_database.hpp index 51e9e4bbf3..82d6cac9eb 100644 --- a/exes/themis/inc/alarm_database.hpp +++ b/exes/themis/inc/alarm_database.hpp @@ -78,7 +78,7 @@ CREATE TABLE IF NOT EXISTS AlarmActivations( alarm_id INTEGER NOT NULL, activation_time LONG INTEGER NOT NULL, reset_time LONG INTEGER, - activation_level BOOLEAN NOT NULL, + activation_level SHORT INTEGER NOT NULL, inserted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY(alarm_id) REFERENCES Alarms(alarm_id) ); @@ -244,8 +244,8 @@ ON Alarms.sha1sum = AlarmTranslations.sha1sum; db_ << "BEGIN;"; std::uint64_t activation_id; try { - db_ << fmt::format("INSERT INTO AlarmActivations(alarm_id, activation_time, activation_level) VALUES({},{},1)", - alarm_id, milliseconds_since_epoch(tp)); + db_ << fmt::format("INSERT INTO AlarmActivations(alarm_id, activation_time, activation_level) VALUES({},{},{})", + alarm_id, milliseconds_since_epoch(tp), static_cast(tfc::snitch::api::active_e::active)); activation_id = static_cast(db_.last_insert_rowid()); for (auto& [key, value] : variables) { @@ -263,7 +263,8 @@ ON Alarms.sha1sum = AlarmTranslations.sha1sum; if (!is_activation_high(activation_id)) { throw dbus_error("Cannot reset an inactive activation"); } - db_ << fmt::format("UPDATE AlarmActivations SET activation_level = 0, reset_time = {} WHERE activation_id = {};", + db_ << fmt::format("UPDATE AlarmActivations SET activation_level = {}, reset_time = {} WHERE activation_id = {};", + static_cast(tfc::snitch::api::active_e::inactive), milliseconds_since_epoch(tp), activation_id); } @@ -308,7 +309,7 @@ WHERE activation_time >= {} AND activation_time <= {})", db_ << populated_query >> [&](std::uint64_t activation_id, snitch::api::alarm_id_t alarm_id, std::int64_t activation_time, std::optional reset_time, - bool activation_level, std::optional primary_details, + std::int64_t activation_level, std::optional primary_details, std::optional primary_description, std::optional backup_details, std::optional backup_description, bool alarm_latching, std::int8_t alarm_level) { @@ -322,7 +323,7 @@ WHERE activation_time >= {} AND activation_time <= {})", if (reset_time.has_value()) { final_reset_time = timepoint_from_milliseconds(reset_time.value()); } - activations.emplace_back(alarm_id, activation_id, description, details, activation_level, + activations.emplace_back(alarm_id, activation_id, description, details, static_cast(activation_level), static_cast(alarm_level), alarm_latching, timepoint_from_milliseconds(activation_time), final_reset_time, in_locale); }; diff --git a/exes/themis/tests/themis_integration_test.cpp b/exes/themis/tests/themis_integration_test.cpp index f2a2a7c428..6c08337e27 100644 --- a/exes/themis/tests/themis_integration_test.cpp +++ b/exes/themis/tests/themis_integration_test.cpp @@ -30,6 +30,32 @@ using tfc::snitch::detail::dbus_client; using tfc::themis::alarm_database; using tfc::themis::interface; +struct test_setup_s { + asio::io_context ctx; + alarm_database db; + std::shared_ptr connection; + interface face; + std::thread t; + test_setup_s() + : db(true), connection{ std::make_shared(ctx, tfc::dbus::sd_bus_open_system()) }, + face(connection, db), t([&] { ctx.run(); }) {} + ~test_setup_s() { + ctx.stop(); + t.join(); + } +}; + +struct test_setup_c { + asio::io_context ctx; + std::shared_ptr connection; + std::array ran{}; + std::array ids{}; + dbus_client client; + test_setup_c() + : connection{ std::make_shared(ctx, tfc::dbus::sd_bus_open_system()) }, + client(connection) {} +}; + struct test_setup { asio::io_context ctx; asio::io_context sctx; @@ -240,5 +266,88 @@ int main(int argc, char** argv) { expect(t.ran[1]); }; + // TODO: The alarm is recreated but its state is not set again. + "Alarm loses database connection set forgotten"_test = []{ + test_setup_s *server = new test_setup_s(); + test_setup_c client; + info<"desc", "details"> i(client.connection, "dead_server_test"); + { + i.set( + [&](auto err) { + expect(!err) << fmt::format("Received error: {}", err.message()); + client.ran[0] = true; + }); + client.ctx.run_for(2ms); + expect(client.ran[0]); + delete server; + } + { + // This servers database is started in ram again. + // should the alarm should be populated and set again. + client.ran[0] = false; // Reset the set callback. Should probably be called again. + client.ctx.run_for(2ms); + test_setup_s *server2 = new test_setup_s(); + client.ctx.run_for(2ms); + expect(client.ran[0]); + client.client.list_alarms([&](const std::error_code& err, std::vector alarms) { + expect(!err) << err.message(); + expect(alarms.size() == 1); + client.ran[1] = true; + }); + client.ctx.run_for(2ms); + expect(client.ran[1]); + client.client.list_activations( + "en", 0, 10000, tfc::snitch::level_e::info, tfc::snitch::api::active_e::active, + tfc::themis::alarm_database::timepoint_from_milliseconds(0), + tfc::themis::alarm_database::timepoint_from_milliseconds(std::numeric_limits::max()), + [&](const std::error_code& err, std::vector act) { + expect(!err) << err.message(); + expect(act.size() == 1); + client.ran[2] = true; + }); + client.ctx.run_for(2ms); + expect(client.ran[2]); + delete server2; + } + }; + + //TODO: An extra alarm could be created that warns of the lost connection. + "An alarm that is lost shall have its activations status set to unknown"_test = []{ + test_setup_s server; + test_setup_c *client = new test_setup_c(); + info<"desc", "details"> i(client->connection, "dead_client_test"); + client->client.list_alarms([&](const std::error_code& err, std::vector alarms) { + expect(!err) << err.message(); + expect(alarms.size() == 1); + client->ran[0] = true; + }); + client->ctx.run_for(2ms); + expect(client->ran[0]); + i.set( + [&](auto err) { + expect(!err) << fmt::format("Received error: {}", err.message()); + client->ran[1] = true; + }); + client->ctx.run_for(2ms); + expect(client->ran[1]); + delete client; + test_setup_c new_client; + new_client.client.list_activations( + "en", 0, 10000, tfc::snitch::level_e::info, tfc::snitch::api::active_e::active, + tfc::themis::alarm_database::timepoint_from_milliseconds(0), + tfc::themis::alarm_database::timepoint_from_milliseconds(std::numeric_limits::max()), + [&](const std::error_code& err, std::vector act) { + expect(!err) << err.message(); + expect(act.size() == 1); + auto const& alarm = act.at(0); + expect(alarm.description == "desc"); + expect(alarm.details == "details"); + expect(alarm.lvl == tfc::snitch::level_e::info); + expect(alarm.active == tfc::snitch::api::active_e::unknown); + new_client.ran[0] = true; + }); + new_client.ctx.run_for(2ms); + expect(new_client.ran[0]); + }; return 0; } diff --git a/libs/snitch/inc/public/tfc/snitch/common.hpp b/libs/snitch/inc/public/tfc/snitch/common.hpp index 91328a9597..ce4b8ac4f9 100644 --- a/libs/snitch/inc/public/tfc/snitch/common.hpp +++ b/libs/snitch/inc/public/tfc/snitch/common.hpp @@ -9,10 +9,10 @@ namespace tfc::snitch { // std::int8_t is not in the dbus spec, so we use std::int16_t instead enum struct level_e : std::int16_t { all = -1, - unknown, - info, - warning, - error, + info = 0, + warning = 1, + error = 2, + unknown = 32767 }; namespace api { @@ -25,7 +25,8 @@ using activation_id_t = alarm_id_t; enum struct active_e : std::int16_t { all = -1, inactive = 0, - active = 1 + active = 1, + unknown = 32767 }; struct alarm { @@ -48,7 +49,7 @@ struct activation { std::uint64_t activation_id; std::string description; std::string details; - bool active{}; + active_e active{ active_e::unknown }; level_e lvl{ level_e::unknown }; bool latching{}; time_point set_timestamp;