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