blob: 3e3c7953dd13d4c59ea7ca01547a49f14786e3a3 [file] [log] [blame]
Tomáš Pecka3f811962023-04-14 10:54:32 +02001#include "trompeloeil_doctest.h"
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +02002#include <iterator>
3#include <sysrepo-cpp/Enum.hpp>
4#include <trompeloeil.hpp>
Tomáš Pecka3f811962023-04-14 10:54:32 +02005#include "ietf-hardware/IETFHardware.h"
6#include "ietf-hardware/sysrepo/Sysrepo.h"
7#include "mock/ietf_hardware.h"
8#include "pretty_printers.h"
9#include "test_log_setup.h"
10#include "test_sysrepo_helpers.h"
11
12using namespace std::literals;
13
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020014std::string nodeAsString(const libyang::DataNode& node)
15{
16 switch (node.schema().nodeType()) {
17 case libyang::NodeType::Container:
18 return "(container)";
19 case libyang::NodeType::List:
20 return "(list instance)";
21 case libyang::NodeType::Leaf:
Tomáš Pecka2117ce52023-05-12 11:28:34 +020022 case libyang::NodeType::Leaflist:
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020023 return std::string(node.asTerm().valueStr());
24 default:
25 return "(unprintable)";
26 }
27};
28
29struct Deleted { };
30bool operator==(const Deleted&, const Deleted&) { return true; }
31
32namespace trompeloeil {
33template <>
34struct printer<std::map<std::string, std::variant<std::string, Deleted>>> {
35 static void print(std::ostream& os, const std::map<std::string, std::variant<std::string, Deleted>>& map)
36 {
37 os << "{" << std::endl;
38 for (const auto& [key, value] : map) {
39 os << " \"" << key << "\": \""
40 << std::visit([](auto&& arg) -> std::string {
41 using T = std::decay_t<decltype(arg)>;
42 if constexpr (std::is_same_v<T, Deleted>)
43 return "Deleted()";
44 if constexpr (std::is_same_v<T, std::string>)
45 return arg; }, value)
46 << "\"," << std::endl;
47 }
48 os << "}";
49 }
50};
51}
52
53struct DatastoreChange {
54 MAKE_CONST_MOCK1(change, void(const std::map<std::string, std::variant<std::string, Deleted>>&));
55};
56
Tomáš Pecka1b3c1732023-05-12 11:45:01 +020057struct AlarmEvent {
58 MAKE_CONST_MOCK1(event, void(const std::map<std::string, std::string>&));
59};
60
Tomáš Pecka2117ce52023-05-12 11:28:34 +020061#define REQUIRE_ALARM_INVENTORY_ADD_ALARM(ALARM_TYPE, IETF_HARDWARE_RESOURCE) \
62 REQUIRE_CALL(dsChangeAlarmInventory, change(std::map<std::string, std::variant<std::string, Deleted>>{ \
63 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='" ALARM_TYPE "'][alarm-type-qualifier='']", "(list instance)"}, \
64 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='" ALARM_TYPE "'][alarm-type-qualifier='']/alarm-type-id", ALARM_TYPE}, \
65 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='" ALARM_TYPE "'][alarm-type-qualifier='']/alarm-type-qualifier", ""}, \
66 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='" ALARM_TYPE "'][alarm-type-qualifier='']/resource[1]", "/ietf-hardware:hardware/component[name='" IETF_HARDWARE_RESOURCE "']"}, \
67 }))
68
69#define REQUIRE_ALARM_INVENTORY_ADD_RESOURCE(ALARM_TYPE, IETF_HARDWARE_RESOURCE) \
70 REQUIRE_CALL(dsChangeAlarmInventory, change(std::map<std::string, std::variant<std::string, Deleted>>{ \
71 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='" ALARM_TYPE "'][alarm-type-qualifier='']/resource[1]", "/ietf-hardware:hardware/component[name='" IETF_HARDWARE_RESOURCE "']"}, \
72 }))
73
Tomáš Peckaf206ab12023-05-11 20:42:10 +020074void processDsChanges(sysrepo::Session session, DatastoreChange& dsChange, const std::set<std::string>& ignoredPaths)
75{
76 std::map<std::string, std::variant<std::string, Deleted>> changes;
77
78 for (const auto& change : session.getChanges()) {
79 if (ignoredPaths.contains(change.node.schema().path())) {
80 continue;
81 }
82
83 if (change.operation == sysrepo::ChangeOperation::Deleted) {
84 changes.emplace(change.node.path(), Deleted());
85 } else {
86 changes.emplace(change.node.path(), nodeAsString(change.node));
87 }
88 }
89
90 dsChange.change(changes);
91}
92
Tomáš Pecka1b3c1732023-05-12 11:45:01 +020093#define REQUIRE_ALARM_RPC(ALARM_TYPE_ID, IETF_HARDWARE_RESOURCE_KEY, SEVERITY, TEXT) \
94 REQUIRE_CALL(alarmEvents, event(std::map<std::string, std::string>{ \
95 {"/sysrepo-ietf-alarms:create-or-update-alarm", "(unprintable)"}, \
96 {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-text", TEXT}, \
97 {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-id", ALARM_TYPE_ID}, \
98 {"/sysrepo-ietf-alarms:create-or-update-alarm/alarm-type-qualifier", ""}, \
99 {"/sysrepo-ietf-alarms:create-or-update-alarm/resource", "/ietf-hardware:hardware/component[name='" IETF_HARDWARE_RESOURCE_KEY "']"}, \
100 {"/sysrepo-ietf-alarms:create-or-update-alarm/severity", SEVERITY}, \
101 }))
102
Tomáš Pecka3f811962023-04-14 10:54:32 +0200103TEST_CASE("IETF Hardware with sysrepo")
104{
105 TEST_SYSREPO_INIT_LOGS;
106 TEST_SYSREPO_INIT;
107 TEST_SYSREPO_INIT_CLIENT;
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200108 auto alarmsClient = sysrepo::Connection{}.sessionStart(sysrepo::Datastore::Operational);
109
Tomáš Pecka3f811962023-04-14 10:54:32 +0200110 static const auto modulePrefix = "/ietf-hardware:hardware"s;
111
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200112 client.switchDatastore(sysrepo::Datastore::Operational);
113
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200114 DatastoreChange dsChangeHardware;
115 DatastoreChange dsChangeAlarmInventory;
Tomáš Pecka1b3c1732023-05-12 11:45:01 +0200116 AlarmEvent alarmEvents;
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200117
Tomáš Pecka3f811962023-04-14 10:54:32 +0200118 trompeloeil::sequence seq1;
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200119
Tomáš Pecka1b3c1732023-05-12 11:45:01 +0200120 auto alarmsRPC = alarmsClient.onRPCAction("/sysrepo-ietf-alarms:create-or-update-alarm", [&](auto, auto, auto, const libyang::DataNode input, auto, auto, auto) {
121 std::map<std::string, std::string> inputData;
122
123 for (const auto& node : input.childrenDfs()) {
124 inputData.emplace(node.path(), nodeAsString(node));
125 }
126
127 alarmEvents.event(inputData);
128
129 return sysrepo::ErrorCode::Ok;
130 });
131
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200132 auto directLeafNodeQuery = [&](const std::string& xpath) {
133 auto val = client.getData(xpath);
134 REQUIRE(val);
135 return std::string{val->findPath(xpath)->asTerm().valueStr()};
136 };
Tomáš Pecka3f811962023-04-14 10:54:32 +0200137
Tomáš Pecka3f811962023-04-14 10:54:32 +0200138 auto sysfsTempCpu = std::make_shared<FakeHWMon>();
Tomáš Pecka3f811962023-04-14 10:54:32 +0200139 auto sysfsPower = std::make_shared<FakeHWMon>();
Tomáš Pecka3f811962023-04-14 10:54:32 +0200140
Tomáš Pecka9af47392023-05-23 14:56:48 +0200141 using velia::ietf_hardware::OneThreshold;
142 using velia::ietf_hardware::Thresholds;
Tomáš Pecka3f811962023-04-14 10:54:32 +0200143 using velia::ietf_hardware::data_reader::SensorType;
144 using velia::ietf_hardware::data_reader::StaticData;
145 using velia::ietf_hardware::data_reader::SysfsValue;
Tomáš Peckae5366c62023-04-14 11:03:04 +0200146
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200147 std::atomic<bool> psuActive; // this needs to be destroyed after ietfHardware to avoid dangling reference (we are passing it as a ref to PsuDataReader)
Tomáš Pecka9af47392023-05-23 14:56:48 +0200148 std::atomic<int64_t> psuSensorValue;
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200149 std::atomic<int64_t> cpuTempValue;
150 std::atomic<int64_t> powerValue;
151
Tomáš Pecka3f811962023-04-14 10:54:32 +0200152 // register components into hw state
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200153 auto ietfHardware = std::make_shared<velia::ietf_hardware::IETFHardware>();
Tomáš Pecka3f811962023-04-14 10:54:32 +0200154 ietfHardware->registerDataReader(StaticData("ne", std::nullopt, {{"class", "iana-hardware:chassis"}, {"mfg-name", "CESNET"s}}));
Tomáš Peckae5366c62023-04-14 11:03:04 +0200155 ietfHardware->registerDataReader(SysfsValue<SensorType::Temperature>("ne:temperature-cpu", "ne", sysfsTempCpu, 1));
Tomáš Pecka9af47392023-05-23 14:56:48 +0200156 ietfHardware->registerDataReader(SysfsValue<SensorType::Power>("ne:power", "ne", sysfsPower, 1, Thresholds<int64_t>{
157 .criticalLow = OneThreshold<int64_t>{8'000'000, 500'000},
158 .warningLow = OneThreshold<int64_t>{10'000'000, 500'000},
159 .warningHigh = OneThreshold<int64_t>{20'000'000, 500'000},
160 .criticalHigh = OneThreshold<int64_t>{22'000'000, 500'000},
161 }));
Tomáš Pecka3f811962023-04-14 10:54:32 +0200162
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200163 /* Some data readers (like our PSU reader, see the FspYhPsu test) may set oper-state to enabled/disabled depending on whether the device is present and Some
164 * data might not even be pushed (e.g. the child sensors).
165 * Since we push data into sysrepo we have to erase old data (that should no longer be present) from the sysrepo operational DS.
166 * We test such situation via the following data reader which returns data only when psuActive is set to true.
167 */
168 struct PsuDataReader {
169 const std::atomic<bool>& active;
Tomáš Pecka9af47392023-05-23 14:56:48 +0200170 const std::atomic<int64_t>& value;
Tomáš Pecka3f811962023-04-14 10:54:32 +0200171
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200172 velia::ietf_hardware::DataTree operator()()
173 {
174 velia::ietf_hardware::DataTree res = {
175 {"/ietf-hardware:hardware/component[name='ne:psu']/class", "iana-hardware:power-supply"},
176 {"/ietf-hardware:hardware/component[name='ne:psu']/parent", "ne"},
177 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "disabled"},
178 };
Tomáš Pecka3f811962023-04-14 10:54:32 +0200179
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200180 if (active) {
181 res["/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state"] = "enabled";
182 res["/ietf-hardware:hardware/component[name='ne:psu:child']/class"] = "iana-hardware:sensor";
183 res["/ietf-hardware:hardware/component[name='ne:psu:child']/parent"] = "ne:psu";
184 res["/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state"] = "enabled";
185 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status"] = "ok";
Tomáš Pecka9af47392023-05-23 14:56:48 +0200186 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value"] = std::to_string(value);
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200187 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision"] = "0";
188 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale"] = "milli";
189 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type"] = "volts-DC";
190 }
Tomáš Pecka3f811962023-04-14 10:54:32 +0200191
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200192 return res;
193 }
Tomáš Pecka4886db22023-05-10 10:46:15 +0200194
195 velia::ietf_hardware::ThresholdsBySensorPath thresholds() const
196 {
Tomáš Pecka1b3c1732023-05-12 11:45:01 +0200197 return {{"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", Thresholds<int64_t>{
198 .criticalLow = std::nullopt,
199 .warningLow = OneThreshold<int64_t>{10000, 2000},
200 .warningHigh = OneThreshold<int64_t>{15000, 2000},
201 .criticalHigh = std::nullopt,
202 }}};
Tomáš Pecka4886db22023-05-10 10:46:15 +0200203 }
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200204 };
Tomáš Pecka9af47392023-05-23 14:56:48 +0200205 ietfHardware->registerDataReader(PsuDataReader{psuActive, psuSensorValue});
Tomáš Pecka3f811962023-04-14 10:54:32 +0200206
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200207 /* Ensure that there are sane data after each sysrepo change callback (all the component subtrees are expected). */
208 auto changeSub = client.onModuleChange(
209 "ietf-hardware",
210 [&](sysrepo::Session session, auto, auto, auto, auto, auto) {
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200211 processDsChanges(session, dsChangeHardware, {"/ietf-hardware:hardware/last-change"});
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200212 return sysrepo::ErrorCode::Ok;
213 },
214 "/ietf-hardware:hardware/component",
215 0,
216 sysrepo::SubscribeOptions::DoneOnly);
Tomáš Peckacb089ac2023-04-21 14:54:26 +0200217
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200218 auto alarmsInvSub = alarmsClient.onModuleChange(
219 "ietf-alarms",
220 [&](sysrepo::Session session, auto, auto, auto, auto, auto) {
221 processDsChanges(session, dsChangeAlarmInventory, {});
222 return sysrepo::ErrorCode::Ok;
223 },
224 "/ietf-alarms:alarms/alarm-inventory",
225 0,
226 sysrepo::SubscribeOptions::DoneOnly);
227
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200228 // first batch of values
229 cpuTempValue = 41800;
Tomáš Pecka9af47392023-05-23 14:56:48 +0200230 powerValue = 0;
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200231 psuActive = true;
Tomáš Pecka9af47392023-05-23 14:56:48 +0200232 psuSensorValue = 12000;
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200233 REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
234 REQUIRE_CALL(*sysfsPower, attribute("power1_input")).LR_RETURN(powerValue).TIMES(AT_LEAST(1));
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200235 REQUIRE_CALL(dsChangeAlarmInventory, change(std::map<std::string, std::variant<std::string, Deleted>>{
236 {"/ietf-alarms:alarms", "(container)"},
237 {"/ietf-alarms:alarms/alarm-inventory", "(container)"},
238 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='velia-alarms:sensor-low-value-alarm'][alarm-type-qualifier='']", "(list instance)"},
239 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='velia-alarms:sensor-low-value-alarm'][alarm-type-qualifier='']/alarm-type-id", "velia-alarms:sensor-low-value-alarm"},
240 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='velia-alarms:sensor-low-value-alarm'][alarm-type-qualifier='']/alarm-type-qualifier", ""},
241 {"/ietf-alarms:alarms/alarm-inventory/alarm-type[alarm-type-id='velia-alarms:sensor-low-value-alarm'][alarm-type-qualifier='']/resource[1]", "/ietf-hardware:hardware/component[name='ne:power']"},
242 }))
243 .IN_SEQUENCE(seq1); // the first alarm-inventory change also adds the two container leafs therefore I have not used the REQUIRE_ALARM_INVENTORY_ADD_ALARM macro
244 REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-high-value-alarm", "ne:power").IN_SEQUENCE(seq1);
245 REQUIRE_ALARM_INVENTORY_ADD_ALARM("velia-alarms:sensor-missing-alarm", "ne:power").IN_SEQUENCE(seq1);
246
247 REQUIRE_ALARM_INVENTORY_ADD_RESOURCE("velia-alarms:sensor-low-value-alarm", "ne:psu:child").IN_SEQUENCE(seq1);
248 REQUIRE_ALARM_INVENTORY_ADD_RESOURCE("velia-alarms:sensor-high-value-alarm", "ne:psu:child").IN_SEQUENCE(seq1);
249 REQUIRE_ALARM_INVENTORY_ADD_RESOURCE("velia-alarms:sensor-missing-alarm", "ne:psu:child").IN_SEQUENCE(seq1);
250
251 REQUIRE_ALARM_INVENTORY_ADD_RESOURCE("velia-alarms:sensor-low-value-alarm", "ne:temperature-cpu").IN_SEQUENCE(seq1);
252 REQUIRE_ALARM_INVENTORY_ADD_RESOURCE("velia-alarms:sensor-high-value-alarm", "ne:temperature-cpu").IN_SEQUENCE(seq1);
253 REQUIRE_ALARM_INVENTORY_ADD_RESOURCE("velia-alarms:sensor-missing-alarm", "ne:temperature-cpu").IN_SEQUENCE(seq1);
254
255 REQUIRE_CALL(dsChangeHardware, change(std::map<std::string, std::variant<std::string, Deleted>>{
256 {"/ietf-hardware:hardware", "(container)"},
257 {"/ietf-hardware:hardware/component[name='ne']", "(list instance)"},
258 {"/ietf-hardware:hardware/component[name='ne']/class", "iana-hardware:chassis"},
259 {"/ietf-hardware:hardware/component[name='ne']/mfg-name", "CESNET"},
260 {"/ietf-hardware:hardware/component[name='ne']/name", "ne"},
261 {"/ietf-hardware:hardware/component[name='ne']/state", "(container)"},
262 {"/ietf-hardware:hardware/component[name='ne']/state/oper-state", "enabled"},
263 {"/ietf-hardware:hardware/component[name='ne:power']", "(list instance)"},
264 {"/ietf-hardware:hardware/component[name='ne:power']/class", "iana-hardware:sensor"},
265 {"/ietf-hardware:hardware/component[name='ne:power']/name", "ne:power"},
266 {"/ietf-hardware:hardware/component[name='ne:power']/parent", "ne"},
267 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data", "(container)"},
268 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/oper-status", "ok"},
Tomáš Pecka9af47392023-05-23 14:56:48 +0200269 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "0"},
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200270 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-precision", "0"},
271 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-scale", "micro"},
272 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-type", "watts"},
273 {"/ietf-hardware:hardware/component[name='ne:power']/state", "(container)"},
274 {"/ietf-hardware:hardware/component[name='ne:power']/state/oper-state", "enabled"},
275 {"/ietf-hardware:hardware/component[name='ne:psu']", "(list instance)"},
276 {"/ietf-hardware:hardware/component[name='ne:psu']/class", "iana-hardware:power-supply"},
277 {"/ietf-hardware:hardware/component[name='ne:psu']/name", "ne:psu"},
278 {"/ietf-hardware:hardware/component[name='ne:psu']/parent", "ne"},
279 {"/ietf-hardware:hardware/component[name='ne:psu']/state", "(container)"},
280 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "enabled"},
281 {"/ietf-hardware:hardware/component[name='ne:psu:child']", "(list instance)"},
282 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", "iana-hardware:sensor"},
283 {"/ietf-hardware:hardware/component[name='ne:psu:child']/name", "ne:psu:child"},
284 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", "ne:psu"},
285 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", "(container)"},
286 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", "ok"},
287 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", "12000"},
288 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", "0"},
289 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", "milli"},
290 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", "volts-DC"},
291 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", "(container)"},
292 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", "enabled"},
293 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']", "(list instance)"},
294 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/class", "iana-hardware:sensor"},
295 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/name", "ne:temperature-cpu"},
296 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/parent", "ne"},
297 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data", "(container)"},
298 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/oper-status", "ok"},
299 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value", "41800"},
300 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-precision", "0"},
301 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-scale", "milli"},
302 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-type", "celsius"},
303 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/state", "(container)"},
304 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/state/oper-state", "enabled"},
305 }))
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200306 .IN_SEQUENCE(seq1);
Tomáš Pecka9af47392023-05-23 14:56:48 +0200307 REQUIRE_ALARM_RPC("velia-alarms:sensor-low-value-alarm", "ne:power", "critical", "Sensor value crossed low threshold.").IN_SEQUENCE(seq1);
Tomáš Pecka3f811962023-04-14 10:54:32 +0200308
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200309 auto ietfHardwareSysrepo = std::make_shared<velia::ietf_hardware::sysrepo::Sysrepo>(srSess, ietfHardware, 150ms);
310 std::this_thread::sleep_for(400ms); // let's wait until the bg polling thread is spawned; 400 ms is probably enough to spawn the thread and poll 2 or 3 times
311
312 std::string lastChange = directLeafNodeQuery(modulePrefix + "/last-change");
313
314 // second batch of values, sensor data changed, PSU ejected
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200315 REQUIRE_CALL(dsChangeHardware, change(std::map<std::string, std::variant<std::string, Deleted>>{
316 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", Deleted{}},
317 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", Deleted{}},
318 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", Deleted{}},
319 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", Deleted{}},
320 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", Deleted{}},
321 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", Deleted{}},
322 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", Deleted{}},
323 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", Deleted{}},
324 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", Deleted{}},
325 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", Deleted{}},
326 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "11222333"},
327 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "disabled"},
328 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value", "222"},
329 }))
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200330 .IN_SEQUENCE(seq1);
Tomáš Pecka9af47392023-05-23 14:56:48 +0200331 REQUIRE_ALARM_RPC("velia-alarms:sensor-low-value-alarm", "ne:power", "cleared", "Sensor value crossed low threshold.").IN_SEQUENCE(seq1);
Tomáš Pecka1b3c1732023-05-12 11:45:01 +0200332 REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu:child", "warning", "Sensor value not reported. Maybe the sensor was unplugged?").IN_SEQUENCE(seq1);
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200333 REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
334 REQUIRE_CALL(*sysfsPower, attribute("power1_input")).LR_RETURN(powerValue).TIMES(AT_LEAST(1));
335 cpuTempValue = 222;
336 powerValue = 11222333;
337 psuActive = false;
338
339 std::this_thread::sleep_for(2000ms); // longer sleep here: last-change does not report milliseconds so this should increase last-change timestamp at least by one second
340 REQUIRE(directLeafNodeQuery(modulePrefix + "/last-change") > lastChange); // check that last-change leaf has timestamp that is greater than the previous one
341
Tomáš Pecka9af47392023-05-23 14:56:48 +0200342 // third batch of changes, wild PSU appears with a warning
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200343 REQUIRE_CALL(dsChangeHardware, change(std::map<std::string, std::variant<std::string, Deleted>>{
344 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "enabled"},
345 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", "iana-hardware:sensor"},
346 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", "ne:psu"},
347 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", "(container)"},
348 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", "ok"},
Tomáš Pecka9af47392023-05-23 14:56:48 +0200349 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", "50000"},
Tomáš Pecka2117ce52023-05-12 11:28:34 +0200350 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", "0"},
351 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", "milli"},
352 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", "volts-DC"},
353 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", "(container)"},
354 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", "enabled"},
355 }))
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200356 .IN_SEQUENCE(seq1);
Tomáš Pecka1b3c1732023-05-12 11:45:01 +0200357 REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu:child", "cleared", "Sensor value not reported. Maybe the sensor was unplugged?").IN_SEQUENCE(seq1);
Tomáš Pecka9af47392023-05-23 14:56:48 +0200358 REQUIRE_ALARM_RPC("velia-alarms:sensor-high-value-alarm", "ne:psu:child", "warning", "Sensor value crossed high threshold.").IN_SEQUENCE(seq1);
359 psuSensorValue = 50000;
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200360 psuActive = true;
361
362 waitForCompletionAndBitMore(seq1);
Tomáš Pecka9af47392023-05-23 14:56:48 +0200363
364 // fourth round. We unplug with a warning
365 REQUIRE_CALL(dsChangeHardware, change(std::map<std::string, std::variant<std::string, Deleted>>{
366 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", Deleted{}},
367 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", Deleted{}},
368 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", Deleted{}},
369 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", Deleted{}},
370 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", Deleted{}},
371 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", Deleted{}},
372 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", Deleted{}},
373 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", Deleted{}},
374 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", Deleted{}},
375 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", Deleted{}},
376 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "disabled"},
377 }))
378 .IN_SEQUENCE(seq1);
379 REQUIRE_ALARM_RPC("velia-alarms:sensor-missing-alarm", "ne:psu:child", "warning", "Sensor value not reported. Maybe the sensor was unplugged?").IN_SEQUENCE(seq1);
380 REQUIRE_ALARM_RPC("velia-alarms:sensor-high-value-alarm", "ne:psu:child", "cleared", "Sensor value crossed high threshold.").IN_SEQUENCE(seq1);
381 psuActive = false;
382 waitForCompletionAndBitMore(seq1);
383
384 // 5+th round: test threshold crossings
385 REQUIRE_CALL(dsChangeHardware, change(std::map<std::string, std::variant<std::string, Deleted>>{
386 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "21000000"},
387 }))
388 .IN_SEQUENCE(seq1);
389 REQUIRE_ALARM_RPC("velia-alarms:sensor-high-value-alarm", "ne:power", "warning", "Sensor value crossed high threshold.").IN_SEQUENCE(seq1);
390 powerValue = 21'000'000;
391 waitForCompletionAndBitMore(seq1);
392
393 REQUIRE_CALL(dsChangeHardware, change(std::map<std::string, std::variant<std::string, Deleted>>{
394 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "24000000"},
395 }))
396 .IN_SEQUENCE(seq1);
397 REQUIRE_ALARM_RPC("velia-alarms:sensor-high-value-alarm", "ne:power", "critical", "Sensor value crossed high threshold.").IN_SEQUENCE(seq1);
398 powerValue = 24'000'000;
399 waitForCompletionAndBitMore(seq1);
400
401 REQUIRE_CALL(dsChangeHardware, change(std::map<std::string, std::variant<std::string, Deleted>>{
402 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "1"},
403 }))
404 .IN_SEQUENCE(seq1);
405 REQUIRE_ALARM_RPC("velia-alarms:sensor-low-value-alarm", "ne:power", "critical", "Sensor value crossed low threshold.").IN_SEQUENCE(seq1);
406 REQUIRE_ALARM_RPC("velia-alarms:sensor-high-value-alarm", "ne:power", "cleared", "Sensor value crossed high threshold.").IN_SEQUENCE(seq1);
407 powerValue = 1;
408 waitForCompletionAndBitMore(seq1);
409
410 REQUIRE_CALL(dsChangeHardware, change(std::map<std::string, std::variant<std::string, Deleted>>{
411 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "14000000"},
412 }))
413 .IN_SEQUENCE(seq1);
414 REQUIRE_ALARM_RPC("velia-alarms:sensor-low-value-alarm", "ne:power", "cleared", "Sensor value crossed low threshold.").IN_SEQUENCE(seq1);
415 powerValue = 14'000'000;
416
417 waitForCompletionAndBitMore(seq1);
Tomáš Pecka3f811962023-04-14 10:54:32 +0200418}