blob: e9030db64c0a4c686af5b569168b311a4a5de492 [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
Jan Kundrát3867c9e2020-06-18 20:26:45 +020013#define THROWS_ON_INVALID_SCHEMA_PATHS 0
14#define THROWS_ON_NONEXISTING_KEYS 0
Václav Kubernát73109382018-09-14 19:52:03 +020015#include "sysrepo_access.hpp"
Václav Kubernátc31bd602019-03-07 11:44:48 +010016#elif defined(netconf_BACKEND)
Jan Kundrát3867c9e2020-06-18 20:26:45 +020017#define THROWS_ON_INVALID_SCHEMA_PATHS 1
18#define THROWS_ON_NONEXISTING_KEYS 1
Václav Kubernátc31bd602019-03-07 11:44:48 +010019#include "netconf_access.hpp"
20#include "netopeer_vars.hpp"
21#else
22#error "Unknown backend"
23#endif
Jan Kundrátbb525b42020-02-04 11:56:59 +010024#include "pretty_printers.hpp"
Václav Kubernát73109382018-09-14 19:52:03 +020025#include "sysrepo_subscription.hpp"
Václav Kubernát8e121ff2019-10-15 15:47:45 +020026#include "utils.hpp"
Václav Kubernát73109382018-09-14 19:52:03 +020027
Jan Kundrát6ee84792020-01-24 01:43:36 +010028using namespace std::literals::string_literals;
29
Václav Kubernát69aabe92020-01-24 16:53:12 +010030class MockRecorder : public trompeloeil::mock_interface<Recorder> {
Václav Kubernát73109382018-09-14 19:52:03 +020031public:
Václav Kubernát69aabe92020-01-24 16:53:12 +010032 IMPLEMENT_MOCK3(write);
Václav Kubernát73109382018-09-14 19:52:03 +020033};
34
Jan Kundrátbb525b42020-02-04 11:56:59 +010035class MockDataSupplier : public trompeloeil::mock_interface<DataSupplier> {
36public:
37 IMPLEMENT_CONST_MOCK1(get_data);
38};
39
Jan Kundrát3867c9e2020-06-18 20:26:45 +020040template <int Flag, typename Callable> void tryThis(const Callable& what) {
41 if constexpr (Flag) {
42 REQUIRE_THROWS_AS(what(), std::runtime_error);
43 } else {
44 what();
45 }
46}
47
Václav Kubernát8e121ff2019-10-15 15:47:45 +020048TEST_CASE("setting/getting values")
Václav Kubernát73109382018-09-14 19:52:03 +020049{
Václav Kubernát73109382018-09-14 19:52:03 +020050 trompeloeil::sequence seq1;
51 MockRecorder mock;
Václav Kubernátab612e92019-11-26 19:51:31 +010052 SysrepoSubscription subscription("example-schema", &mock);
Václav Kubernátc31bd602019-03-07 11:44:48 +010053
54#ifdef sysrepo_BACKEND
Václav Kubernát715c85c2020-04-14 01:46:08 +020055 SysrepoAccess datastore("netconf-cli-test", Datastore::Running);
Václav Kubernátc31bd602019-03-07 11:44:48 +010056#elif defined(netconf_BACKEND)
57 NetconfAccess datastore(NETOPEER_SOCKET_PATH);
58#else
59#error "Unknown backend"
60#endif
Václav Kubernát73109382018-09-14 19:52:03 +020061
Václav Kubernát69aabe92020-01-24 16:53:12 +010062
Václav Kubernát134d78f2019-09-03 16:42:29 +020063 SECTION("set leafInt8 to -128")
Václav Kubernát73109382018-09-14 19:52:03 +020064 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010065 REQUIRE_CALL(mock, write("/example-schema:leafInt8", std::nullopt, "-128"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020066 datastore.setLeaf("/example-schema:leafInt8", int8_t{-128});
67 datastore.commitChanges();
68 }
69
70 SECTION("set leafInt16 to -32768")
71 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010072 REQUIRE_CALL(mock, write("/example-schema:leafInt16", std::nullopt, "-32768"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020073 datastore.setLeaf("/example-schema:leafInt16", int16_t{-32768});
74 datastore.commitChanges();
75 }
76
77 SECTION("set leafInt32 to -2147483648")
78 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010079 REQUIRE_CALL(mock, write("/example-schema:leafInt32", std::nullopt, "-2147483648"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020080 datastore.setLeaf("/example-schema:leafInt32", int32_t{-2147483648});
81 datastore.commitChanges();
82 }
83
84 SECTION("set leafInt64 to -50000000000")
85 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010086 REQUIRE_CALL(mock, write("/example-schema:leafInt64", std::nullopt, "-50000000000"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020087 datastore.setLeaf("/example-schema:leafInt64", int64_t{-50000000000});
88 datastore.commitChanges();
89 }
90
91 SECTION("set leafUInt8 to 255")
92 {
Václav Kubernát69aabe92020-01-24 16:53:12 +010093 REQUIRE_CALL(mock, write("/example-schema:leafUInt8", std::nullopt, "255"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +020094 datastore.setLeaf("/example-schema:leafUInt8", uint8_t{255});
95 datastore.commitChanges();
96 }
97
98 SECTION("set leafUInt16 to 65535")
99 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100100 REQUIRE_CALL(mock, write("/example-schema:leafUInt16", std::nullopt, "65535"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200101 datastore.setLeaf("/example-schema:leafUInt16", uint16_t{65535});
102 datastore.commitChanges();
103 }
104
105 SECTION("set leafUInt32 to 4294967295")
106 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100107 REQUIRE_CALL(mock, write("/example-schema:leafUInt32", std::nullopt, "4294967295"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200108 datastore.setLeaf("/example-schema:leafUInt32", uint32_t{4294967295});
109 datastore.commitChanges();
110 }
111
112 SECTION("set leafUInt64 to 50000000000")
113 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100114 REQUIRE_CALL(mock, write("/example-schema:leafUInt64", std::nullopt, "50000000000"s));
Václav Kubernát134d78f2019-09-03 16:42:29 +0200115 datastore.setLeaf("/example-schema:leafUInt64", uint64_t{50000000000});
Václav Kubernát73109382018-09-14 19:52:03 +0200116 datastore.commitChanges();
117 }
118
119 SECTION("set leafEnum to coze")
120 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100121 REQUIRE_CALL(mock, write("/example-schema:leafEnum", std::nullopt, "coze"s));
Václav Kubernát73109382018-09-14 19:52:03 +0200122 datastore.setLeaf("/example-schema:leafEnum", enum_{"coze"});
123 datastore.commitChanges();
124 }
125
126 SECTION("set leafDecimal to 123.544")
127 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100128 REQUIRE_CALL(mock, write("/example-schema:leafDecimal", std::nullopt, "123.544"s));
Václav Kubernát73109382018-09-14 19:52:03 +0200129 datastore.setLeaf("/example-schema:leafDecimal", 123.544);
130 datastore.commitChanges();
131 }
132
133 SECTION("create presence container")
134 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100135 REQUIRE_CALL(mock, write("/example-schema:pContainer", std::nullopt, ""s));
Václav Kubernát73109382018-09-14 19:52:03 +0200136 datastore.createPresenceContainer("/example-schema:pContainer");
137 datastore.commitChanges();
138 }
139
Václav Kubernátcc7a93f2020-02-04 11:48:15 +0100140 SECTION("create/delete a list instance")
Václav Kubernát45f4a822019-05-29 21:10:50 +0200141 {
Václav Kubernátcc7a93f2020-02-04 11:48:15 +0100142 {
143 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));
145 datastore.createListInstance("/example-schema:person[name='Nguyen']");
146 datastore.commitChanges();
147 }
148 {
149 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']", ""s, std::nullopt));
150 REQUIRE_CALL(mock, write("/example-schema:person[name='Nguyen']/name", "Nguyen"s, std::nullopt));
151 datastore.deleteListInstance("/example-schema:person[name='Nguyen']");
152 datastore.commitChanges();
153 }
Václav Kubernát45f4a822019-05-29 21:10:50 +0200154 }
155
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200156 SECTION("deleting non-existing list keys")
157 {
158 tryThis<THROWS_ON_NONEXISTING_KEYS>([&]{
159 datastore.deleteListInstance("/example-schema:person[name='non existing']");
160 datastore.commitChanges();
161 });
162 }
163
164 SECTION("accessing non-existing schema nodes")
165 {
166 tryThis<THROWS_ON_INVALID_SCHEMA_PATHS>([&]{
167 datastore.deleteListInstance("/example-schema:non-existing-list[xxx='non existing']");
168 datastore.commitChanges();
169 });
170 }
171
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200172 SECTION("leafref pointing to a key of a list")
173 {
174 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100175 REQUIRE_CALL(mock, write("/example-schema:person[name='Dan']", std::nullopt, ""s));
176 REQUIRE_CALL(mock, write("/example-schema:person[name='Dan']/name", std::nullopt, "Dan"s));
177 REQUIRE_CALL(mock, write("/example-schema:person[name='Elfi']", std::nullopt, ""s));
178 REQUIRE_CALL(mock, write("/example-schema:person[name='Elfi']/name", std::nullopt, "Elfi"s));
179 REQUIRE_CALL(mock, write("/example-schema:person[name='Kolafa']", std::nullopt, ""s));
180 REQUIRE_CALL(mock, write("/example-schema:person[name='Kolafa']/name", std::nullopt, "Kolafa"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200181 datastore.createListInstance("/example-schema:person[name='Dan']");
182 datastore.createListInstance("/example-schema:person[name='Elfi']");
183 datastore.createListInstance("/example-schema:person[name='Kolafa']");
184 datastore.commitChanges();
185 }
186
187 // The commitChanges method has to be called in each of the
188 // SECTIONs, because the REQUIRE_CALL only works inside the given
189 // SECTION.
190 SECTION("Dan")
191 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100192 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Dan"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200193 datastore.setLeaf("/example-schema:bossPerson", std::string{"Dan"});
194 datastore.commitChanges();
195 }
196
197 SECTION("Elfi")
198 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100199 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Elfi"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200200 datastore.setLeaf("/example-schema:bossPerson", std::string{"Elfi"});
201 datastore.commitChanges();
202 }
203
204 SECTION("Kolafa")
205 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100206 REQUIRE_CALL(mock, write("/example-schema:bossPerson", std::nullopt, "Kolafa"s));
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200207 datastore.setLeaf("/example-schema:bossPerson", std::string{"Kolafa"});
208 datastore.commitChanges();
209 }
210 }
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200211 SECTION("bool values get correctly represented as bools")
212 {
213 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100214 REQUIRE_CALL(mock, write("/example-schema:down", std::nullopt, "true"s));
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200215 datastore.setLeaf("/example-schema:down", bool{true});
216 datastore.commitChanges();
217 }
218
Jan Kundrátb331b552020-01-23 15:25:29 +0100219 DatastoreAccess::Tree expected{{"/example-schema:down", bool{true}}};
Václav Kubernát8e121ff2019-10-15 15:47:45 +0200220 REQUIRE(datastore.getItems("/example-schema:down") == expected);
221 }
Václav Kubernát3efb5ca2019-10-09 20:07:40 +0200222
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200223 SECTION("getting items from the whole module")
224 {
225 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100226 REQUIRE_CALL(mock, write("/example-schema:up", std::nullopt, "true"s));
227 REQUIRE_CALL(mock, write("/example-schema:down", std::nullopt, "false"s));
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200228 datastore.setLeaf("/example-schema:up", bool{true});
229 datastore.setLeaf("/example-schema:down", bool{false});
230 datastore.commitChanges();
231 }
232
Václav Kubernátcf9224f2020-06-02 09:55:29 +0200233 DatastoreAccess::Tree expected{
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200234 // Sysrepo always returns containers when getting values, but
235 // libnetconf does not. This is fine by the YANG standard:
236 // https://tools.ietf.org/html/rfc7950#section-7.5.7 Furthermore,
237 // NetconfAccess implementation actually only iterates over leafs,
238 // so even if libnetconf did include containers, they wouldn't get
239 // shown here anyway. With sysrepo2, this won't be necessary,
240 // because it'll use the same data structure as libnetconf, so the
241 // results will be consistent.
242#ifdef sysrepo_BACKEND
Václav Kubernátda8e4b92020-02-04 11:56:30 +0100243 {"/example-schema:inventory", special_{SpecialValue::Container}},
Václav Kubernátcf9224f2020-06-02 09:55:29 +0200244 {"/example-schema:lol", special_{SpecialValue::Container}},
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200245#endif
Václav Kubernátcf9224f2020-06-02 09:55:29 +0200246 {"/example-schema:up", bool{true}},
247 {"/example-schema:down", bool{false}}};
Václav Kubernát9456b5c2019-10-02 21:14:52 +0200248 REQUIRE(datastore.getItems("/example-schema:*") == expected);
249 }
250
Václav Kubernát152ce222019-12-19 12:23:32 +0100251 SECTION("getItems returns correct datatypes")
252 {
253 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100254 REQUIRE_CALL(mock, write("/example-schema:leafEnum", std::nullopt, "lol"s));
Václav Kubernát152ce222019-12-19 12:23:32 +0100255 datastore.setLeaf("/example-schema:leafEnum", enum_{"lol"});
256 datastore.commitChanges();
257 }
Jan Kundrátb331b552020-01-23 15:25:29 +0100258 DatastoreAccess::Tree expected{{"/example-schema:leafEnum", enum_{"lol"}}};
Václav Kubernát152ce222019-12-19 12:23:32 +0100259
260 REQUIRE(datastore.getItems("/example-schema:leafEnum") == expected);
261 }
262
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100263 SECTION("getItems on a list")
264 {
265 {
Václav Kubernát69aabe92020-01-24 16:53:12 +0100266 REQUIRE_CALL(mock, write("/example-schema:person[name='Jan']", std::nullopt, ""s));
267 REQUIRE_CALL(mock, write("/example-schema:person[name='Jan']/name", std::nullopt, "Jan"s));
268 REQUIRE_CALL(mock, write("/example-schema:person[name='Michal']", std::nullopt, ""s));
269 REQUIRE_CALL(mock, write("/example-schema:person[name='Michal']/name", std::nullopt, "Michal"s));
270 REQUIRE_CALL(mock, write("/example-schema:person[name='Petr']", std::nullopt, ""s));
271 REQUIRE_CALL(mock, write("/example-schema:person[name='Petr']/name", std::nullopt, "Petr"s));
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100272 datastore.createListInstance("/example-schema:person[name='Jan']");
273 datastore.createListInstance("/example-schema:person[name='Michal']");
274 datastore.createListInstance("/example-schema:person[name='Petr']");
275 datastore.commitChanges();
276 }
Jan Kundrátb331b552020-01-23 15:25:29 +0100277 DatastoreAccess::Tree expected{
Václav Kubernát144729d2020-01-08 15:20:35 +0100278 {"/example-schema:person[name='Jan']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100279 {"/example-schema:person[name='Jan']/name", std::string{"Jan"}},
Václav Kubernát144729d2020-01-08 15:20:35 +0100280 {"/example-schema:person[name='Michal']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100281 {"/example-schema:person[name='Michal']/name", std::string{"Michal"}},
Václav Kubernát144729d2020-01-08 15:20:35 +0100282 {"/example-schema:person[name='Petr']", special_{SpecialValue::List}},
Václav Kubernátd812cfb2020-01-07 17:30:20 +0100283 {"/example-schema:person[name='Petr']/name", std::string{"Petr"}}
284 };
285
286 REQUIRE(datastore.getItems("/example-schema:person") == expected);
287 }
288
Václav Kubernát69aabe92020-01-24 16:53:12 +0100289 SECTION("presence containers")
290 {
291 DatastoreAccess::Tree expected;
292 // Make sure it's not there before we create it
293 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
294
295 {
296 REQUIRE_CALL(mock, write("/example-schema:pContainer", std::nullopt, ""s));
297 datastore.createPresenceContainer("/example-schema:pContainer");
298 datastore.commitChanges();
299 }
300 expected = {
301 {"/example-schema:pContainer", special_{SpecialValue::PresenceContainer}}
302 };
303 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
304
305 // Make sure it's not there after we delete it
306 {
307 REQUIRE_CALL(mock, write("/example-schema:pContainer", ""s, std::nullopt));
308 datastore.deletePresenceContainer("/example-schema:pContainer");
309 datastore.commitChanges();
310 }
311 expected = {};
312 REQUIRE(datastore.getItems("/example-schema:pContainer") == expected);
Václav Kubernátda8e4b92020-02-04 11:56:30 +0100313 }
Václav Kubernát69aabe92020-01-24 16:53:12 +0100314
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200315 SECTION("non-existing persistent container schema node")
316 {
317 tryThis<THROWS_ON_INVALID_SCHEMA_PATHS>([&]{
318 datastore.deletePresenceContainer("/example-schema:non-existing-presence-container");
319 datastore.commitChanges();
320 });
321 }
322
Václav Kubernátda8e4b92020-02-04 11:56:30 +0100323 SECTION("nested presence container")
324 {
325 DatastoreAccess::Tree expected;
326 // Make sure it's not there before we create it
327 REQUIRE(datastore.getItems("/example-schema:inventory/stuff") == expected);
328 {
329 REQUIRE_CALL(mock, write("/example-schema:inventory", std::nullopt, ""s));
330 REQUIRE_CALL(mock, write("/example-schema:inventory/stuff", std::nullopt, ""s));
331 datastore.createPresenceContainer("/example-schema:inventory/stuff");
332 datastore.commitChanges();
333 }
334 expected = {
335 {"/example-schema:inventory/stuff", special_{SpecialValue::PresenceContainer}}
336 };
337 REQUIRE(datastore.getItems("/example-schema:inventory/stuff") == expected);
338 {
339 REQUIRE_CALL(mock, write("/example-schema:inventory", ""s, std::nullopt));
340 REQUIRE_CALL(mock, write("/example-schema:inventory/stuff", ""s, std::nullopt));
341 datastore.deletePresenceContainer("/example-schema:inventory/stuff");
342 datastore.commitChanges();
343 }
344 expected = {};
345 REQUIRE(datastore.getItems("/example-schema:inventory/stuff") == expected);
Václav Kubernát69aabe92020-01-24 16:53:12 +0100346 }
347
Jan Kundrátbd3169c2020-02-03 19:31:34 +0100348 SECTION("floats")
349 {
350 datastore.setLeaf("/example-schema:leafDecimal", 123.4);
351 REQUIRE_CALL(mock, write("/example-schema:leafDecimal", std::nullopt, "123.4"s));
352 datastore.commitChanges();
353 DatastoreAccess::Tree expected {
354 {"/example-schema:leafDecimal", 123.4},
355 };
356 REQUIRE(datastore.getItems("/example-schema:leafDecimal") == expected);
357 }
358
Jan Kundrát3ff50122020-05-07 00:37:50 +0200359 SECTION("unions")
360 {
361 datastore.setLeaf("/example-schema:unionIntString", int32_t{10});
362 REQUIRE_CALL(mock, write("/example-schema:unionIntString", std::nullopt, "10"s));
363 datastore.commitChanges();
364 DatastoreAccess::Tree expected {
365 {"/example-schema:unionIntString", int32_t{10}},
366 };
367 REQUIRE(datastore.getItems("/example-schema:unionIntString") == expected);
368 }
369
Jan Kundrát0d8abd12020-05-07 02:00:14 +0200370 SECTION("identityref") {
371 datastore.setLeaf("/example-schema:beast", identityRef_{"example-schema", "Mammal"});
372 REQUIRE_CALL(mock, write("/example-schema:beast", std::nullopt, "example-schema:Mammal"s));
373 datastore.commitChanges();
374 DatastoreAccess::Tree expected {
375 {"/example-schema:beast", identityRef_{"example-schema", "Mammal"}},
376 };
377 REQUIRE(datastore.getItems("/example-schema:beast") == expected);
378
379 datastore.setLeaf("/example-schema:beast", identityRef_{"Whale"});
380 REQUIRE_CALL(mock, write("/example-schema:beast", "example-schema:Mammal", "example-schema:Whale"s));
381 datastore.commitChanges();
382 expected = {
383 {"/example-schema:beast", identityRef_{"example-schema", "Whale"}},
384 };
385 REQUIRE(datastore.getItems("/example-schema:beast") == expected);
386 }
387
Jan Kundrát68985442020-05-07 02:15:34 +0200388 SECTION("binary")
389 {
390 datastore.setLeaf("/example-schema:blob", binary_{"cHduegByIQ=="s});
391 REQUIRE_CALL(mock, write("/example-schema:blob", std::nullopt, "cHduegByIQ=="s));
392 datastore.commitChanges();
393 DatastoreAccess::Tree expected {
394 {"/example-schema:blob", binary_{"cHduegByIQ=="s}},
395 };
396 REQUIRE(datastore.getItems("/example-schema:blob") == expected);
397 }
398
Jan Kundrát379bb572020-05-07 03:23:13 +0200399 SECTION("empty")
400 {
401 datastore.setLeaf("/example-schema:dummy", empty_{});
402 REQUIRE_CALL(mock, write("/example-schema:dummy", std::nullopt, ""s));
403 datastore.commitChanges();
404 DatastoreAccess::Tree expected {
405 {"/example-schema:dummy", empty_{}},
406 };
407 REQUIRE(datastore.getItems("/example-schema:dummy") == expected);
408 }
409
Jan Kundrátbb525b42020-02-04 11:56:59 +0100410 SECTION("operational data")
411 {
412 MockDataSupplier mockOpsData;
413 OperationalDataSubscription opsDataSub("/example-schema:temperature", mockOpsData);
414 DatastoreAccess::Tree expected;
415 std::string xpath;
416 SECTION("temperature")
417 {
418 expected = {{"/example-schema:temperature", int32_t{22}}};
419 xpath = "/example-schema:temperature";
420 }
421
422 REQUIRE_CALL(mockOpsData, get_data(xpath)).RETURN(expected);
423 REQUIRE(datastore.getItems(xpath) == expected);
424 }
425
Václav Kubernát5b8a8f32020-05-20 00:57:22 +0200426 SECTION("leaf list")
427 {
428 DatastoreAccess::Tree expected;
429 REQUIRE_CALL(mock, write("/example-schema:addresses", std::nullopt, "0.0.0.0"s));
430 REQUIRE_CALL(mock, write("/example-schema:addresses", std::nullopt, "127.0.0.1"s));
431 datastore.createLeafListInstance("/example-schema:addresses[.='0.0.0.0']");
432 datastore.createLeafListInstance("/example-schema:addresses[.='127.0.0.1']");
433 datastore.commitChanges();
434 expected = {
435 {"/example-schema:addresses", special_{SpecialValue::LeafList}},
436 {"/example-schema:addresses[.='0.0.0.0']", "0.0.0.0"s},
437 {"/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s},
438 };
439 REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
440
441 REQUIRE_CALL(mock, write("/example-schema:addresses", "0.0.0.0"s, std::nullopt));
442 datastore.deleteLeafListInstance("/example-schema:addresses[.='0.0.0.0']");
443 datastore.commitChanges();
444 expected = {
445 {"/example-schema:addresses", special_{SpecialValue::LeafList}},
446 {"/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s},
447 };
448 REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
449
450 REQUIRE_CALL(mock, write("/example-schema:addresses", "127.0.0.1"s, std::nullopt));
451 datastore.deleteLeafListInstance("/example-schema:addresses[.='127.0.0.1']");
452 datastore.commitChanges();
453 expected = {};
454 REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
455 }
Jan Kundrátbb525b42020-02-04 11:56:59 +0100456
Jan Kundrát3867c9e2020-06-18 20:26:45 +0200457 SECTION("non-existing leaf-list")
458 {
459 tryThis<THROWS_ON_NONEXISTING_KEYS>([&]{
460 datastore.deleteLeafListInstance("/example-schema:addresses[.='non-existing']");
461 datastore.commitChanges();
462 });
463 }
464
465 SECTION("non-existing leaf-list schema node")
466 {
467 tryThis<THROWS_ON_INVALID_SCHEMA_PATHS>([&]{
468 datastore.deleteLeafListInstance("/example-schema:non-existing[.='non-existing']");
469 datastore.commitChanges();
470 });
471 }
472
Václav Kubernát7160a132020-04-03 02:11:01 +0200473 SECTION("copying data from startup refreshes the data")
474 {
475 {
476 REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
477 REQUIRE_CALL(mock, write("/example-schema:leafInt16", std::nullopt, "123"s));
478 datastore.setLeaf("/example-schema:leafInt16", int16_t{123});
479 datastore.commitChanges();
480 }
481 REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{{"/example-schema:leafInt16", int16_t{123}}});
482 REQUIRE_CALL(mock, write("/example-schema:leafInt16", "123"s, std::nullopt));
483 datastore.copyConfig(Datastore::Startup, Datastore::Running);
484 REQUIRE(datastore.getItems("/example-schema:leafInt16") == DatastoreAccess::Tree{});
485 }
486
Václav Kubernátbf65dd72020-05-28 02:32:31 +0200487 SECTION("moving leaflist instances")
488 {
489 DatastoreAccess::Tree expected;
490 {
491 // sysrepo does this twice for some reason, it's possibly a bug
492 REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "http"s)).TIMES(2);
493 REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "ftp"s));
494 REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "pop3"s));
495 REQUIRE_CALL(mock, write("/example-schema:protocols", "http"s, "ftp"s));
496 REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "pop3"s));
497 datastore.createLeafListInstance("/example-schema:protocols[.='http']");
498 datastore.createLeafListInstance("/example-schema:protocols[.='ftp']");
499 datastore.createLeafListInstance("/example-schema:protocols[.='pop3']");
500 datastore.commitChanges();
501 expected = {
502 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
503 {"/example-schema:protocols[.='http']", "http"s},
504 {"/example-schema:protocols[.='ftp']", "ftp"s},
505 {"/example-schema:protocols[.='pop3']", "pop3"s},
506 };
507 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
508 }
509
510 std::string sourcePath;
511 SECTION("begin")
512 {
513 REQUIRE_CALL(mock, write("/example-schema:protocols", std::nullopt, "pop3"s));
514 sourcePath = "/example-schema:protocols[.='pop3']";
515 datastore.moveItem(sourcePath, yang::move::Absolute::Begin);
516 datastore.commitChanges();
517 expected = {
518 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
519 {"/example-schema:protocols[.='pop3']", "pop3"s},
520 {"/example-schema:protocols[.='http']", "http"s},
521 {"/example-schema:protocols[.='ftp']", "ftp"s},
522 };
523 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
524 }
525
526 SECTION("end")
527 {
528 sourcePath = "/example-schema:protocols[.='http']";
529 REQUIRE_CALL(mock, write("/example-schema:protocols", "pop3"s, "http"s));
530 datastore.moveItem(sourcePath, yang::move::Absolute::End);
531 datastore.commitChanges();
532 expected = {
533 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
534 {"/example-schema:protocols[.='ftp']", "ftp"s},
535 {"/example-schema:protocols[.='pop3']", "pop3"s},
536 {"/example-schema:protocols[.='http']", "http"s},
537 };
538 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
539 }
540
541 SECTION("after")
542 {
543 sourcePath = "/example-schema:protocols[.='http']";
544 REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "http"s));
545 datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::After, {{".", "ftp"s}}});
546 datastore.commitChanges();
547 expected = {
548 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
549 {"/example-schema:protocols[.='ftp']", "ftp"s},
550 {"/example-schema:protocols[.='http']", "http"s},
551 {"/example-schema:protocols[.='pop3']", "pop3"s},
552 };
553 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
554 }
555
556 SECTION("before")
557 {
558 sourcePath = "/example-schema:protocols[.='http']";
559 REQUIRE_CALL(mock, write("/example-schema:protocols", "ftp"s, "http"s));
560 datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::Before, {{".", "pop3"s}}});
561 datastore.commitChanges();
562 expected = {
563 {"/example-schema:protocols", special_{SpecialValue::LeafList}},
564 {"/example-schema:protocols[.='ftp']", "ftp"s},
565 {"/example-schema:protocols[.='http']", "http"s},
566 {"/example-schema:protocols[.='pop3']", "pop3"s},
567 };
568 REQUIRE(datastore.getItems("/example-schema:protocols") == expected);
569 }
570 }
571
572 SECTION("moving list instances")
573 {
574 DatastoreAccess::Tree expected;
575 {
576 // sysrepo does this twice for some reason, it's possibly a bug
577 REQUIRE_CALL(mock, write("/example-schema:players[name='John']", std::nullopt, ""s)).TIMES(2);
578 REQUIRE_CALL(mock, write("/example-schema:players[name='John']/name", std::nullopt, "John"s));
579 REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']", std::nullopt, ""s));
580 REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']", ""s, ""s));
581 REQUIRE_CALL(mock, write("/example-schema:players[name='Eve']/name", std::nullopt, "Eve"s));
582 REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", std::nullopt, ""s));
583 REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']/name", std::nullopt, "Adam"s));
584 REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", ""s, ""s));
585 datastore.createListInstance("/example-schema:players[name='John']");
586 datastore.createListInstance("/example-schema:players[name='Eve']");
587 datastore.createListInstance("/example-schema:players[name='Adam']");
588 datastore.commitChanges();
589 expected = {
590 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
591 {"/example-schema:players[name='John']/name", "John"s},
592 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
593 {"/example-schema:players[name='Eve']/name", "Eve"s},
594 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
595 {"/example-schema:players[name='Adam']/name", "Adam"s},
596 };
597 REQUIRE(datastore.getItems("/example-schema:players") == expected);
598 }
599
600 std::string sourcePath;
601 SECTION("begin")
602 {
603 sourcePath = "/example-schema:players[name='Adam']";
604 REQUIRE_CALL(mock, write("/example-schema:players[name='Adam']", std::nullopt, ""s));
605 datastore.moveItem(sourcePath, yang::move::Absolute::Begin);
606 datastore.commitChanges();
607 expected = {
608 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
609 {"/example-schema:players[name='Adam']/name", "Adam"s},
610 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
611 {"/example-schema:players[name='John']/name", "John"s},
612 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
613 {"/example-schema:players[name='Eve']/name", "Eve"s},
614 };
615 REQUIRE(datastore.getItems("/example-schema:players") == expected);
616 }
617
618 SECTION("end")
619 {
620 sourcePath = "/example-schema:players[name='John']";
621 REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
622 datastore.moveItem(sourcePath, yang::move::Absolute::End);
623 datastore.commitChanges();
624 expected = {
625 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
626 {"/example-schema:players[name='Eve']/name", "Eve"s},
627 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
628 {"/example-schema:players[name='Adam']/name", "Adam"s},
629 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
630 {"/example-schema:players[name='John']/name", "John"s},
631 };
632 REQUIRE(datastore.getItems("/example-schema:players") == expected);
633 }
634
635 SECTION("after")
636 {
637 sourcePath = "/example-schema:players[name='John']";
638 REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
639 datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::After, {{"name", "Eve"s}}});
640 datastore.commitChanges();
641 expected = {
642 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
643 {"/example-schema:players[name='Eve']/name", "Eve"s},
644 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
645 {"/example-schema:players[name='John']/name", "John"s},
646 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
647 {"/example-schema:players[name='Adam']/name", "Adam"s},
648 };
649 REQUIRE(datastore.getItems("/example-schema:players") == expected);
650 }
651
652 SECTION("before")
653 {
654 sourcePath = "/example-schema:players[name='John']";
655 REQUIRE_CALL(mock, write("/example-schema:players[name='John']", ""s, ""s));
656 datastore.moveItem(sourcePath, yang::move::Relative{yang::move::Relative::Position::Before, {{"name", "Adam"s}}});
657 datastore.commitChanges();
658 expected = {
659 {"/example-schema:players[name='Eve']", special_{SpecialValue::List}},
660 {"/example-schema:players[name='Eve']/name", "Eve"s},
661 {"/example-schema:players[name='John']", special_{SpecialValue::List}},
662 {"/example-schema:players[name='John']/name", "John"s},
663 {"/example-schema:players[name='Adam']", special_{SpecialValue::List}},
664 {"/example-schema:players[name='Adam']/name", "Adam"s},
665 };
666 REQUIRE(datastore.getItems("/example-schema:players") == expected);
667 }
668 }
669
Václav Kubernátfa96ac22020-06-18 17:03:52 +0200670 SECTION("getting /")
671 {
672 {
673 REQUIRE_CALL(mock, write("/example-schema:leafInt32", std::nullopt, "64"s));
674 datastore.setLeaf("/example-schema:leafInt32", 64);
675 datastore.commitChanges();
676 }
677
678 DatastoreAccess::Tree expected{
679 // Sysrepo always returns containers when getting values, but
680 // libnetconf does not. This is fine by the YANG standard:
681 // https://tools.ietf.org/html/rfc7950#section-7.5.7 Furthermore,
682 // NetconfAccess implementation actually only iterates over leafs,
683 // so even if libnetconf did include containers, they wouldn't get
684 // shown here anyway. With sysrepo2, this won't be necessary,
685 // because it'll use the same data structure as libnetconf, so the
686 // results will be consistent.
687#ifdef sysrepo_BACKEND
688 {"/example-schema:inventory", special_{SpecialValue::Container}},
689 {"/example-schema:lol", special_{SpecialValue::Container}},
690#endif
691 {"/example-schema:leafInt32", 64}};
692 auto items = datastore.getItems("/");
693 // This tests if we at least get the data WE added.
694 REQUIRE(std::all_of(expected.begin(), expected.end(), [items] (const auto& item) { return std::find(items.begin(), items.end(), item) != items.end(); }));
695 }
696
Václav Kubernát73109382018-09-14 19:52:03 +0200697 waitForCompletionAndBitMore(seq1);
698}
Jan Kundrát6ee84792020-01-24 01:43:36 +0100699
700class RpcCb: public sysrepo::Callback {
701 int rpc(const char *xpath, const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void *) override
702 {
703 const auto nukes = "/example-schema:launch-nukes"s;
704 if (xpath == "/example-schema:noop"s) {
705 return SR_ERR_OK;
706 } else if (xpath == nukes) {
707 uint64_t kilotons = 0;
708 bool hasCities = false;
709 for (size_t i = 0; i < input->val_cnt(); ++i) {
710 const auto& val = input->val(i);
711 if (val->xpath() == nukes + "/payload/kilotons") {
712 kilotons = val->data()->get_uint64();
713 } else if (val->xpath() == nukes + "/payload") {
714 // ignore, container
715 } else if (val->xpath() == nukes + "/description") {
716 // unused
717 } else if (std::string_view{val->xpath()}.find(nukes + "/cities") == 0) {
718 hasCities = true;
719 } else {
720 throw std::runtime_error("RPC launch-nukes: unexpected input "s + val->xpath());
721 }
722 }
723 if (kilotons == 333'666) {
724 // magic, just do not generate any output. This is important because the NETCONF RPC returns just <ok/>.
725 return SR_ERR_OK;
726 }
727 auto buf = output->allocate(2);
728 size_t i = 0;
729 buf->val(i++)->set((nukes + "/blast-radius").c_str(), uint32_t{33'666});
730 buf->val(i++)->set((nukes + "/actual-yield").c_str(), static_cast<uint64_t>(1.33 * kilotons));
731 if (hasCities) {
732 buf = output->reallocate(output->val_cnt() + 2);
733 buf->val(i++)->set((nukes + "/damaged-places/targets[city='London']/city").c_str(), "London");
734 buf->val(i++)->set((nukes + "/damaged-places/targets[city='Berlin']/city").c_str(), "Berlin");
735 }
736 return SR_ERR_OK;
737 }
738 throw std::runtime_error("unrecognized RPC");
739 }
740};
741
742TEST_CASE("rpc") {
743 trompeloeil::sequence seq1;
744 auto srConn = std::make_shared<sysrepo::Connection>("netconf-cli-test-rpc");
745 auto srSession = std::make_shared<sysrepo::Session>(srConn);
746 auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSession);
747 auto cb = std::make_shared<RpcCb>();
748 sysrepo::Logs{}.set_stderr(SR_LL_INF);
749 srSubscription->rpc_subscribe("/example-schema:noop", cb, nullptr, SR_SUBSCR_CTX_REUSE);
750 srSubscription->rpc_subscribe("/example-schema:launch-nukes", cb, nullptr, SR_SUBSCR_CTX_REUSE);
751
752#ifdef sysrepo_BACKEND
Václav Kubernát715c85c2020-04-14 01:46:08 +0200753 SysrepoAccess datastore("netconf-cli-test", Datastore::Running);
Jan Kundrát6ee84792020-01-24 01:43:36 +0100754#elif defined(netconf_BACKEND)
755 NetconfAccess datastore(NETOPEER_SOCKET_PATH);
756#else
757#error "Unknown backend"
758#endif
759
760 std::string rpc;
761 DatastoreAccess::Tree input, output;
762
763 SECTION("noop") {
764 rpc = "/example-schema:noop";
765 }
766
767 SECTION("small nuke") {
768 rpc = "/example-schema:launch-nukes";
769 input = {
770 {"description", "dummy"s},
771 {"payload/kilotons", uint64_t{333'666}},
772 };
773 // no data are returned
774 }
775
776 SECTION("small nuke") {
777 rpc = "/example-schema:launch-nukes";
778 input = {
779 {"description", "dummy"s},
780 {"payload/kilotons", uint64_t{4}},
781 };
782 output = {
783 {"blast-radius", uint32_t{33'666}},
784 {"actual-yield", uint64_t{5}},
785 };
786 }
787
788 SECTION("with lists") {
789 rpc = "/example-schema:launch-nukes";
790 input = {
791 {"payload/kilotons", uint64_t{6}},
792 {"cities/targets[city='Prague']/city", "Prague"s},
793 };
794 output = {
795 {"blast-radius", uint32_t{33'666}},
796 {"actual-yield", uint64_t{7}},
797 {"damaged-places", special_{SpecialValue::PresenceContainer}},
798 {"damaged-places/targets[city='London']", special_{SpecialValue::List}},
799 {"damaged-places/targets[city='London']/city", "London"s},
800 {"damaged-places/targets[city='Berlin']", special_{SpecialValue::List}},
801 {"damaged-places/targets[city='Berlin']/city", "Berlin"s},
802 };
803 }
804
805 REQUIRE(datastore.executeRpc(rpc, input) == output);
806
807 waitForCompletionAndBitMore(seq1);
808}