blob: b8631d5d5889f0cdd381b2421d8a32f205afc426 [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;
Václav Kubernát94bb7cf2021-02-03 09:59:39 +0100134 lyNodesToTree(res, setWrapper.data());
Václav Kubernát74487df2020-06-04 01:29:28 +0200135 return res;
136}
137
138void YangAccess::setLeaf(const std::string& path, leaf_data_ value)
139{
140 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
141 impl_newPath(path, lyValue);
142}
143
144void YangAccess::createItem(const std::string& path)
145{
146 impl_newPath(path);
147}
148
149void YangAccess::deleteItem(const std::string& path)
150{
151 impl_removeNode(path);
152}
153
154namespace {
155struct impl_moveItem {
156 DatastoreType& m_datastore;
157 lyd_node* m_sourceNode;
158
159 void operator()(yang::move::Absolute absolute) const
160 {
161 auto set = lyWrap(lyd_find_instance(m_sourceNode, m_sourceNode->schema));
162 if (set->number == 1) { // m_sourceNode is the sole instance, do nothing
163 return;
164 }
165
166 doUnlink();
167 switch (absolute) {
168 case yang::move::Absolute::Begin:
169 if (set->set.d[0] == m_sourceNode) { // List is already at the beginning, do nothing
170 return;
171 }
172 lyd_insert_before(set->set.d[0], m_sourceNode);
173 return;
174 case yang::move::Absolute::End:
175 if (set->set.d[set->number - 1] == m_sourceNode) { // List is already at the end, do nothing
176 return;
177 }
178 lyd_insert_after(set->set.d[set->number - 1], m_sourceNode);
179 return;
180 }
181 }
182
183 void operator()(const yang::move::Relative& relative) const
184 {
185 auto keySuffix = m_sourceNode->schema->nodetype == LYS_LIST ? instanceToString(relative.m_path)
186 : leafDataToString(relative.m_path.at("."));
187 lyd_node* destNode;
188 lyd_find_sibling_val(m_sourceNode, m_sourceNode->schema, keySuffix.c_str(), &destNode);
189
190 doUnlink();
191 if (relative.m_position == yang::move::Relative::Position::After) {
192 lyd_insert_after(destNode, m_sourceNode);
193 } else {
194 lyd_insert_before(destNode, m_sourceNode);
195 }
196 }
197
198private:
199 void doUnlink() const
200 {
201 impl_unlink(m_datastore, m_sourceNode);
202 }
203};
204}
205
206void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
207{
208 auto set = lyWrap(lyd_find_path(m_datastore.get(), source.c_str()));
209 if (!set) { // Error, the node probably doesn't exist in the schema
210 getErrorsAndThrow();
211 }
212 if (set->number == 0) {
213 return;
214 }
215 auto sourceNode = set->set.d[0];
216 std::visit(impl_moveItem{m_datastore, sourceNode}, move);
217}
218
219void YangAccess::commitChanges()
220{
221 validate();
222}
223
224void YangAccess::discardChanges()
225{
226}
227
Václav Kubernátb3960f82020-12-01 03:21:48 +0100228[[noreturn]] DatastoreAccess::Tree YangAccess::execute(const std::string& path, const Tree& input)
Václav Kubernát74487df2020-06-04 01:29:28 +0200229{
230 auto root = lyWrap(lyd_new_path(nullptr, m_ctx.get(), path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0));
231 if (!root) {
232 getErrorsAndThrow();
233 }
234 for (const auto& [k, v] : input) {
Václav Kubernáte7248b22020-06-26 15:38:59 +0200235 if (v.type() == typeid(special_) && boost::get<special_>(v).m_value != SpecialValue::PresenceContainer) {
236 continue;
237 }
Václav Kubernát94bb7cf2021-02-03 09:59:39 +0100238
239 lyd_new_path(root.get(), m_ctx.get(), k.c_str(), (void*)leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, LYD_PATH_OPT_UPDATE);
Václav Kubernát74487df2020-06-04 01:29:28 +0200240 }
Václav Kubernátb3960f82020-12-01 03:21:48 +0100241 throw std::logic_error("in-memory datastore doesn't support executing RPC/action");
Václav Kubernát74487df2020-06-04 01:29:28 +0200242}
243
244void YangAccess::copyConfig(const Datastore source, const Datastore dest)
245{
246 if (source == Datastore::Startup && dest == Datastore::Running) {
247 m_datastore = nullptr;
248 }
249}
250
251std::shared_ptr<Schema> YangAccess::schema()
252{
253 return m_schema;
254}
255
256std::vector<ListInstance> YangAccess::listInstances(const std::string& path)
257{
258 std::vector<ListInstance> res;
259 if (!m_datastore) {
260 return res;
261 }
262
263 auto instances = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
264 auto instancesWrapper = libyang::Set(instances.get(), nullptr);
265 for (const auto& list : instancesWrapper.data()) {
266 ListInstance instance;
267 for (const auto& child : list->child()->tree_for()) {
268 if (child->schema()->nodetype() == LYS_LEAF) {
269 libyang::Schema_Node_Leaf leafSchema(child->schema());
270 if (leafSchema.is_key()) {
Václav Kubernát2e4cafe2020-11-05 01:53:21 +0100271 auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(child);
272 instance.insert({leafSchema.name(), leafValueFromNode(leafData)});
Václav Kubernát74487df2020-06-04 01:29:28 +0200273 }
274 }
275 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200276 res.emplace_back(instance);
Václav Kubernát74487df2020-06-04 01:29:28 +0200277 }
278 return res;
279}
280
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200281std::string YangAccess::dump(const DataFormat format) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200282{
283 char* output;
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200284 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 +0200285 std::unique_ptr<char, decltype(&free)> deleter{output, free};
Václav Kubernát74487df2020-06-04 01:29:28 +0200286
287 if (output) {
288 std::string res = output;
Václav Kubernát74487df2020-06-04 01:29:28 +0200289 return res;
290 }
291
292 return "";
293}
294
Václav Kubernát619e6542020-06-29 14:13:43 +0200295void YangAccess::loadModule(const std::string& name)
296{
297 m_schema->loadModule(name);
298}
299
Václav Kubernát74487df2020-06-04 01:29:28 +0200300void YangAccess::addSchemaFile(const std::string& path)
301{
302 m_schema->addSchemaFile(path.c_str());
303}
304
305void YangAccess::addSchemaDir(const std::string& path)
306{
307 m_schema->addSchemaDirectory(path.c_str());
308}
309
310void YangAccess::enableFeature(const std::string& module, const std::string& feature)
311{
312 m_schema->enableFeature(module, feature);
313}
Václav Kubernát548cb192020-06-26 14:00:42 +0200314
315void YangAccess::addDataFile(const std::string& path)
316{
317 std::ifstream fs(path);
318 char firstChar;
319 fs >> firstChar;
320
321 std::cout << "Parsing \"" << path << "\" as " << (firstChar == '{' ? "JSON" : "XML") << "...\n";
322 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);
323
324 if (!dataNode) {
325 throw std::runtime_error("Supplied data file " + path + " couldn't be parsed.");
326 }
327
328 if (!m_datastore) {
329 m_datastore = lyWrap(dataNode);
330 } else {
331 lyd_merge(m_datastore.get(), dataNode, LYD_OPT_DESTRUCT);
332 }
333
334 validate();
335}