blob: c509231559fbcf5b79e63d489e65f10f6f55f7d4 [file] [log] [blame]
Václav Kubernát74487df2020-06-04 01:29:28 +02001#include <boost/algorithm/string/predicate.hpp>
2#include <experimental/iterator>
3#include <iostream>
4#include <libyang/Tree_Data.hpp>
5#include <libyang/libyang.h>
6#include "UniqueResource.hpp"
7#include "libyang_utils.hpp"
8#include "utils.hpp"
9#include "yang_access.hpp"
10#include "yang_schema.hpp"
11
12namespace {
13template <typename Type> using lyPtrDeleter_type = void (*)(Type*);
14template <typename Type> const lyPtrDeleter_type<Type> lyPtrDeleter;
15template <> const auto lyPtrDeleter<ly_set> = ly_set_free;
16template <> const auto lyPtrDeleter<ly_ctx> = static_cast<lyPtrDeleter_type<ly_ctx>>([] (auto* ptr) {ly_ctx_destroy(ptr, nullptr);});
17template <> const auto lyPtrDeleter<lyd_node> = lyd_free_withsiblings;
18
19template <typename Type>
20auto lyWrap(Type* ptr)
21{
22 return std::unique_ptr<Type, lyPtrDeleter_type<Type>>{ptr, lyPtrDeleter<Type>};
23}
24
25// Convenient for functions that take m_datastore as an argument
26using DatastoreType = std::unique_ptr<lyd_node, lyPtrDeleter_type<lyd_node>>;
27}
28
29YangAccess::YangAccess()
30 : m_ctx(lyWrap(ly_ctx_new(nullptr, LY_CTX_DISABLE_SEARCHDIR_CWD)))
31 , m_datastore(lyWrap<lyd_node>(nullptr))
32 , m_schema(std::make_shared<YangSchema>(libyang::create_new_Context(m_ctx.get())))
33{
34}
35
36YangAccess::~YangAccess()
37{
38}
39
40[[noreturn]] void YangAccess::getErrorsAndThrow() const
41{
42 auto errors = libyang::get_ly_errors(libyang::create_new_Context(m_ctx.get()));
43 std::vector<DatastoreError> errorsRes;
44 for (const auto& error : errors) {
45 using namespace std::string_view_literals;
46 errorsRes.emplace_back(error->errmsg(), error->errpath() != ""sv ? std::optional{error->errpath()} : std::nullopt);
47 }
48
49 throw DatastoreException(errorsRes);
50}
51
52void YangAccess::impl_newPath(const std::string& path, const std::optional<std::string>& value)
53{
54 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);
55 if (!newNode) {
56 getErrorsAndThrow();
57 }
58 if (!m_datastore) {
59 m_datastore = lyWrap(newNode);
60 }
61}
62
63namespace {
64void impl_unlink(DatastoreType& datastore, lyd_node* what)
65{
66 // 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)
67 if (datastore.get() == what) {
68 auto oldDatastore = datastore.release();
69 if (oldDatastore->prev != oldDatastore) {
70 datastore = lyWrap(oldDatastore->prev);
71 } else {
72 datastore = lyWrap(oldDatastore->next);
73 }
74 }
75
76 lyd_unlink(what);
77}
78}
79
80void YangAccess::impl_removeNode(const std::string& path)
81{
82 auto set = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
83 if (!set || set->number == 0) {
84 // Check if schema node exists - lyd_find_path first checks if the first argument is non-null before checking for path validity
85 if (!ly_ctx_get_node(m_ctx.get(), nullptr, path.c_str(), 0)) {
86 throw DatastoreException{{DatastoreError{"Schema node doesn't exist.", path}}};
87 }
88 // Check if libyang found another error
89 if (ly_err_first(m_ctx.get())) {
90 getErrorsAndThrow();
91 }
92
93 // Otherwise the datastore just doesn't contain the wanted node.
94 throw DatastoreException{{DatastoreError{"Data node doesn't exist.", path}}};
95 }
96
97 auto toRemove = set->set.d[0];
98
99 impl_unlink(m_datastore, toRemove);
100
101 lyd_free(toRemove);
102}
103
104void YangAccess::validate()
105{
106 auto datastore = m_datastore.release();
107 lyd_validate(&datastore, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB, m_ctx.get());
108 m_datastore = lyWrap(datastore);
109}
110
Václav Kubernátd6282912020-06-23 14:49:34 +0200111DatastoreAccess::Tree YangAccess::getItems(const std::string& path) const
Václav Kubernát74487df2020-06-04 01:29:28 +0200112{
113 DatastoreAccess::Tree res;
114 if (!m_datastore) {
115 return res;
116 }
117
118 auto set = lyWrap(lyd_find_path(m_datastore.get(), path == "/" ? "/*" : path.c_str()));
119 auto setWrapper = libyang::Set(set.get(), nullptr);
120
121 lyNodesToTree(res, setWrapper.data());
122 return res;
123}
124
125void YangAccess::setLeaf(const std::string& path, leaf_data_ value)
126{
127 auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
128 impl_newPath(path, lyValue);
129}
130
131void YangAccess::createItem(const std::string& path)
132{
133 impl_newPath(path);
134}
135
136void YangAccess::deleteItem(const std::string& path)
137{
138 impl_removeNode(path);
139}
140
141namespace {
142struct impl_moveItem {
143 DatastoreType& m_datastore;
144 lyd_node* m_sourceNode;
145
146 void operator()(yang::move::Absolute absolute) const
147 {
148 auto set = lyWrap(lyd_find_instance(m_sourceNode, m_sourceNode->schema));
149 if (set->number == 1) { // m_sourceNode is the sole instance, do nothing
150 return;
151 }
152
153 doUnlink();
154 switch (absolute) {
155 case yang::move::Absolute::Begin:
156 if (set->set.d[0] == m_sourceNode) { // List is already at the beginning, do nothing
157 return;
158 }
159 lyd_insert_before(set->set.d[0], m_sourceNode);
160 return;
161 case yang::move::Absolute::End:
162 if (set->set.d[set->number - 1] == m_sourceNode) { // List is already at the end, do nothing
163 return;
164 }
165 lyd_insert_after(set->set.d[set->number - 1], m_sourceNode);
166 return;
167 }
168 }
169
170 void operator()(const yang::move::Relative& relative) const
171 {
172 auto keySuffix = m_sourceNode->schema->nodetype == LYS_LIST ? instanceToString(relative.m_path)
173 : leafDataToString(relative.m_path.at("."));
174 lyd_node* destNode;
175 lyd_find_sibling_val(m_sourceNode, m_sourceNode->schema, keySuffix.c_str(), &destNode);
176
177 doUnlink();
178 if (relative.m_position == yang::move::Relative::Position::After) {
179 lyd_insert_after(destNode, m_sourceNode);
180 } else {
181 lyd_insert_before(destNode, m_sourceNode);
182 }
183 }
184
185private:
186 void doUnlink() const
187 {
188 impl_unlink(m_datastore, m_sourceNode);
189 }
190};
191}
192
193void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
194{
195 auto set = lyWrap(lyd_find_path(m_datastore.get(), source.c_str()));
196 if (!set) { // Error, the node probably doesn't exist in the schema
197 getErrorsAndThrow();
198 }
199 if (set->number == 0) {
200 return;
201 }
202 auto sourceNode = set->set.d[0];
203 std::visit(impl_moveItem{m_datastore, sourceNode}, move);
204}
205
206void YangAccess::commitChanges()
207{
208 validate();
209}
210
211void YangAccess::discardChanges()
212{
213}
214
215DatastoreAccess::Tree YangAccess::executeRpc(const std::string& path, const Tree& input)
216{
217 auto root = lyWrap(lyd_new_path(nullptr, m_ctx.get(), path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0));
218 if (!root) {
219 getErrorsAndThrow();
220 }
221 for (const auto& [k, v] : input) {
222 auto node = lyd_new_path(root.get(), m_ctx.get(), joinPaths(path, k).c_str(), (void*)leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, 0);
223 if (!node) {
224 getErrorsAndThrow();
225 }
226 }
227 throw std::logic_error("in-memory datastore doesn't support executing RPCs.");
228}
229
230void YangAccess::copyConfig(const Datastore source, const Datastore dest)
231{
232 if (source == Datastore::Startup && dest == Datastore::Running) {
233 m_datastore = nullptr;
234 }
235}
236
237std::shared_ptr<Schema> YangAccess::schema()
238{
239 return m_schema;
240}
241
242std::vector<ListInstance> YangAccess::listInstances(const std::string& path)
243{
244 std::vector<ListInstance> res;
245 if (!m_datastore) {
246 return res;
247 }
248
249 auto instances = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
250 auto instancesWrapper = libyang::Set(instances.get(), nullptr);
251 for (const auto& list : instancesWrapper.data()) {
252 ListInstance instance;
253 for (const auto& child : list->child()->tree_for()) {
254 if (child->schema()->nodetype() == LYS_LEAF) {
255 libyang::Schema_Node_Leaf leafSchema(child->schema());
256 if (leafSchema.is_key()) {
257 libyang::Data_Node_Leaf_List leafData(child);
258 instance.insert({leafSchema.name(), leafValueFromValue(leafData.value(), leafSchema.type()->base())});
259 }
260 }
261 }
262 res.push_back(instance);
263 }
264 return res;
265}
266
267std::string impl_dumpConfig(const lyd_node* datastore, LYD_FORMAT format)
268{
269 char* output;
270 lyd_print_mem(&output, datastore, format, LYP_WITHSIBLINGS);
271
272 if (output) {
273 std::string res = output;
274 free(output);
275 return res;
276 }
277
278 return "";
279}
280
281std::string YangAccess::dumpXML() const
282{
283 return impl_dumpConfig(m_datastore.get(), LYD_XML);
284}
285
286std::string YangAccess::dumpJSON() const
287{
288 return impl_dumpConfig(m_datastore.get(), LYD_JSON);
289}
290
291void YangAccess::addSchemaFile(const std::string& path)
292{
293 m_schema->addSchemaFile(path.c_str());
294}
295
296void YangAccess::addSchemaDir(const std::string& path)
297{
298 m_schema->addSchemaDirectory(path.c_str());
299}
300
301void YangAccess::enableFeature(const std::string& module, const std::string& feature)
302{
303 m_schema->enableFeature(module, feature);
304}