blob: 91eb95ddc46f80d5c7ab005d1c71dda4562f7f08 [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"
15#include "mock/health.h"
16#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
88 EXPECT_ALARM_RPC("unit2.service", "cleared", "systemd unit state: (active, running)");
89 EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
90 EXPECT_ALARM_RPC("unit4.service", "critical", "systemd unit state: (failed, failed)");
91 EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (activating, auto-restart)");
92 EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
93 EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (failed, failed)");
94 EXPECT_ALARM_RPC("unit3.service", "critical", "systemd unit state: (activating, auto-restart)");
95 EXPECT_ALARM_RPC("unit3.service", "cleared", "systemd unit state: (active, running)");
96 EXPECT_ALARM_RPC("unit4.service", "cleared", "systemd unit state: (active, running)");
97
98 std::thread systemdSimulator([&] {
99 server.changeUnitState("/org/freedesktop/systemd1/unit/unit2", "active", "running");
100 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "active", "running");
101
102 // In case we obtain a notifications that unit changed state from (X,Y) to (X,Y), do not trigger any events.
103 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "active", "running");
104
105 // add new unit with failed/failed, DbusSystemdInput should receive UnitNew signal and monitor this unit too
106 server.createUnit(*serverConnection, "unit4.service", "/org/freedesktop/systemd1/unit/unit4", "failed", "failed");
107
108 // 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
109 std::this_thread::sleep_for(25ms);
110
111 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "activating", "auto-restart");
112 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "active", "running");
113 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "failed", "failed");
114 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "activating", "auto-restart");
115 server.changeUnitState("/org/freedesktop/systemd1/unit/unit3", "active", "running");
116
117 server.changeUnitState("/org/freedesktop/systemd1/unit/unit4", "active", "running");
118 });
119
120
121 systemdSimulator.join();
122 waitForCompletionAndBitMore(seq1);
123}