blob: e97aade346e4b201b69202bd745c83141d094daa [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áš Pecka3f811962023-04-14 10:54:32 +020056TEST_CASE("IETF Hardware with sysrepo")
57{
58 TEST_SYSREPO_INIT_LOGS;
59 TEST_SYSREPO_INIT;
60 TEST_SYSREPO_INIT_CLIENT;
61 static const auto modulePrefix = "/ietf-hardware:hardware"s;
62
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020063 client.switchDatastore(sysrepo::Datastore::Operational);
64
65 DatastoreChange dsChange;
66
Tomáš Pecka3f811962023-04-14 10:54:32 +020067 trompeloeil::sequence seq1;
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020068
69 auto directLeafNodeQuery = [&](const std::string& xpath) {
70 auto val = client.getData(xpath);
71 REQUIRE(val);
72 return std::string{val->findPath(xpath)->asTerm().valueStr()};
73 };
Tomáš Pecka3f811962023-04-14 10:54:32 +020074
Tomáš Pecka3f811962023-04-14 10:54:32 +020075 auto sysfsTempCpu = std::make_shared<FakeHWMon>();
Tomáš Pecka3f811962023-04-14 10:54:32 +020076 auto sysfsPower = std::make_shared<FakeHWMon>();
Tomáš Pecka3f811962023-04-14 10:54:32 +020077
Tomáš Pecka3f811962023-04-14 10:54:32 +020078 using velia::ietf_hardware::data_reader::SensorType;
79 using velia::ietf_hardware::data_reader::StaticData;
80 using velia::ietf_hardware::data_reader::SysfsValue;
Tomáš Peckae5366c62023-04-14 11:03:04 +020081
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020082 std::atomic<bool> psuActive; // this needs to be destroyed after ietfHardware to avoid dangling reference (we are passing it as a ref to PsuDataReader)
83 std::atomic<int64_t> cpuTempValue;
84 std::atomic<int64_t> powerValue;
85
Tomáš Pecka3f811962023-04-14 10:54:32 +020086 // register components into hw state
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020087 auto ietfHardware = std::make_shared<velia::ietf_hardware::IETFHardware>();
Tomáš Pecka3f811962023-04-14 10:54:32 +020088 ietfHardware->registerDataReader(StaticData("ne", std::nullopt, {{"class", "iana-hardware:chassis"}, {"mfg-name", "CESNET"s}}));
Tomáš Peckae5366c62023-04-14 11:03:04 +020089 ietfHardware->registerDataReader(SysfsValue<SensorType::Temperature>("ne:temperature-cpu", "ne", sysfsTempCpu, 1));
90 ietfHardware->registerDataReader(SysfsValue<SensorType::Power>("ne:power", "ne", sysfsPower, 1));
Tomáš Pecka3f811962023-04-14 10:54:32 +020091
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +020092 /* 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
93 * data might not even be pushed (e.g. the child sensors).
94 * Since we push data into sysrepo we have to erase old data (that should no longer be present) from the sysrepo operational DS.
95 * We test such situation via the following data reader which returns data only when psuActive is set to true.
96 */
97 struct PsuDataReader {
98 const std::atomic<bool>& active;
Tomáš Pecka3f811962023-04-14 10:54:32 +020099
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200100 velia::ietf_hardware::DataTree operator()()
101 {
102 velia::ietf_hardware::DataTree res = {
103 {"/ietf-hardware:hardware/component[name='ne:psu']/class", "iana-hardware:power-supply"},
104 {"/ietf-hardware:hardware/component[name='ne:psu']/parent", "ne"},
105 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "disabled"},
106 };
Tomáš Pecka3f811962023-04-14 10:54:32 +0200107
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200108 if (active) {
109 res["/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state"] = "enabled";
110 res["/ietf-hardware:hardware/component[name='ne:psu:child']/class"] = "iana-hardware:sensor";
111 res["/ietf-hardware:hardware/component[name='ne:psu:child']/parent"] = "ne:psu";
112 res["/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state"] = "enabled";
113 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status"] = "ok";
114 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value"] = "12000";
115 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision"] = "0";
116 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale"] = "milli";
117 res["/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type"] = "volts-DC";
118 }
Tomáš Pecka3f811962023-04-14 10:54:32 +0200119
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200120 return res;
121 }
Tomáš Pecka4886db22023-05-10 10:46:15 +0200122
123 velia::ietf_hardware::ThresholdsBySensorPath thresholds() const
124 {
125 return {{"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", {}}};
126 }
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200127 };
128 ietfHardware->registerDataReader(PsuDataReader{psuActive});
Tomáš Pecka3f811962023-04-14 10:54:32 +0200129
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200130 /* Ensure that there are sane data after each sysrepo change callback (all the component subtrees are expected). */
131 auto changeSub = client.onModuleChange(
132 "ietf-hardware",
133 [&](sysrepo::Session session, auto, auto, auto, auto, auto) {
134 std::map<std::string, std::variant<std::string, Deleted>> changes;
Tomáš Pecka3f811962023-04-14 10:54:32 +0200135
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200136 for (const auto& change : session.getChanges()) {
137 if (change.node.path() == "/ietf-hardware:hardware/last-change") { // skip timestamp changes - we can't test them properly in expectations with current code
138 continue;
139 }
Tomáš Peckacb089ac2023-04-21 14:54:26 +0200140
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200141 if (change.operation == sysrepo::ChangeOperation::Deleted) {
142 changes.emplace(change.node.path(), Deleted());
143 } else {
144 changes.emplace(change.node.path(), nodeAsString(change.node));
145 }
146 }
Tomáš Peckacb089ac2023-04-21 14:54:26 +0200147
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200148 dsChange.change(changes);
149 return sysrepo::ErrorCode::Ok;
150 },
151 "/ietf-hardware:hardware/component",
152 0,
153 sysrepo::SubscribeOptions::DoneOnly);
Tomáš Peckacb089ac2023-04-21 14:54:26 +0200154
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200155 // first batch of values
156 cpuTempValue = 41800;
157 powerValue = 14000000;
158 psuActive = true;
159 REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
160 REQUIRE_CALL(*sysfsPower, attribute("power1_input")).LR_RETURN(powerValue).TIMES(AT_LEAST(1));
161 REQUIRE_CALL(dsChange, change(std::map<std::string, std::variant<std::string, Deleted>>{
162 {"/ietf-hardware:hardware", "(container)"},
163 {"/ietf-hardware:hardware/component[name='ne']", "(list instance)"},
164 {"/ietf-hardware:hardware/component[name='ne']/class", "iana-hardware:chassis"},
165 {"/ietf-hardware:hardware/component[name='ne']/mfg-name", "CESNET"},
166 {"/ietf-hardware:hardware/component[name='ne']/name", "ne"},
167 {"/ietf-hardware:hardware/component[name='ne']/state", "(container)"},
168 {"/ietf-hardware:hardware/component[name='ne']/state/oper-state", "enabled"},
169 {"/ietf-hardware:hardware/component[name='ne:power']", "(list instance)"},
170 {"/ietf-hardware:hardware/component[name='ne:power']/class", "iana-hardware:sensor"},
171 {"/ietf-hardware:hardware/component[name='ne:power']/name", "ne:power"},
172 {"/ietf-hardware:hardware/component[name='ne:power']/parent", "ne"},
173 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data", "(container)"},
174 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/oper-status", "ok"},
175 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "14000000"},
176 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-precision", "0"},
177 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-scale", "micro"},
178 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value-type", "watts"},
179 {"/ietf-hardware:hardware/component[name='ne:power']/state", "(container)"},
180 {"/ietf-hardware:hardware/component[name='ne:power']/state/oper-state", "enabled"},
181 {"/ietf-hardware:hardware/component[name='ne:psu']", "(list instance)"},
182 {"/ietf-hardware:hardware/component[name='ne:psu']/class", "iana-hardware:power-supply"},
183 {"/ietf-hardware:hardware/component[name='ne:psu']/name", "ne:psu"},
184 {"/ietf-hardware:hardware/component[name='ne:psu']/parent", "ne"},
185 {"/ietf-hardware:hardware/component[name='ne:psu']/state", "(container)"},
186 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "enabled"},
187 {"/ietf-hardware:hardware/component[name='ne:psu:child']", "(list instance)"},
188 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", "iana-hardware:sensor"},
189 {"/ietf-hardware:hardware/component[name='ne:psu:child']/name", "ne:psu:child"},
190 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", "ne:psu"},
191 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", "(container)"},
192 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", "ok"},
193 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", "12000"},
194 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", "0"},
195 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", "milli"},
196 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", "volts-DC"},
197 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", "(container)"},
198 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", "enabled"},
199 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']", "(list instance)"},
200 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/class", "iana-hardware:sensor"},
201 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/name", "ne:temperature-cpu"},
202 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/parent", "ne"},
203 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data", "(container)"},
204 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/oper-status", "ok"},
205 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value", "41800"},
206 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-precision", "0"},
207 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-scale", "milli"},
208 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value-type", "celsius"},
209 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/state", "(container)"},
210 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/state/oper-state", "enabled"},
211 }))
212 .IN_SEQUENCE(seq1);
Tomáš Pecka3f811962023-04-14 10:54:32 +0200213
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200214 auto ietfHardwareSysrepo = std::make_shared<velia::ietf_hardware::sysrepo::Sysrepo>(srSess, ietfHardware, 150ms);
215 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
216
217 std::string lastChange = directLeafNodeQuery(modulePrefix + "/last-change");
218
219 // second batch of values, sensor data changed, PSU ejected
220 REQUIRE_CALL(dsChange, change(std::map<std::string, std::variant<std::string, Deleted>>{
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200221 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", Deleted{}},
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200222 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", Deleted{}},
223 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", Deleted{}},
224 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", Deleted{}},
225 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", Deleted{}},
226 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", Deleted{}},
227 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", Deleted{}},
228 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", Deleted{}},
229 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", Deleted{}},
230 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", Deleted{}},
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200231 {"/ietf-hardware:hardware/component[name='ne:power']/sensor-data/value", "11222333"},
232 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "disabled"},
233 {"/ietf-hardware:hardware/component[name='ne:temperature-cpu']/sensor-data/value", "222"},
234 }))
235 .IN_SEQUENCE(seq1);
236 REQUIRE_CALL(*sysfsTempCpu, attribute("temp1_input")).LR_RETURN(cpuTempValue).TIMES(AT_LEAST(1));
237 REQUIRE_CALL(*sysfsPower, attribute("power1_input")).LR_RETURN(powerValue).TIMES(AT_LEAST(1));
238 cpuTempValue = 222;
239 powerValue = 11222333;
240 psuActive = false;
241
242 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
243 REQUIRE(directLeafNodeQuery(modulePrefix + "/last-change") > lastChange); // check that last-change leaf has timestamp that is greater than the previous one
244
245 // third batch of changes, wild PSU appears
246 REQUIRE_CALL(dsChange, change(std::map<std::string, std::variant<std::string, Deleted>>{
247 {"/ietf-hardware:hardware/component[name='ne:psu']/state/oper-state", "enabled"},
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200248 {"/ietf-hardware:hardware/component[name='ne:psu:child']/class", "iana-hardware:sensor"},
Tomáš Pecka43ef7ba2023-04-13 15:56:48 +0200249 {"/ietf-hardware:hardware/component[name='ne:psu:child']/parent", "ne:psu"},
250 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data", "(container)"},
251 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/oper-status", "ok"},
252 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", "12000"},
253 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-precision", "0"},
254 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-scale", "milli"},
255 {"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value-type", "volts-DC"},
256 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state", "(container)"},
257 {"/ietf-hardware:hardware/component[name='ne:psu:child']/state/oper-state", "enabled"},
258 }))
259 .IN_SEQUENCE(seq1);
260 psuActive = true;
261
262 waitForCompletionAndBitMore(seq1);
Tomáš Pecka3f811962023-04-14 10:54:32 +0200263}