blob: 7a4350740b772f4fc0b8673778ef8dcac9cf0cd1 [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átb3960f82020-12-01 03:21:48 +0100232[[noreturn]] DatastoreAccess::Tree YangAccess::execute(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átb3960f82020-12-01 03:21:48 +0100247 throw std::logic_error("in-memory datastore doesn't support executing RPC/action");
Václav Kubernát74487df2020-06-04 01:29:28 +0200248}
249
250void YangAccess::copyConfig(const Datastore source, const Datastore dest)
251{
252 if (source == Datastore::Startup && dest == Datastore::Running) {
253 m_datastore = nullptr;
254 }
255}
256
257std::shared_ptr<Schema> YangAccess::schema()
258{
259 return m_schema;
260}
261
262std::vector<ListInstance> YangAccess::listInstances(const std::string& path)
263{
264 std::vector<ListInstance> res;
265 if (!m_datastore) {
266 return res;
267 }
268
269 auto instances = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
270 auto instancesWrapper = libyang::Set(instances.get(), nullptr);
271 for (const auto& list : instancesWrapper.data()) {
272 ListInstance instance;
273 for (const auto& child : list->child()->tree_for()) {
274 if (child->schema()->nodetype() == LYS_LEAF) {
275 libyang::Schema_Node_Leaf leafSchema(child->schema());
276 if (leafSchema.is_key()) {
Václav Kubernát2e4cafe2020-11-05 01:53:21 +0100277 auto leafData = std::make_shared<libyang::Data_Node_Leaf_List>(child);
278 instance.insert({leafSchema.name(), leafValueFromNode(leafData)});
Václav Kubernát74487df2020-06-04 01:29:28 +0200279 }
280 }
281 }
Václav Kubernátfaacd022020-07-08 16:44:38 +0200282 res.emplace_back(instance);
Václav Kubernát74487df2020-06-04 01:29:28 +0200283 }
284 return res;
285}
286
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200287std::string YangAccess::dump(const DataFormat format) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200288{
289 char* output;
Václav Kubernát70d7f7a2020-06-23 14:40:40 +0200290 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 +0200291 std::unique_ptr<char, decltype(&free)> deleter{output, free};
Václav Kubernát74487df2020-06-04 01:29:28 +0200292
293 if (output) {
294 std::string res = output;
Václav Kubernát74487df2020-06-04 01:29:28 +0200295 return res;
296 }
297
298 return "";
299}
300
Václav Kubernát619e6542020-06-29 14:13:43 +0200301void YangAccess::loadModule(const std::string& name)
302{
303 m_schema->loadModule(name);
304}
305
Václav Kubernát74487df2020-06-04 01:29:28 +0200306void YangAccess::addSchemaFile(const std::string& path)
307{
308 m_schema->addSchemaFile(path.c_str());
309}
310
311void YangAccess::addSchemaDir(const std::string& path)
312{
313 m_schema->addSchemaDirectory(path.c_str());
314}
315
316void YangAccess::enableFeature(const std::string& module, const std::string& feature)
317{
318 m_schema->enableFeature(module, feature);
319}
Václav Kubernát548cb192020-06-26 14:00:42 +0200320
321void YangAccess::addDataFile(const std::string& path)
322{
323 std::ifstream fs(path);
324 char firstChar;
325 fs >> firstChar;
326
327 std::cout << "Parsing \"" << path << "\" as " << (firstChar == '{' ? "JSON" : "XML") << "...\n";
328 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);
329
330 if (!dataNode) {
331 throw std::runtime_error("Supplied data file " + path + " couldn't be parsed.");
332 }
333
334 if (!m_datastore) {
335 m_datastore = lyWrap(dataNode);
336 } else {
337 lyd_merge(m_datastore.get(), dataNode, LYD_OPT_DESTRUCT);
338 }
339
340 validate();
341}