blob: a46015068a57b3cda0fa0d822a5c9987f28a3467 [file] [log] [blame]
Václav Kubernát74487df2020-06-04 01:29:28 +02001#include <boost/algorithm/string/predicate.hpp>
2#include <experimental/iterator>
Václav Kubernát548cb192020-06-26 14:00:42 +02003#include <fstream>
Václav Kubernát74487df2020-06-04 01:29:28 +02004#include <iostream>
5#include <libyang/Tree_Data.hpp>
6#include <libyang/libyang.h>
7#include "UniqueResource.hpp"
8#include "libyang_utils.hpp"
9#include "utils.hpp"
10#include "yang_access.hpp"
11#include "yang_schema.hpp"
12
13namespace {
14template <typename Type> using lyPtrDeleter_type = void (*)(Type*);
15template <typename Type> const lyPtrDeleter_type<Type> lyPtrDeleter;
16template <> const auto lyPtrDeleter<ly_set> = ly_set_free;
17template <> const auto lyPtrDeleter<ly_ctx> = static_cast<lyPtrDeleter_type<ly_ctx>>([] (auto* ptr) {ly_ctx_destroy(ptr, nullptr);});
18template <> const auto lyPtrDeleter<lyd_node> = lyd_free_withsiblings;
19
20template <typename Type>
21auto lyWrap(Type* ptr)
22{
23 return std::unique_ptr<Type, lyPtrDeleter_type<Type>>{ptr, lyPtrDeleter<Type>};
24}
25
26// Convenient for functions that take m_datastore as an argument
27using DatastoreType = std::unique_ptr<lyd_node, lyPtrDeleter_type<lyd_node>>;
28}
29
30YangAccess::YangAccess()
31 : m_ctx(lyWrap(ly_ctx_new(nullptr, LY_CTX_DISABLE_SEARCHDIR_CWD)))
32 , m_datastore(lyWrap<lyd_node>(nullptr))
33 , m_schema(std::make_shared<YangSchema>(libyang::create_new_Context(m_ctx.get())))
Václav Kubernáte7248b22020-06-26 15:38:59 +020034 , m_validation_mode(LYD_OPT_DATA)
35{
36}
37
38YangAccess::YangAccess(std::shared_ptr<YangSchema> schema)
39 : m_ctx(schema->m_context->swig_ctx(), [](auto) {})
40 , m_datastore(lyWrap<lyd_node>(nullptr))
41 , m_schema(schema)
42 , m_validation_mode(LYD_OPT_RPC)
Václav Kubernát74487df2020-06-04 01:29:28 +020043{
44}
45
Václav Kubernát51fa48e2020-07-08 17:17:34 +020046YangAccess::~YangAccess() = default;
Václav Kubernát74487df2020-06-04 01:29:28 +020047
48[[noreturn]] void YangAccess::getErrorsAndThrow() const
49{
50 auto errors = libyang::get_ly_errors(libyang::create_new_Context(m_ctx.get()));
51 std::vector<DatastoreError> errorsRes;
52 for (const auto& error : errors) {
53 using namespace std::string_view_literals;
54 errorsRes.emplace_back(error->errmsg(), error->errpath() != ""sv ? std::optional{error->errpath()} : std::nullopt);
55 }
56
57 throw DatastoreException(errorsRes);
58}
59
60void YangAccess::impl_newPath(const std::string& path, const std::optional<std::string>& value)
61{
62 auto newNode = lyd_new_path(m_datastore.get(), m_ctx.get(), path.c_str(), value ? (void*)value->c_str() : nullptr, LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE);
63 if (!newNode) {
64 getErrorsAndThrow();
65 }
66 if (!m_datastore) {
67 m_datastore = lyWrap(newNode);
68 }
69}
70
71namespace {
72void impl_unlink(DatastoreType& datastore, lyd_node* what)
73{
74 // If the node to be unlinked is the one our datastore variable points to, we need to find a new one to point to (one of its siblings)
75 if (datastore.get() == what) {
76 auto oldDatastore = datastore.release();
77 if (oldDatastore->prev != oldDatastore) {
78 datastore = lyWrap(oldDatastore->prev);
79 } else {
80 datastore = lyWrap(oldDatastore->next);
81 }
82 }
83
84 lyd_unlink(what);
85}
86}
87
88void YangAccess::impl_removeNode(const std::string& path)
89{
90 auto set = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
91 if (!set || set->number == 0) {
92 // Check if schema node exists - lyd_find_path first checks if the first argument is non-null before checking for path validity
93 if (!ly_ctx_get_node(m_ctx.get(), nullptr, path.c_str(), 0)) {
94 throw DatastoreException{{DatastoreError{"Schema node doesn't exist.", path}}};
95 }
96 // Check if libyang found another error
97 if (ly_err_first(m_ctx.get())) {
98 getErrorsAndThrow();
99 }
100
101 // Otherwise the datastore just doesn't contain the wanted node.
102 throw DatastoreException{{DatastoreError{"Data node doesn't exist.", path}}};
103 }
104
105 auto toRemove = set->set.d[0];
106
107 impl_unlink(m_datastore, toRemove);
108
109 lyd_free(toRemove);
110}
111
112void YangAccess::validate()
113{
114 auto datastore = m_datastore.release();
Václav Kubernáte7248b22020-06-26 15:38:59 +0200115
116 if (m_validation_mode == LYD_OPT_RPC) {
117 lyd_validate(&datastore, m_validation_mode, nullptr);
118 } else {
119 lyd_validate(&datastore, m_validation_mode | LYD_OPT_DATA_NO_YANGLIB, m_ctx.get());
120 }
Václav Kubernát74487df2020-06-04 01:29:28 +0200121 m_datastore = lyWrap(datastore);
122}
123
Václav Kubernátd6282912020-06-23 14:49:34 +0200124DatastoreAccess::Tree YangAccess::getItems(const std::string& path) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200125{
126 DatastoreAccess::Tree res;
127 if (!m_datastore) {
128 return res;
129 }
130
131 auto set = lyWrap(lyd_find_path(m_datastore.get(), path == "/" ? "/*" : path.c_str()));
132 auto setWrapper = libyang::Set(set.get(), nullptr);
Václav Kubernáte7248b22020-06-26 15:38:59 +0200133 std::optional<std::string> ignoredXPathPrefix;
134 if (m_datastore->schema->nodetype == LYS_RPC) {
135 auto path = std::unique_ptr<char, decltype(&free)>(lys_path(m_datastore->schema, 0), &free);
136 ignoredXPathPrefix = joinPaths(path.get(), "/");
137 }
138 lyNodesToTree(res, setWrapper.data(), ignoredXPathPrefix);
Václav Kubernát74487df2020-06-04 01:29:28 +0200139 return res;
140}
141
142void YangAccess::setLeaf(const std::string& path, leaf_data_ value)
143{
144 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
145 impl_newPath(path, lyValue);
146}
147
148void YangAccess::createItem(const std::string& path)
149{
150 impl_newPath(path);
151}
152
153void YangAccess::deleteItem(const std::string& path)
154{
155 impl_removeNode(path);
156}
157
158namespace {
159struct impl_moveItem {
160 DatastoreType& m_datastore;
161 lyd_node* m_sourceNode;
162
163 void operator()(yang::move::Absolute absolute) const
164 {
165 auto set = lyWrap(lyd_find_instance(m_sourceNode, m_sourceNode->schema));
166 if (set->number == 1) { // m_sourceNode is the sole instance, do nothing
167 return;
168 }
169
170 doUnlink();
171 switch (absolute) {
172 case yang::move::Absolute::Begin:
173 if (set->set.d[0] == m_sourceNode) { // List is already at the beginning, do nothing
174 return;
175 }
176 lyd_insert_before(set->set.d[0], m_sourceNode);
177 return;
178 case yang::move::Absolute::End:
179 if (set->set.d[set->number - 1] == m_sourceNode) { // List is already at the end, do nothing
180 return;
181 }
182 lyd_insert_after(set->set.d[set->number - 1], m_sourceNode);
183 return;
184 }
185 }
186
187 void operator()(const yang::move::Relative& relative) const
188 {
189 auto keySuffix = m_sourceNode->schema->nodetype == LYS_LIST ? instanceToString(relative.m_path)
190 : leafDataToString(relative.m_path.at("."));
191 lyd_node* destNode;
192 lyd_find_sibling_val(m_sourceNode, m_sourceNode->schema, keySuffix.c_str(), &destNode);
193
194 doUnlink();
195 if (relative.m_position == yang::move::Relative::Position::After) {
196 lyd_insert_after(destNode, m_sourceNode);
197 } else {
198 lyd_insert_before(destNode, m_sourceNode);
199 }
200 }
201
202private:
203 void doUnlink() const
204 {
205 impl_unlink(m_datastore, m_sourceNode);
206 }
207};
208}
209
210void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
211{
212 auto set = lyWrap(lyd_find_path(m_datastore.get(), source.c_str()));
213 if (!set) { // Error, the node probably doesn't exist in the schema
214 getErrorsAndThrow();
215 }
216 if (set->number == 0) {
217 return;
218 }
219 auto sourceNode = set->set.d[0];
220 std::visit(impl_moveItem{m_datastore, sourceNode}, move);
221}
222
223void YangAccess::commitChanges()
224{
225 validate();
226}
227
228void YangAccess::discardChanges()
229{
230}
231
Václav Kubernáta8789602020-07-20 15:18:19 +0200232[[noreturn]] void YangAccess::impl_execute(const std::string& type, const std::string& path, const Tree& input)
Václav Kubernát74487df2020-06-04 01:29:28 +0200233{
234 auto root = lyWrap(lyd_new_path(nullptr, m_ctx.get(), path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0));
235 if (!root) {
236 getErrorsAndThrow();
237 }
238 for (const auto& [k, v] : input) {
Václav Kubernáte7248b22020-06-26 15:38:59 +0200239 if (v.type() == typeid(special_) && boost::get<special_>(v).m_value != SpecialValue::PresenceContainer) {
240 continue;
241 }
Václav Kubernát74487df2020-06-04 01:29:28 +0200242 auto node = lyd_new_path(root.get(), m_ctx.get(), joinPaths(path, k).c_str(), (void*)leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, 0);
243 if (!node) {
244 getErrorsAndThrow();
245 }
246 }
Václav Kubernáta8789602020-07-20 15:18:19 +0200247 throw std::logic_error("in-memory datastore doesn't support executing " + type + "s");
248}
249
250DatastoreAccess::Tree YangAccess::executeRpc(const std::string& path, const Tree& input)
251{
252 impl_execute("RPC", path, input);
253}
254
255DatastoreAccess::Tree YangAccess::executeAction(const std::string& path, const Tree& input)
256{
257 impl_execute("action", path, input);
Václav Kubernát74487df2020-06-04 01:29:28 +0200258}
259
260void YangAccess::copyConfig(const Datastore source, const Datastore dest)
261{
262 if (source == Datastore::Startup && dest == Datastore::Running) {
263 m_datastore = nullptr;
264 }
265}
266
267std::shared_ptr<Schema> YangAccess::schema()
268{
269 return m_schema;
270}
271
272std::vector<ListInstance> YangAccess::listInstances(const std::string& path)
273{
274 std::vector<ListInstance> res;
275 if (!m_datastore) {
276 return res;
277 }
278
279 auto instances = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
280 auto instancesWrapper = libyang::Set(instances.get(), nullptr);
281 for (const auto& list : instancesWrapper.data()) {
282 ListInstance instance;
283 for (const auto& child : list->child()->tree_for()) {
284 if (child->schema()->nodetype() == LYS_LEAF) {
285 libyang::Schema_Node_Leaf leafSchema(child->schema());
286 if (leafSchema.is_key()) {
Václav Kubernát2e4cafe2020-11-05 01:53:21 +0100287 auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(child);
288 instance.insert({leafSchema.name(), leafValueFromNode(leafData)});
Václav Kubernát74487df2020-06-04 01:29:28 +0200289 }
290 }
291 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200292 res.emplace_back(instance);
Václav Kubernát74487df2020-06-04 01:29:28 +0200293 }
294 return res;
295}
296
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200297std::string YangAccess::dump(const DataFormat format) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200298{
299 char* output;
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200300 lyd_print_mem(&output, m_datastore.get(), format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
Václav Kubernáte3d282a2020-07-09 10:32:12 +0200301 std::unique_ptr<char, decltype(&free)> deleter{output, free};
Václav Kubernát74487df2020-06-04 01:29:28 +0200302
303 if (output) {
304 std::string res = output;
Václav Kubernát74487df2020-06-04 01:29:28 +0200305 return res;
306 }
307
308 return "";
309}
310
Václav Kubernát619e6542020-06-29 14:13:43 +0200311void YangAccess::loadModule(const std::string& name)
312{
313 m_schema->loadModule(name);
314}
315
Václav Kubernát74487df2020-06-04 01:29:28 +0200316void YangAccess::addSchemaFile(const std::string& path)
317{
318 m_schema->addSchemaFile(path.c_str());
319}
320
321void YangAccess::addSchemaDir(const std::string& path)
322{
323 m_schema->addSchemaDirectory(path.c_str());
324}
325
326void YangAccess::enableFeature(const std::string& module, const std::string& feature)
327{
328 m_schema->enableFeature(module, feature);
329}
Václav Kubernát548cb192020-06-26 14:00:42 +0200330
331void YangAccess::addDataFile(const std::string& path)
332{
333 std::ifstream fs(path);
334 char firstChar;
335 fs >> firstChar;
336
337 std::cout << "Parsing \"" << path << "\" as " << (firstChar == '{' ? "JSON" : "XML") << "...\n";
338 auto dataNode = lyd_parse_path(m_ctx.get(), path.c_str(), firstChar == '{' ? LYD_JSON : LYD_XML, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB | LYD_OPT_TRUSTED);
339
340 if (!dataNode) {
341 throw std::runtime_error("Supplied data file " + path + " couldn't be parsed.");
342 }
343
344 if (!m_datastore) {
345 m_datastore = lyWrap(dataNode);
346 } else {
347 lyd_merge(m_datastore.get(), dataNode, LYD_OPT_DESTRUCT);
348 }
349
350 validate();
351}