blob: fe7df811f9bd344cd9f816450c4e99764bbfdc93 [file] [log] [blame]
Václav Kubernát73109382018-09-14 19:52:03 +02001/*
2 * Copyright (C) 2018 CESNET, https://photonics.cesnet.cz/
3 * Copyright (C) 2018 FIT CVUT, https://fit.cvut.cz/
4 *
5 * Written by Václav Kubernát <kubervac@fit.cvut.cz>
6 *
7*/
8
Václav Kubernát26b56082020-02-03 18:28:56 +01009#include "trompeloeil_doctest.hpp"
Jan Kundrát6ee84792020-01-24 01:43:36 +010010#include <sysrepo-cpp/Session.hpp>
Václav Kubernát73109382018-09-14 19:52:03 +020011
Václav Kubernátc31bd602019-03-07 11:44:48 +010012#ifdef sysrepo_BACKEND
Václav Kubernát73109382018-09-14 19:52:03 +020013#include "sysrepo_access.hpp"
Václav Kubernátc31bd602019-03-07 11:44:48 +010014#elif defined(netconf_BACKEND)
15#include "netconf_access.hpp"
16#include "netopeer_vars.hpp"
17#else
18#error "Unknown backend"
19#endif
Václav Kubernát73109382018-09-14 19:52:03 +020020#include "sysrepo_subscription.hpp"
Václav Kubernát8e121ff2019-10-15 15:47:45 +020021#include "utils.hpp"
Václav Kubernát73109382018-09-14 19:52:03 +020022
Jan Kundrát6ee84792020-01-24 01:43:36 +010023using namespace std::literals::string_literals;
24
Václav Kubernát69aabe92020-01-24 16:53:12 +010025class MockRecorder : public trompeloeil::mock_interface<Recorder> {
Václav Kubernát73109382018-09-14 19:52:03 +020026public:
Václav Kubernát69aabe92020-01-24 16:53:12 +010027 IMPLEMENT_MOCK3(write);
Václav Kubernát73109382018-09-14 19:52:03 +020028};
29
Václav Kubernát8e121ff2019-10-15 15:47:45 +020030namespace std {
Václav Kubernát69aabe92020-01-24 16:53:12 +010031std::ostream& operator<<(std::ostream& s, const std::optional<std::string>& opt)
32{
33 s << (opt ? *opt : "std::nullopt");
34 return s;
35}
36
Jan Kundrátb331b552020-01-23 15:25:29 +010037std::ostream& operator<<(std::ostream& s, const DatastoreAccess::Tree& map)
Václav Kubernát8e121ff2019-10-15 15:47:45 +020038{
39 s << std::endl
40 << "{";
41 for (const auto& it : map) {
42 s << "{\"" << it.first << "\", " << leafDataToString(it.second) << "}" << std::endl;
43 }
44 s << "}" << std::endl;
45 return s;
46}
47}
48
49TEST_CASE("setting/getting values")
Václav Kubernát73109382018-09-14 19:52:03 +020050{
Václav Kubernát73109382018-09-14 19:52:03 +020051 trompeloeil::sequence seq1;
52 MockRecorder mock;
Václav Kubernátab612e92019-11-26 19:51:31 +010053 SysrepoSubscription subscription("example-schema", &mock);
Václav Kubernátc31bd602019-03-07 11:44:48 +010054
55#ifdef sysrepo_BACKEND
Václav Kubernát73109382018-09-14 19:52:03 +020056 SysrepoAccess datastore("netconf-cli-test");
Václav Kubernátc31bd602019-03-07 11:44:48 +010057#elif defined(netconf_BACKEND)
58 NetconfAccess datastore(NETOPEER_SOCKET_PATH);
59#else
60#error "Unknown backend"
61#endif
Václav Kubernát73109382018-09-14 19:52:03 +020062
Václav Kubernát69aabe92020-01-24 16:53:12 +010063
Václav Kubernát134d78f2019-09-03 16:42:29 +020064 SECTION("set leafInt8 to -128")
Václav Kubernát73109382018-09-14 19:52:03 +020065 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010066 REQUIRE_CALL(mock, write("/example-schema:leafInt8", std::nullopt, "-128"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020067 datastore.setLeaf("/example-schema:leafInt8", int8_t{-128});
68 datastore.commitChanges();
69 }
70
71 SECTION("set leafInt16 to -32768")
72 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010073 REQUIRE_CALL(mock, write("/example-schema:leafInt16", std::nullopt, "-32768"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020074 datastore.setLeaf("/example-schema:leafInt16", int16_t{-32768});
75 datastore.commitChanges();
76 }
77
78 SECTION("set leafInt32 to -2147483648")
79 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010080 REQUIRE_CALL(mock, write("/example-schema:leafInt32", std::nullopt, "-2147483648"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020081 datastore.setLeaf("/example-schema:leafInt32", int32_t{-2147483648});
82 datastore.commitChanges();
83 }
84
85 SECTION("set leafInt64 to -50000000000")
86 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010087 REQUIRE_CALL(mock, write("/example-schema:leafInt64", std::nullopt, "-50000000000"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020088 datastore.setLeaf("/example-schema:leafInt64", int64_t{-50000000000});
89 datastore.commitChanges();
90 }
91
92 SECTION("set leafUInt8 to 255")
93 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010094 REQUIRE_CALL(mock, write("/example-schema:leafUInt8", std::nullopt, "255"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020095 datastore.setLeaf("/example-schema:leafUInt8", uint8_t{255});
96 datastore.commitChanges();
97 }
98
99 SECTION("set leafUInt16 to 65535")
100 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100101 REQUIRE_CALL(mock, write("/example-schema:leafUInt16", std::nullopt, "65535"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200102 datastore.setLeaf("/example-schema:leafUInt16", uint16_t{65535});
103 datastore.commitChanges();
104 }
105
106 SECTION("set leafUInt32 to 4294967295")
107 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100108 REQUIRE_CALL(mock, write("/example-schema:leafUInt32", std::nullopt, "4294967295"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200109 datastore.setLeaf("/example-schema:leafUInt32", uint32_t{4294967295});
110 datastore.commitChanges();
111 }
112
113 SECTION("set leafUInt64 to 50000000000")
114 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100115 REQUIRE_CALL(mock, write("/example-schema:leafUInt64", std::nullopt, "50000000000"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200116 datastore.setLeaf("/example-schema:leafUInt64", uint64_t{50000000000});
Václav Kubernát73109382018-09-14 19:52:03 +0200117 datastore.commitChanges();
118 }
119
120 SECTION("set leafEnum to coze")
121 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100122 REQUIRE_CALL(mock, write("/example-schema:leafEnum", std::nullopt, "coze"s));
Václav Kubernát73109382018-09-14 19:52:03 +0200123 datastore.setLeaf("/example-schema:leafEnum", enum_{"coze"});
124 datastore.commitChanges();
125 }
126
127 SECTION("set leafDecimal to 123.544")
128 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100129 REQUIRE_CALL(mock, write("/example-schema:leafDecimal", std::nullopt, "123.544"s));
Václav Kubernát73109382018-09-14 19:52:03 +0200130 datastore.setLeaf("/example-schema:leafDecimal", 123.544);
131 datastore.commitChanges();
132 }
133
134 SECTION("create presence container")
135 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100136 REQUIRE_CALL(mock, write("/example-schema:pContainer", std::nullopt, ""s));
Václav Kubernát73109382018-09-14 19:52:03 +0200137 datastore.createPresenceContainer("/example-schema:pContainer");
138 datastore.commitChanges();
139 }
140
Václav Kubernátcc7a93f2020-02-04 11:48:15 +0100141 SECTION("create/delete a list instance")
Václav Kubernát45f4a822019-05-29 21:10:50 +0200142 {
Václav Kubernátcc7a93f2020-02-04 11:48:15 +0100143 {
144 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']", std::nullopt, ""s));
145 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']/name", std::nullopt, "Nguyen"s));
146 datastore.createListInstance("/example-schema:person[name='Nguyen']");
147 datastore.commitChanges();
148 }
149 {
150 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']", ""s, std::nullopt));
151 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']/name", "Nguyen"s, std::nullopt));
152 datastore.deleteListInstance("/example-schema:person[name='Nguyen']");
153 datastore.commitChanges();
154 }
Václav Kubernát45f4a822019-05-29 21:10:50 +0200155 }
156
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200157 SECTION("leafref pointing to a key of a list")
158 {
159 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100160 REQUIRE_CALL(mock, write("/example-schema:person[name='Dan']", std::nullopt, ""s));
161 REQUIRE_CALL(mock, write("/example-schema:person[name='Dan']/name", std::nullopt, "Dan"s));
162 REQUIRE_CALL(mock, write("/example-schema:person[name='Elfi']", std::nullopt, ""s));
163 REQUIRE_CALL(mock, write("/example-schema:person[name='Elfi']/name", std::nullopt, "Elfi"s));
164 REQUIRE_CALL(mock, write("/example-schema:person[name='Kolafa']", std::nullopt, ""s));
165 REQUIRE_CALL(mock, write("/example-schema:person[name='Kolafa']/name", std::nullopt, "Kolafa"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200166 datastore.createListInstance("/example-schema:person[name='Dan']");
167 datastore.createListInstance("/example-schema:person[name='Elfi']");
168 datastore.createListInstance("/example-schema:person[name='Kolafa']");
169 datastore.commitChanges();
170 }
171
172 // The commitChanges method has to be called in each of the
173 // SECTIONs, because the REQUIRE_CALL only works inside the given
174 // SECTION.
175 SECTION("Dan")
176 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100177 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Dan"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200178 datastore.setLeaf("/example-schema:bossPerson", std::string{"Dan"});
179 datastore.commitChanges();
180 }
181
182 SECTION("Elfi")
183 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100184 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Elfi"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200185 datastore.setLeaf("/example-schema:bossPerson", std::string{"Elfi"});
186 datastore.commitChanges();
187 }
188
189 SECTION("Kolafa")
190 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100191 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Kolafa"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200192 datastore.setLeaf("/example-schema:bossPerson", std::string{"Kolafa"});
193 datastore.commitChanges();
194 }
195 }
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200196 SECTION("bool values get correctly represented as bools")
197 {
198 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100199 REQUIRE_CALL(mock, write("/example-schema:down", std::nullopt, "true"s));
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200200 datastore.setLeaf("/example-schema:down", bool{true});
201 datastore.commitChanges();
202 }
203
Jan Kundrátb331b552020-01-23 15:25:29 +0100204 DatastoreAccess::Tree expected{{"/example-schema:down", bool{true}}};
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200205 REQUIRE(datastore.getItems("/example-schema:down") == expected);
206 }
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200207
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200208 SECTION("getting items from the whole module")
209 {
210 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100211 REQUIRE_CALL(mock, write("/example-schema:up", std::nullopt, "true"s));
212 REQUIRE_CALL(mock, write("/example-schema:down", std::nullopt, "false"s));
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200213 datastore.setLeaf("/example-schema:up", bool{true});
214 datastore.setLeaf("/example-schema:down", bool{false});
215 datastore.commitChanges();
216 }
217
Jan Kundrátb331b552020-01-23 15:25:29 +0100218 DatastoreAccess::Tree expected{{"/example-schema:down", bool{false}},
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200219 // Sysrepo always returns containers when getting values, but
220 // libnetconf does not. This is fine by the YANG standard:
221 // https://tools.ietf.org/html/rfc7950#section-7.5.7 Furthermore,
222 // NetconfAccess implementation actually only iterates over leafs,
223 // so even if libnetconf did include containers, they wouldn't get
224 // shown here anyway. With sysrepo2, this won't be necessary,
225 // because it'll use the same data structure as libnetconf, so the
226 // results will be consistent.
227#ifdef sysrepo_BACKEND
Václav Kubernát144729d2020-01-08 15:20:35 +0100228 {"/example-schema:lol", special_{SpecialValue::Container}},
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200229#endif
230 {"/example-schema:up", bool{true}}};
231 REQUIRE(datastore.getItems("/example-schema:*") == expected);
232 }
233
Václav Kubernát152ce222019-12-19 12:23:32 +0100234 SECTION("getItems returns correct datatypes")
235 {
236 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100237 REQUIRE_CALL(mock, write("/example-schema:leafEnum", std::nullopt, "lol"s));
Václav Kubernát152ce222019-12-19 12:23:32 +0100238 datastore.setLeaf("/example-schema:leafEnum", enum_{"lol"});
239 datastore.commitChanges();
240 }
Jan Kundrátb331b552020-01-23 15:25:29 +0100241 DatastoreAccess::Tree expected{{"/example-schema:leafEnum", enum_{"lol"}}};
Václav Kubernát152ce222019-12-19 12:23:32 +0100242
243 REQUIRE(datastore.getItems("/example-schema:leafEnum") == expected);
244 }
245
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100246 SECTION("getItems on a list")
247 {
248 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100249 REQUIRE_CALL(mock, write("/example-schema:person[name='Jan']", std::nullopt, ""s));
250 REQUIRE_CALL(mock, write("/example-schema:person[name='Jan']/name", std::nullopt, "Jan"s));
251 REQUIRE_CALL(mock, write("/example-schema:person[name='Michal']", std::nullopt, ""s));
252 REQUIRE_CALL(mock, write("/example-schema:person[name='Michal']/name", std::nullopt, "Michal"s));
253 REQUIRE_CALL(mock, write("/example-schema:person[name='Petr']", std::nullopt, ""s));
254 REQUIRE_CALL(mock, write("/example-schema:person[name='Petr']/name", std::nullopt, "Petr"s));
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100255 datastore.createListInstance("/example-schema:person[name='Jan']");
256 datastore.createListInstance("/example-schema:person[name='Michal']");
257 datastore.createListInstance("/example-schema:person[name='Petr']");
258 datastore.commitChanges();
259 }
Jan Kundrátb331b552020-01-23 15:25:29 +0100260 DatastoreAccess::Tree expected{
Václav Kubernát144729d2020-01-08 15:20:35 +0100261 {"/example-schema:person[name='Jan']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100262 {"/example-schema:person[name='Jan']/name", std::string{"Jan"}},
Václav Kubernát144729d2020-01-08 15:20:35 +0100263 {"/example-schema:person[name='Michal']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100264 {"/example-schema:person[name='Michal']/name", std::string{"Michal"}},
Václav Kubernát144729d2020-01-08 15:20:35 +0100265 {"/example-schema:person[name='Petr']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100266 {"/example-schema:person[name='Petr']/name", std::string{"Petr"}}
267 };
268
269 REQUIRE(datastore.getItems("/example-schema:person") == expected);
270 }
271
Václav Kubernát69aabe92020-01-24 16:53:12 +0100272 SECTION("presence containers")
273 {
274 DatastoreAccess::Tree expected;
275 // Make sure it's not there before we create it
276 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
277
278 {
279 REQUIRE_CALL(mock, write("/example-schema:pContainer", std::nullopt, ""s));
280 datastore.createPresenceContainer("/example-schema:pContainer");
281 datastore.commitChanges();
282 }
283 expected = {
284 {"/example-schema:pContainer", special_{SpecialValue::PresenceContainer}}
285 };
286 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
287
288 // Make sure it's not there after we delete it
289 {
290 REQUIRE_CALL(mock, write("/example-schema:pContainer", ""s, std::nullopt));
291 datastore.deletePresenceContainer("/example-schema:pContainer");
292 datastore.commitChanges();
293 }
294 expected = {};
295 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
296
297 }
298
Jan Kundrátbd3169c2020-02-03 19:31:34 +0100299 SECTION("floats")
300 {
301 datastore.setLeaf("/example-schema:leafDecimal", 123.4);
302 REQUIRE_CALL(mock, write("/example-schema:leafDecimal", std::nullopt, "123.4"s));
303 datastore.commitChanges();
304 DatastoreAccess::Tree expected {
305 {"/example-schema:leafDecimal", 123.4},
306 };
307 REQUIRE(datastore.getItems("/example-schema:leafDecimal") == expected);
308 }
309
Václav Kubernát73109382018-09-14 19:52:03 +0200310 waitForCompletionAndBitMore(seq1);
311}
Jan Kundrát6ee84792020-01-24 01:43:36 +0100312
313class RpcCb: public sysrepo::Callback {
314 int rpc(const char *xpath, const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void *) override
315 {
316 const auto nukes = "/example-schema:launch-nukes"s;
317 if (xpath == "/example-schema:noop"s) {
318 return SR_ERR_OK;
319 } else if (xpath == nukes) {
320 uint64_t kilotons = 0;
321 bool hasCities = false;
322 for (size_t i = 0; i < input->val_cnt(); ++i) {
323 const auto& val = input->val(i);
324 if (val->xpath() == nukes + "/payload/kilotons") {
325 kilotons = val->data()->get_uint64();
326 } else if (val->xpath() == nukes + "/payload") {
327 // ignore, container
328 } else if (val->xpath() == nukes + "/description") {
329 // unused
330 } else if (std::string_view{val->xpath()}.find(nukes + "/cities") == 0) {
331 hasCities = true;
332 } else {
333 throw std::runtime_error("RPC launch-nukes: unexpected input "s + val->xpath());
334 }
335 }
336 if (kilotons == 333'666) {
337 // magic, just do not generate any output. This is important because the NETCONF RPC returns just <ok/>.
338 return SR_ERR_OK;
339 }
340 auto buf = output->allocate(2);
341 size_t i = 0;
342 buf->val(i++)->set((nukes + "/blast-radius").c_str(), uint32_t{33'666});
343 buf->val(i++)->set((nukes + "/actual-yield").c_str(), static_cast<uint64_t>(1.33 * kilotons));
344 if (hasCities) {
345 buf = output->reallocate(output->val_cnt() + 2);
346 buf->val(i++)->set((nukes + "/damaged-places/targets[city='London']/city").c_str(), "London");
347 buf->val(i++)->set((nukes + "/damaged-places/targets[city='Berlin']/city").c_str(), "Berlin");
348 }
349 return SR_ERR_OK;
350 }
351 throw std::runtime_error("unrecognized RPC");
352 }
353};
354
355TEST_CASE("rpc") {
356 trompeloeil::sequence seq1;
357 auto srConn = std::make_shared<sysrepo::Connection>("netconf-cli-test-rpc");
358 auto srSession = std::make_shared<sysrepo::Session>(srConn);
359 auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSession);
360 auto cb = std::make_shared<RpcCb>();
361 sysrepo::Logs{}.set_stderr(SR_LL_INF);
362 srSubscription->rpc_subscribe("/example-schema:noop", cb, nullptr, SR_SUBSCR_CTX_REUSE);
363 srSubscription->rpc_subscribe("/example-schema:launch-nukes", cb, nullptr, SR_SUBSCR_CTX_REUSE);
364
365#ifdef sysrepo_BACKEND
366 SysrepoAccess datastore("netconf-cli-test");
367#elif defined(netconf_BACKEND)
368 NetconfAccess datastore(NETOPEER_SOCKET_PATH);
369#else
370#error "Unknown backend"
371#endif
372
373 std::string rpc;
374 DatastoreAccess::Tree input, output;
375
376 SECTION("noop") {
377 rpc = "/example-schema:noop";
378 }
379
380 SECTION("small nuke") {
381 rpc = "/example-schema:launch-nukes";
382 input = {
383 {"description", "dummy"s},
384 {"payload/kilotons", uint64_t{333'666}},
385 };
386 // no data are returned
387 }
388
389 SECTION("small nuke") {
390 rpc = "/example-schema:launch-nukes";
391 input = {
392 {"description", "dummy"s},
393 {"payload/kilotons", uint64_t{4}},
394 };
395 output = {
396 {"blast-radius", uint32_t{33'666}},
397 {"actual-yield", uint64_t{5}},
398 };
399 }
400
401 SECTION("with lists") {
402 rpc = "/example-schema:launch-nukes";
403 input = {
404 {"payload/kilotons", uint64_t{6}},
405 {"cities/targets[city='Prague']/city", "Prague"s},
406 };
407 output = {
408 {"blast-radius", uint32_t{33'666}},
409 {"actual-yield", uint64_t{7}},
410 {"damaged-places", special_{SpecialValue::PresenceContainer}},
411 {"damaged-places/targets[city='London']", special_{SpecialValue::List}},
412 {"damaged-places/targets[city='London']/city", "London"s},
413 {"damaged-places/targets[city='Berlin']", special_{SpecialValue::List}},
414 {"damaged-places/targets[city='Berlin']/city", "Berlin"s},
415 };
416 }
417
418 REQUIRE(datastore.executeRpc(rpc, input) == output);
419
420 waitForCompletionAndBitMore(seq1);
421}