blob: 112ace9e1776374f6e5d1af0e2522c2d261044c8 [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
Jan Kundráta33cf082019-03-28 11:55:57 +01009#include "trompeloeil_doctest.h"
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;
53 SysrepoSubscription subscription(&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át45f4a822019-05-29 21:10:50 +0200141 SECTION("create a list instance")
142 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100143 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']", std::nullopt, ""s));
144 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']/name", std::nullopt, "Nguyen"s));
Václav Kubernát45f4a822019-05-29 21:10:50 +0200145 datastore.createListInstance("/example-schema:person[name='Nguyen']");
146 datastore.commitChanges();
147 }
148
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200149 SECTION("leafref pointing to a key of a list")
150 {
151 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100152 REQUIRE_CALL(mock, write("/example-schema:person[name='Dan']", std::nullopt, ""s));
153 REQUIRE_CALL(mock, write("/example-schema:person[name='Dan']/name", std::nullopt, "Dan"s));
154 REQUIRE_CALL(mock, write("/example-schema:person[name='Elfi']", std::nullopt, ""s));
155 REQUIRE_CALL(mock, write("/example-schema:person[name='Elfi']/name", std::nullopt, "Elfi"s));
156 REQUIRE_CALL(mock, write("/example-schema:person[name='Kolafa']", std::nullopt, ""s));
157 REQUIRE_CALL(mock, write("/example-schema:person[name='Kolafa']/name", std::nullopt, "Kolafa"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200158 datastore.createListInstance("/example-schema:person[name='Dan']");
159 datastore.createListInstance("/example-schema:person[name='Elfi']");
160 datastore.createListInstance("/example-schema:person[name='Kolafa']");
161 datastore.commitChanges();
162 }
163
164 // The commitChanges method has to be called in each of the
165 // SECTIONs, because the REQUIRE_CALL only works inside the given
166 // SECTION.
167 SECTION("Dan")
168 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100169 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Dan"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200170 datastore.setLeaf("/example-schema:bossPerson", std::string{"Dan"});
171 datastore.commitChanges();
172 }
173
174 SECTION("Elfi")
175 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100176 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Elfi"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200177 datastore.setLeaf("/example-schema:bossPerson", std::string{"Elfi"});
178 datastore.commitChanges();
179 }
180
181 SECTION("Kolafa")
182 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100183 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Kolafa"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200184 datastore.setLeaf("/example-schema:bossPerson", std::string{"Kolafa"});
185 datastore.commitChanges();
186 }
187 }
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200188 SECTION("bool values get correctly represented as bools")
189 {
190 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100191 REQUIRE_CALL(mock, write("/example-schema:down", std::nullopt, "true"s));
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200192 datastore.setLeaf("/example-schema:down", bool{true});
193 datastore.commitChanges();
194 }
195
Jan Kundrátb331b552020-01-23 15:25:29 +0100196 DatastoreAccess::Tree expected{{"/example-schema:down", bool{true}}};
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200197 REQUIRE(datastore.getItems("/example-schema:down") == expected);
198 }
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200199
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200200 SECTION("getting items from the whole module")
201 {
202 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100203 REQUIRE_CALL(mock, write("/example-schema:up", std::nullopt, "true"s));
204 REQUIRE_CALL(mock, write("/example-schema:down", std::nullopt, "false"s));
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200205 datastore.setLeaf("/example-schema:up", bool{true});
206 datastore.setLeaf("/example-schema:down", bool{false});
207 datastore.commitChanges();
208 }
209
Jan Kundrátb331b552020-01-23 15:25:29 +0100210 DatastoreAccess::Tree expected{{"/example-schema:down", bool{false}},
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200211 // Sysrepo always returns containers when getting values, but
212 // libnetconf does not. This is fine by the YANG standard:
213 // https://tools.ietf.org/html/rfc7950#section-7.5.7 Furthermore,
214 // NetconfAccess implementation actually only iterates over leafs,
215 // so even if libnetconf did include containers, they wouldn't get
216 // shown here anyway. With sysrepo2, this won't be necessary,
217 // because it'll use the same data structure as libnetconf, so the
218 // results will be consistent.
219#ifdef sysrepo_BACKEND
Václav Kubernát144729d2020-01-08 15:20:35 +0100220 {"/example-schema:lol", special_{SpecialValue::Container}},
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200221#endif
222 {"/example-schema:up", bool{true}}};
223 REQUIRE(datastore.getItems("/example-schema:*") == expected);
224 }
225
Václav Kubernát152ce222019-12-19 12:23:32 +0100226 SECTION("getItems returns correct datatypes")
227 {
228 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100229 REQUIRE_CALL(mock, write("/example-schema:leafEnum", std::nullopt, "lol"s));
Václav Kubernát152ce222019-12-19 12:23:32 +0100230 datastore.setLeaf("/example-schema:leafEnum", enum_{"lol"});
231 datastore.commitChanges();
232 }
Jan Kundrátb331b552020-01-23 15:25:29 +0100233 DatastoreAccess::Tree expected{{"/example-schema:leafEnum", enum_{"lol"}}};
Václav Kubernát152ce222019-12-19 12:23:32 +0100234
235 REQUIRE(datastore.getItems("/example-schema:leafEnum") == expected);
236 }
237
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100238 SECTION("getItems on a list")
239 {
240 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100241 REQUIRE_CALL(mock, write("/example-schema:person[name='Jan']", std::nullopt, ""s));
242 REQUIRE_CALL(mock, write("/example-schema:person[name='Jan']/name", std::nullopt, "Jan"s));
243 REQUIRE_CALL(mock, write("/example-schema:person[name='Michal']", std::nullopt, ""s));
244 REQUIRE_CALL(mock, write("/example-schema:person[name='Michal']/name", std::nullopt, "Michal"s));
245 REQUIRE_CALL(mock, write("/example-schema:person[name='Petr']", std::nullopt, ""s));
246 REQUIRE_CALL(mock, write("/example-schema:person[name='Petr']/name", std::nullopt, "Petr"s));
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100247 datastore.createListInstance("/example-schema:person[name='Jan']");
248 datastore.createListInstance("/example-schema:person[name='Michal']");
249 datastore.createListInstance("/example-schema:person[name='Petr']");
250 datastore.commitChanges();
251 }
Jan Kundrátb331b552020-01-23 15:25:29 +0100252 DatastoreAccess::Tree expected{
Václav Kubernát144729d2020-01-08 15:20:35 +0100253 {"/example-schema:person[name='Jan']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100254 {"/example-schema:person[name='Jan']/name", std::string{"Jan"}},
Václav Kubernát144729d2020-01-08 15:20:35 +0100255 {"/example-schema:person[name='Michal']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100256 {"/example-schema:person[name='Michal']/name", std::string{"Michal"}},
Václav Kubernát144729d2020-01-08 15:20:35 +0100257 {"/example-schema:person[name='Petr']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100258 {"/example-schema:person[name='Petr']/name", std::string{"Petr"}}
259 };
260
261 REQUIRE(datastore.getItems("/example-schema:person") == expected);
262 }
263
Václav Kubernát69aabe92020-01-24 16:53:12 +0100264 SECTION("presence containers")
265 {
266 DatastoreAccess::Tree expected;
267 // Make sure it's not there before we create it
268 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
269
270 {
271 REQUIRE_CALL(mock, write("/example-schema:pContainer", std::nullopt, ""s));
272 datastore.createPresenceContainer("/example-schema:pContainer");
273 datastore.commitChanges();
274 }
275 expected = {
276 {"/example-schema:pContainer", special_{SpecialValue::PresenceContainer}}
277 };
278 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
279
280 // Make sure it's not there after we delete it
281 {
282 REQUIRE_CALL(mock, write("/example-schema:pContainer", ""s, std::nullopt));
283 datastore.deletePresenceContainer("/example-schema:pContainer");
284 datastore.commitChanges();
285 }
286 expected = {};
287 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
288
289 }
290
Václav Kubernát73109382018-09-14 19:52:03 +0200291 waitForCompletionAndBitMore(seq1);
292}
Jan Kundrát6ee84792020-01-24 01:43:36 +0100293
294class RpcCb: public sysrepo::Callback {
295 int rpc(const char *xpath, const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void *) override
296 {
297 const auto nukes = "/example-schema:launch-nukes"s;
298 if (xpath == "/example-schema:noop"s) {
299 return SR_ERR_OK;
300 } else if (xpath == nukes) {
301 uint64_t kilotons = 0;
302 bool hasCities = false;
303 for (size_t i = 0; i < input->val_cnt(); ++i) {
304 const auto& val = input->val(i);
305 if (val->xpath() == nukes + "/payload/kilotons") {
306 kilotons = val->data()->get_uint64();
307 } else if (val->xpath() == nukes + "/payload") {
308 // ignore, container
309 } else if (val->xpath() == nukes + "/description") {
310 // unused
311 } else if (std::string_view{val->xpath()}.find(nukes + "/cities") == 0) {
312 hasCities = true;
313 } else {
314 throw std::runtime_error("RPC launch-nukes: unexpected input "s + val->xpath());
315 }
316 }
317 if (kilotons == 333'666) {
318 // magic, just do not generate any output. This is important because the NETCONF RPC returns just <ok/>.
319 return SR_ERR_OK;
320 }
321 auto buf = output->allocate(2);
322 size_t i = 0;
323 buf->val(i++)->set((nukes + "/blast-radius").c_str(), uint32_t{33'666});
324 buf->val(i++)->set((nukes + "/actual-yield").c_str(), static_cast<uint64_t>(1.33 * kilotons));
325 if (hasCities) {
326 buf = output->reallocate(output->val_cnt() + 2);
327 buf->val(i++)->set((nukes + "/damaged-places/targets[city='London']/city").c_str(), "London");
328 buf->val(i++)->set((nukes + "/damaged-places/targets[city='Berlin']/city").c_str(), "Berlin");
329 }
330 return SR_ERR_OK;
331 }
332 throw std::runtime_error("unrecognized RPC");
333 }
334};
335
336TEST_CASE("rpc") {
337 trompeloeil::sequence seq1;
338 auto srConn = std::make_shared<sysrepo::Connection>("netconf-cli-test-rpc");
339 auto srSession = std::make_shared<sysrepo::Session>(srConn);
340 auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSession);
341 auto cb = std::make_shared<RpcCb>();
342 sysrepo::Logs{}.set_stderr(SR_LL_INF);
343 srSubscription->rpc_subscribe("/example-schema:noop", cb, nullptr, SR_SUBSCR_CTX_REUSE);
344 srSubscription->rpc_subscribe("/example-schema:launch-nukes", cb, nullptr, SR_SUBSCR_CTX_REUSE);
345
346#ifdef sysrepo_BACKEND
347 SysrepoAccess datastore("netconf-cli-test");
348#elif defined(netconf_BACKEND)
349 NetconfAccess datastore(NETOPEER_SOCKET_PATH);
350#else
351#error "Unknown backend"
352#endif
353
354 std::string rpc;
355 DatastoreAccess::Tree input, output;
356
357 SECTION("noop") {
358 rpc = "/example-schema:noop";
359 }
360
361 SECTION("small nuke") {
362 rpc = "/example-schema:launch-nukes";
363 input = {
364 {"description", "dummy"s},
365 {"payload/kilotons", uint64_t{333'666}},
366 };
367 // no data are returned
368 }
369
370 SECTION("small nuke") {
371 rpc = "/example-schema:launch-nukes";
372 input = {
373 {"description", "dummy"s},
374 {"payload/kilotons", uint64_t{4}},
375 };
376 output = {
377 {"blast-radius", uint32_t{33'666}},
378 {"actual-yield", uint64_t{5}},
379 };
380 }
381
382 SECTION("with lists") {
383 rpc = "/example-schema:launch-nukes";
384 input = {
385 {"payload/kilotons", uint64_t{6}},
386 {"cities/targets[city='Prague']/city", "Prague"s},
387 };
388 output = {
389 {"blast-radius", uint32_t{33'666}},
390 {"actual-yield", uint64_t{7}},
391 {"damaged-places", special_{SpecialValue::PresenceContainer}},
392 {"damaged-places/targets[city='London']", special_{SpecialValue::List}},
393 {"damaged-places/targets[city='London']/city", "London"s},
394 {"damaged-places/targets[city='Berlin']", special_{SpecialValue::List}},
395 {"damaged-places/targets[city='Berlin']/city", "Berlin"s},
396 };
397 }
398
399 REQUIRE(datastore.executeRpc(rpc, input) == output);
400
401 waitForCompletionAndBitMore(seq1);
402}