blob: 7ca30a210b08c8463b3d9919f0f3c6e034bab557 [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())))
34{
35}
36
Václav Kubernát51fa48e2020-07-08 17:17:34 +020037YangAccess::~YangAccess() = default;
Václav Kubernát74487df2020-06-04 01:29:28 +020038
39[[noreturn]] void YangAccess::getErrorsAndThrow() const
40{
41 auto errors = libyang::get_ly_errors(libyang::create_new_Context(m_ctx.get()));
42 std::vector<DatastoreError> errorsRes;
43 for (const auto& error : errors) {
44 using namespace std::string_view_literals;
45 errorsRes.emplace_back(error->errmsg(), error->errpath() != ""sv ? std::optional{error->errpath()} : std::nullopt);
46 }
47
48 throw DatastoreException(errorsRes);
49}
50
51void YangAccess::impl_newPath(const std::string& path, const std::optional<std::string>& value)
52{
53 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);
54 if (!newNode) {
55 getErrorsAndThrow();
56 }
57 if (!m_datastore) {
58 m_datastore = lyWrap(newNode);
59 }
60}
61
62namespace {
63void impl_unlink(DatastoreType& datastore, lyd_node* what)
64{
65 // 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)
66 if (datastore.get() == what) {
67 auto oldDatastore = datastore.release();
68 if (oldDatastore->prev != oldDatastore) {
69 datastore = lyWrap(oldDatastore->prev);
70 } else {
71 datastore = lyWrap(oldDatastore->next);
72 }
73 }
74
75 lyd_unlink(what);
76}
77}
78
79void YangAccess::impl_removeNode(const std::string& path)
80{
81 auto set = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
82 if (!set || set->number == 0) {
83 // Check if schema node exists - lyd_find_path first checks if the first argument is non-null before checking for path validity
84 if (!ly_ctx_get_node(m_ctx.get(), nullptr, path.c_str(), 0)) {
85 throw DatastoreException{{DatastoreError{"Schema node doesn't exist.", path}}};
86 }
87 // Check if libyang found another error
88 if (ly_err_first(m_ctx.get())) {
89 getErrorsAndThrow();
90 }
91
92 // Otherwise the datastore just doesn't contain the wanted node.
93 throw DatastoreException{{DatastoreError{"Data node doesn't exist.", path}}};
94 }
95
96 auto toRemove = set->set.d[0];
97
98 impl_unlink(m_datastore, toRemove);
99
100 lyd_free(toRemove);
101}
102
103void YangAccess::validate()
104{
105 auto datastore = m_datastore.release();
106 lyd_validate(&datastore, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB, m_ctx.get());
107 m_datastore = lyWrap(datastore);
108}
109
Václav Kubernátd6282912020-06-23 14:49:34 +0200110DatastoreAccess::Tree YangAccess::getItems(const std::string& path) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200111{
112 DatastoreAccess::Tree res;
113 if (!m_datastore) {
114 return res;
115 }
116
117 auto set = lyWrap(lyd_find_path(m_datastore.get(), path == "/" ? "/*" : path.c_str()));
118 auto setWrapper = libyang::Set(set.get(), nullptr);
119
120 lyNodesToTree(res, setWrapper.data());
121 return res;
122}
123
124void YangAccess::setLeaf(const std::string& path, leaf_data_ value)
125{
126 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
127 impl_newPath(path, lyValue);
128}
129
130void YangAccess::createItem(const std::string& path)
131{
132 impl_newPath(path);
133}
134
135void YangAccess::deleteItem(const std::string& path)
136{
137 impl_removeNode(path);
138}
139
140namespace {
141struct impl_moveItem {
142 DatastoreType& m_datastore;
143 lyd_node* m_sourceNode;
144
145 void operator()(yang::move::Absolute absolute) const
146 {
147 auto set = lyWrap(lyd_find_instance(m_sourceNode, m_sourceNode->schema));
148 if (set->number == 1) { // m_sourceNode is the sole instance, do nothing
149 return;
150 }
151
152 doUnlink();
153 switch (absolute) {
154 case yang::move::Absolute::Begin:
155 if (set->set.d[0] == m_sourceNode) { // List is already at the beginning, do nothing
156 return;
157 }
158 lyd_insert_before(set->set.d[0], m_sourceNode);
159 return;
160 case yang::move::Absolute::End:
161 if (set->set.d[set->number - 1] == m_sourceNode) { // List is already at the end, do nothing
162 return;
163 }
164 lyd_insert_after(set->set.d[set->number - 1], m_sourceNode);
165 return;
166 }
167 }
168
169 void operator()(const yang::move::Relative& relative) const
170 {
171 auto keySuffix = m_sourceNode->schema->nodetype == LYS_LIST ? instanceToString(relative.m_path)
172 : leafDataToString(relative.m_path.at("."));
173 lyd_node* destNode;
174 lyd_find_sibling_val(m_sourceNode, m_sourceNode->schema, keySuffix.c_str(), &destNode);
175
176 doUnlink();
177 if (relative.m_position == yang::move::Relative::Position::After) {
178 lyd_insert_after(destNode, m_sourceNode);
179 } else {
180 lyd_insert_before(destNode, m_sourceNode);
181 }
182 }
183
184private:
185 void doUnlink() const
186 {
187 impl_unlink(m_datastore, m_sourceNode);
188 }
189};
190}
191
192void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
193{
194 auto set = lyWrap(lyd_find_path(m_datastore.get(), source.c_str()));
195 if (!set) { // Error, the node probably doesn't exist in the schema
196 getErrorsAndThrow();
197 }
198 if (set->number == 0) {
199 return;
200 }
201 auto sourceNode = set->set.d[0];
202 std::visit(impl_moveItem{m_datastore, sourceNode}, move);
203}
204
205void YangAccess::commitChanges()
206{
207 validate();
208}
209
210void YangAccess::discardChanges()
211{
212}
213
214DatastoreAccess::Tree YangAccess::executeRpc(const std::string& path, const Tree& input)
215{
216 auto root = lyWrap(lyd_new_path(nullptr, m_ctx.get(), path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0));
217 if (!root) {
218 getErrorsAndThrow();
219 }
220 for (const auto& [k, v] : input) {
221 auto node = lyd_new_path(root.get(), m_ctx.get(), joinPaths(path, k).c_str(), (void*)leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, 0);
222 if (!node) {
223 getErrorsAndThrow();
224 }
225 }
226 throw std::logic_error("in-memory datastore doesn't support executing RPCs.");
227}
228
229void YangAccess::copyConfig(const Datastore source, const Datastore dest)
230{
231 if (source == Datastore::Startup && dest == Datastore::Running) {
232 m_datastore = nullptr;
233 }
234}
235
236std::shared_ptr<Schema> YangAccess::schema()
237{
238 return m_schema;
239}
240
241std::vector<ListInstance> YangAccess::listInstances(const std::string& path)
242{
243 std::vector<ListInstance> res;
244 if (!m_datastore) {
245 return res;
246 }
247
248 auto instances = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
249 auto instancesWrapper = libyang::Set(instances.get(), nullptr);
250 for (const auto& list : instancesWrapper.data()) {
251 ListInstance instance;
252 for (const auto& child : list->child()->tree_for()) {
253 if (child->schema()->nodetype() == LYS_LEAF) {
254 libyang::Schema_Node_Leaf leafSchema(child->schema());
255 if (leafSchema.is_key()) {
256 libyang::Data_Node_Leaf_List leafData(child);
257 instance.insert({leafSchema.name(), leafValueFromValue(leafData.value(), leafSchema.type()->base())});
258 }
259 }
260 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200261 res.emplace_back(instance);
Václav Kubernát74487df2020-06-04 01:29:28 +0200262 }
263 return res;
264}
265
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200266std::string YangAccess::dump(const DataFormat format) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200267{
268 char* output;
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200269 lyd_print_mem(&output, m_datastore.get(), format == DataFormat::Xml ? LYD_XML : LYD_JSON, LYP_WITHSIBLINGS | LYP_FORMAT);
Václav Kubernát74487df2020-06-04 01:29:28 +0200270
271 if (output) {
272 std::string res = output;
273 free(output);
274 return res;
275 }
276
277 return "";
278}
279
Václav Kubernát619e6542020-06-29 14:13:43 +0200280void YangAccess::loadModule(const std::string& name)
281{
282 m_schema->loadModule(name);
283}
284
Václav Kubernát74487df2020-06-04 01:29:28 +0200285void YangAccess::addSchemaFile(const std::string& path)
286{
287 m_schema->addSchemaFile(path.c_str());
288}
289
290void YangAccess::addSchemaDir(const std::string& path)
291{
292 m_schema->addSchemaDirectory(path.c_str());
293}
294
295void YangAccess::enableFeature(const std::string& module, const std::string& feature)
296{
297 m_schema->enableFeature(module, feature);
298}
Václav Kubernát548cb192020-06-26 14:00:42 +0200299
300void YangAccess::addDataFile(const std::string& path)
301{
302 std::ifstream fs(path);
303 char firstChar;
304 fs >> firstChar;
305
306 std::cout << "Parsing \"" << path << "\" as " << (firstChar == '{' ? "JSON" : "XML") << "...\n";
307 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);
308
309 if (!dataNode) {
310 throw std::runtime_error("Supplied data file " + path + " couldn't be parsed.");
311 }
312
313 if (!m_datastore) {
314 m_datastore = lyWrap(dataNode);
315 } else {
316 lyd_merge(m_datastore.get(), dataNode, LYD_OPT_DESTRUCT);
317 }
318
319 validate();
320}