blob: 411e7c358fb982800602c4b8c5697ab665ba2ca3 [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:
22 return std::string(node.asTerm().valueStr());
23 default:
24 return "(unprintable)";
25 }
26};
27
28struct Deleted { };
29bool operator==(const Deleted&, const Deleted&) { return true; }
30
31namespace trompeloeil {
32template <>
33struct printer<std::map<std::string, std::variant<std::string, Deleted>>> {
34 static void print(std::ostream& os, const std::map<std::string, std::variant<std::string, Deleted>>& map)
35 {
36 os << "{" << std::endl;
37 for (const auto& [key, value] : map) {
38 os << " \"" << key << "\": \""
39 << std::visit([](auto&& arg) -> std::string {
40 using T = std::decay_t<decltype(arg)>;
41 if constexpr (std::is_same_v<T, Deleted>)
42 return "Deleted()";
43 if constexpr (std::is_same_v<T, std::string>)
44 return arg; }, value)
45 << "\"," << std::endl;
46 }
47 os << "}";
48 }
49};
50}
51
52struct DatastoreChange {
53 MAKE_CONST_MOCK1(change, void(const std::map<std::string, std::variant<std::string, Deleted>>&));
54};
55
Tomáš Peckaf206ab12023-05-11 20:42:10 +020056void processDsChanges(sysrepo::Session session, DatastoreChange& dsChange, const std::set<std::string>& ignoredPaths)
57{
58 std::map<std::string, std::variant<std::string, Deleted>> changes;
59
60 for (const auto& change : session.getChanges()) {
61 if (ignoredPaths.contains(change.node.schema().path())) {
62 continue;
63 }
64
65 if (change.operation == sysrepo::ChangeOperation::Deleted) {
66 changes.emplace(change.node.path(), Deleted());
67 } else {
68 changes.emplace(change.node.path(), nodeAsString(change.node));
69 }
70 }
71
72 dsChange.change(changes);
73}
74
Tomáš Pecka3f811962023-04-14 10:54:32 +020075TEST_CASE("IETF Hardware with sysrepo")
76{
77 TEST_SYSREPO_INIT_LOGS;
78 TEST_SYSREPO_INIT;
79 TEST_SYSREPO_INIT_CLIENT;
80 static const auto modulePrefix = "/ietf-hardware:hardware"s;
81
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020082 client.switchDatastore(sysrepo::Datastore::Operational);
83
84 DatastoreChange dsChange;
85
Tomáš Pecka3f811962023-04-14 10:54:32 +020086 trompeloeil::sequence seq1;
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020087
88 auto directLeafNodeQuery = [&](const std::string& xpath) {
89 auto val = client.getData(xpath);
90 REQUIRE(val);
91 return std::string{val->findPath(xpath)->asTerm().valueStr()};
92 };
Tomáš Pecka3f811962023-04-14 10:54:32 +020093
Tomáš Pecka3f811962023-04-14 10:54:32 +020094 auto sysfsTempCpu = std::make_shared<FakeHWMon>();
Tomáš Pecka3f811962023-04-14 10:54:32 +020095 auto sysfsPower = std::make_shared<FakeHWMon>();
Tomáš Pecka3f811962023-04-14 10:54:32 +020096
Tomáš Pecka3f811962023-04-14 10:54:32 +020097 using velia::ietf_hardware::data_reader::SensorType;
98 using velia::ietf_hardware::data_reader::StaticData;
99 using velia::ietf_hardware::data_reader::SysfsValue;
Tomáš Peckae5366c62023-04-14 11:03:04 +0200100
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200101 std::atomic<bool> psuActive; // this needs to be destroyed after ietfHardware to avoid dangling reference (we are passing it as a ref to PsuDataReader)
102 std::atomic<int64_t> cpuTempValue;
103 std::atomic<int64_t> powerValue;
104
Tomáš Pecka3f811962023-04-14 10:54:32 +0200105 // register components into hw state
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200106 auto ietfHardware = std::make_shared<velia::ietf_hardware::IETFHardware>();
Tomáš Pecka3f811962023-04-14 10:54:32 +0200107 ietfHardware->registerDataReader(StaticData("ne", std::nullopt, {{"class", "iana-hardware:chassis"}, {"mfg-name", "CESNET"s}}));
Tomáš Peckae5366c62023-04-14 11:03:04 +0200108 ietfHardware->registerDataReader(SysfsValue<SensorType::Temperature>("ne:temperature-cpu", "ne", sysfsTempCpu, 1));
109 ietfHardware->registerDataReader(SysfsValue<SensorType::Power>("ne:power", "ne", sysfsPower, 1));
Tomáš Pecka3f811962023-04-14 10:54:32 +0200110
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200111 /* 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
112 * data might not even be pushed (e.g. the child sensors).
113 * Since we push data into sysrepo we have to erase old data (that should no longer be present) from the sysrepo operational DS.
114 * We test such situation via the following data reader which returns data only when psuActive is set to true.
115 */
116 struct PsuDataReader {
117 const std::atomic<bool>& active;
Tomáš Pecka3f811962023-04-14 10:54:32 +0200118
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200119 velia::ietf_hardware::DataTree operator()()
120 {
121 velia::ietf_hardware::DataTree res = {
122 {"/ietf-hardware:hardware/component[name='ne:psu']/class", "iana-hardware:power-supply"},
123 {"/ietf-hardware:hardware/component[name='ne:psu']/parent", "ne"},
124 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "disabled"},
125 };
Tomáš Pecka3f811962023-04-14 10:54:32 +0200126
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200127 if (active) {
128 res["/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state"] = "enabled";
129 res["/ietf-hardware:hardware/component[name='ne:psu:child']/class"] = "iana-hardware:sensor";
130 res["/ietf-hardware:hardware/component[name='ne:psu:child']/parent"] = "ne:psu";
131 res["/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state"] = "enabled";
132 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status"] = "ok";
133 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value"] = "12000";
134 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision"] = "0";
135 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale"] = "milli";
136 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type"] = "volts-DC";
137 }
Tomáš Pecka3f811962023-04-14 10:54:32 +0200138
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200139 return res;
140 }
Tomáš Pecka4886db22023-05-10 10:46:15 +0200141
142 velia::ietf_hardware::ThresholdsBySensorPath thresholds() const
143 {
144 return {{"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", {}}};
145 }
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200146 };
147 ietfHardware->registerDataReader(PsuDataReader{psuActive});
Tomáš Pecka3f811962023-04-14 10:54:32 +0200148
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200149 /* Ensure that there are sane data after each sysrepo change callback (all the component subtrees are expected). */
150 auto changeSub = client.onModuleChange(
151 "ietf-hardware",
152 [&](sysrepo::Session session, auto, auto, auto, auto, auto) {
Tomáš Peckaf206ab12023-05-11 20:42:10 +0200153 processDsChanges(session, dsChange, {"/ietf-hardware:hardware/last-change"});
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200154 return sysrepo::ErrorCode::Ok;
155 },
156 "/ietf-hardware:hardware/component",
157 0,
158 sysrepo::SubscribeOptions::DoneOnly);
Tomáš Peckacb089ac2023-04-21 14:54:26 +0200159
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200160 // first batch of values
161 cpuTempValue = 41800;
162 powerValue = 14000000;
163 psuActive = true;
164 REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
165 REQUIRE_CALL(*sysfsPower, attribute("power1_input")).LR_RETURN(powerValue).TIMES(AT_LEAST(1));
166 REQUIRE_CALL(dsChange, change(std::map<std::string, std::variant<std::string, Deleted>>{
167 {"/ietf-hardware:hardware", "(container)"},
168 {"/ietf-hardware:hardware/component[name='ne']", "(list instance)"},
169 {"/ietf-hardware:hardware/component[name='ne']/class", "iana-hardware:chassis"},
170 {"/ietf-hardware:hardware/component[name='ne']/mfg-name", "CESNET"},
171 {"/ietf-hardware:hardware/component[name='ne']/name", "ne"},
172 {"/ietf-hardware:hardware/component[name='ne']/state", "(container)"},
173 {"/ietf-hardware:hardware/component[name='ne']/state/oper-state", "enabled"},
174 {"/ietf-hardware:hardware/component[name='ne:power']", "(list instance)"},
175 {"/ietf-hardware:hardware/component[name='ne:power']/class", "iana-hardware:sensor"},
176 {"/ietf-hardware:hardware/component[name='ne:power']/name", "ne:power"},
177 {"/ietf-hardware:hardware/component[name='ne:power']/parent", "ne"},
178 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data", "(container)"},
179 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/oper-status", "ok"},
180 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "14000000"},
181 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-precision", "0"},
182 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-scale", "micro"},
183 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-type", "watts"},
184 {"/ietf-hardware:hardware/component[name='ne:power']/state", "(container)"},
185 {"/ietf-hardware:hardware/component[name='ne:power']/state/oper-state", "enabled"},
186 {"/ietf-hardware:hardware/component[name='ne:psu']", "(list instance)"},
187 {"/ietf-hardware:hardware/component[name='ne:psu']/class", "iana-hardware:power-supply"},
188 {"/ietf-hardware:hardware/component[name='ne:psu']/name", "ne:psu"},
189 {"/ietf-hardware:hardware/component[name='ne:psu']/parent", "ne"},
190 {"/ietf-hardware:hardware/component[name='ne:psu']/state", "(container)"},
191 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "enabled"},
192 {"/ietf-hardware:hardware/component[name='ne:psu:child']", "(list instance)"},
193 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", "iana-hardware:sensor"},
194 {"/ietf-hardware:hardware/component[name='ne:psu:child']/name", "ne:psu:child"},
195 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", "ne:psu"},
196 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", "(container)"},
197 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", "ok"},
198 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", "12000"},
199 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", "0"},
200 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", "milli"},
201 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", "volts-DC"},
202 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", "(container)"},
203 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", "enabled"},
204 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']", "(list instance)"},
205 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/class", "iana-hardware:sensor"},
206 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/name", "ne:temperature-cpu"},
207 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/parent", "ne"},
208 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data", "(container)"},
209 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/oper-status", "ok"},
210 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value", "41800"},
211 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-precision", "0"},
212 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-scale", "milli"},
213 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-type", "celsius"},
214 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/state", "(container)"},
215 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/state/oper-state", "enabled"},
216 }))
217 .IN_SEQUENCE(seq1);
Tomáš Pecka3f811962023-04-14 10:54:32 +0200218
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200219 auto ietfHardwareSysrepo = std::make_shared<velia::ietf_hardware::sysrepo::Sysrepo>(srSess, ietfHardware, 150ms);
220 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
221
222 std::string lastChange = directLeafNodeQuery(modulePrefix + "/last-change");
223
224 // second batch of values, sensor data changed, PSU ejected
225 REQUIRE_CALL(dsChange, change(std::map<std::string, std::variant<std::string, Deleted>>{
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200226 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", Deleted{}},
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200227 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", Deleted{}},
228 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", Deleted{}},
229 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", Deleted{}},
230 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", Deleted{}},
231 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", Deleted{}},
232 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", Deleted{}},
233 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", Deleted{}},
234 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", Deleted{}},
235 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", Deleted{}},
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200236 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "11222333"},
237 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "disabled"},
238 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value", "222"},
239 }))
240 .IN_SEQUENCE(seq1);
241 REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
242 REQUIRE_CALL(*sysfsPower, attribute("power1_input")).LR_RETURN(powerValue).TIMES(AT_LEAST(1));
243 cpuTempValue = 222;
244 powerValue = 11222333;
245 psuActive = false;
246
247 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
248 REQUIRE(directLeafNodeQuery(modulePrefix + "/last-change") > lastChange); // check that last-change leaf has timestamp that is greater than the previous one
249
250 // third batch of changes, wild PSU appears
251 REQUIRE_CALL(dsChange, change(std::map<std::string, std::variant<std::string, Deleted>>{
252 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "enabled"},
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200253 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", "iana-hardware:sensor"},
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200254 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", "ne:psu"},
255 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", "(container)"},
256 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", "ok"},
257 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", "12000"},
258 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", "0"},
259 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", "milli"},
260 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", "volts-DC"},
261 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", "(container)"},
262 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", "enabled"},
263 }))
264 .IN_SEQUENCE(seq1);
265 psuActive = true;
266
267 waitForCompletionAndBitMore(seq1);
Tomáš Pecka3f811962023-04-14 10:54:32 +0200268}