blob: c855d599fad8df81ff20e0fb53aecfde6fad88c4 [file] [log] [blame]
Tomáš Pecka6a2334b2022-07-12 13:57:54 +02001/*
2 * Copyright (C) 2020 - 2022 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
5 *
6 */
7
8#include "trompeloeil_doctest.h"
9#include <sysrepo-cpp/Connection.hpp>
10#include <sysrepo-cpp/Enum.hpp>
11#include <sysrepo-cpp/Subscription.hpp>
12#include <thread>
13#include "dbus-helpers/dbus_systemd_server.h"
14#include "health/alarms/SystemdUnits.h"
Tomáš Pecka8e5a2d32022-08-22 17:48:58 +020015#include "pretty_printers.h"
Tomáš Pecka6a2334b2022-07-12 13:57:54 +020016#include "test_log_setup.h"
17#include "test_sysrepo_helpers.h"
18#include "utils/log-init.h"
19#include "utils/log.h"
20
21using namespace std::chrono_literals;
22
23// clang-format off
24#define EXPECT_ALARM_RPC(RESOURCE, SEVERITY, TEXT) REQUIRE_CALL(fakeAlarmServer, rpcCalled(alarmRPC, std::map<std::string, std::string>{ \
25 {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-text", TEXT}, \
26 {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-id", "velia-alarms:systemd-unit-failure"}, \
27 {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-qualifier", ""}, \
28 {"/sysrepo-ietf-alarms:create-or-update-alarm/resource", RESOURCE}, \
29 {"/sysrepo-ietf-alarms:create-or-update-alarm/severity", SEVERITY} \
30 })).IN_SEQUENCE(seq1);
31// clang-format on
32
33const auto alarmRPC = "/sysrepo-ietf-alarms:create-or-update-alarm";
34
35class FakeAlarmServerSysrepo {
36public:
37 FakeAlarmServerSysrepo();
38 MAKE_CONST_MOCK2(rpcCalled, void(std::string_view, const std::map<std::string, std::string>&));
39
40private:
41 sysrepo::Session m_srSess;
42 std::optional<sysrepo::Subscription> m_srSub;
43};
44
45
46FakeAlarmServerSysrepo::FakeAlarmServerSysrepo()
47 : m_srSess(sysrepo::Connection{}.sessionStart())
48{
49 m_srSub = m_srSess.onRPCAction(alarmRPC, [&](auto, auto, std::string_view path, const libyang::DataNode input, auto, auto, auto) {
50 std::map<std::string, std::string> in;
51
52 for (auto n : input.childrenDfs()) {
53 if (n.isTerm()) {
54 in[n.path()] = n.asTerm().valueStr();
55 }
56 }
57
58 rpcCalled(path, in);
59 return sysrepo::ErrorCode::Ok;
60 });
61}
62
63TEST_CASE("systemd unit state monitoring (alarms)")
64{
65 TEST_INIT_LOGS;
66 TEST_SYSREPO_INIT;
67 trompeloeil::sequence seq1;
68
69 // Create and setup separate connections for both client and server to simulate real-world server-client architecture.
70 // Also this doesn't work one a single dbus connection.
71 auto clientConnection = sdbus::createSessionBusConnection();
72 auto serverConnection = sdbus::createSessionBusConnection();
73 clientConnection->enterEventLoopAsync();
74 serverConnection->enterEventLoopAsync();
75
76 auto server = DbusSystemdServer(*serverConnection);
77 FakeAlarmServerSysrepo fakeAlarmServer;
78
79 EXPECT_ALARM_RPC("unit1.service", "cleared", "systemd unit state: (active, running)");
80 server.createUnit(*serverConnection, "unit1.service", "/org/freedesktop/systemd1/unit/unit1", "active", "running");
81 EXPECT_ALARM_RPC("unit2.service", "critical", "systemd unit state: (activating, auto-restart)");
82 server.createUnit(*serverConnection, "unit2.service", "/org/freedesktop/systemd1/unit/unit2", "activating", "auto-restart");
83 EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (failed, failed)");
84 server.createUnit(*serverConnection, "unit3.service", "/org/freedesktop/systemd1/unit/unit3", "failed", "failed");
85
86 auto systemdAlarms = std::make_shared<velia::health::SystemdUnits>(srSess, *clientConnection, serverConnection->getUniqueName(), "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "org.freedesktop.systemd1.Unit");
87
Tomáš Pecka8e5a2d32022-08-22 17:48:58 +020088 waitForCompletionAndBitMore(seq1);
89 // clang-format off
90 REQUIRE(dataFromSysrepo(srSess, "/ietf-alarms:alarms/alarm-inventory", sysrepo::Datastore::Operational) == std::map<std::string, std::string>{
91 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']", ""},
92 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/alarm-type-id", "velia-alarms:systemd-unit-failure"},
93 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/alarm-type-qualifier", ""},
94 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/resource[1]", "unit1.service"},
95 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/resource[2]", "unit2.service"},
96 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/resource[3]", "unit3.service"},
97 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/will-clear", "true"},
98 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/severity-level[1]", "critical"},
99 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/description", "The systemd service is considered in failed state."}
100 });
101 // clang-format on
102
Tomáš Pecka6a2334b2022-07-12 13:57:54 +0200103 EXPECT_ALARM_RPC("unit2.service", "cleared", "systemd unit state: (active, running)");
104 EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
105 EXPECT_ALARM_RPC("unit4.service", "critical", "systemd unit state: (failed, failed)");
106 EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (activating, auto-restart)");
107 EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
108 EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (failed, failed)");
109 EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (activating, auto-restart)");
110 EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
111 EXPECT_ALARM_RPC("unit4.service", "cleared", "systemd unit state: (active, running)");
112
113 std::thread systemdSimulator([&] {
114 server.changeUnitState("/org/freedesktop/systemd1/unit/unit2", "active", "running");
115 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "active", "running");
116
117 // In case we obtain a notifications that unit changed state from (X,Y) to (X,Y), do not trigger any events.
118 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "active", "running");
119
120 // add new unit with failed/failed, DbusSystemdInput should receive UnitNew signal and monitor this unit too
121 server.createUnit(*serverConnection, "unit4.service", "/org/freedesktop/systemd1/unit/unit4", "failed", "failed");
122
123 // Sleep for a while; the rest of the code might be too fast and we need to be sure that we pick up event for (failed, failed) before the state of unit4 is changed in the DBus server
124 std::this_thread::sleep_for(25ms);
125
126 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "activating", "auto-restart");
127 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "active", "running");
128 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "failed", "failed");
129 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "activating", "auto-restart");
130 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "active", "running");
131
132 server.changeUnitState("/org/freedesktop/systemd1/unit/unit4", "active", "running");
133 });
134
Tomáš Pecka6a2334b2022-07-12 13:57:54 +0200135 systemdSimulator.join();
136 waitForCompletionAndBitMore(seq1);
Tomáš Pecka8e5a2d32022-08-22 17:48:58 +0200137
138 // clang-format off
139 REQUIRE(dataFromSysrepo(srSess, "/ietf-alarms:alarms/alarm-inventory", sysrepo::Datastore::Operational) == std::map<std::string, std::string>{
140 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']", ""},
141 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/alarm-type-id", "velia-alarms:systemd-unit-failure"},
142 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/alarm-type-qualifier", ""},
143 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/resource[1]", "unit1.service"},
144 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/resource[2]", "unit2.service"},
145 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/resource[3]", "unit3.service"},
146 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/resource[4]", "unit4.service"},
147 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/will-clear", "true"},
148 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/severity-level[1]", "critical"},
149 {"/alarm-type[alarm-type-id='velia-alarms:systemd-unit-failure'][alarm-type-qualifier='']/description", "The systemd service is considered in failed state."}
150 });
151 // clang-format on
Tomáš Pecka6a2334b2022-07-12 13:57:54 +0200152}