Add yang-cli
The original idea for this was that I would libyang C++ bindings for
this. Unfortunately, there have been problems with them as explained
here: https://github.com/CESNET/libyang/issues/1106. The easiest
solution to this was to just use the C api of libyang. After creating
some safe wrappers around pointers, it wasn't too difficult.
Change-Id: I0421cb64df66c640956501e56ffc4122eef0b9b7
diff --git a/src/yang_access.cpp b/src/yang_access.cpp
new file mode 100644
index 0000000..71aebf1
--- /dev/null
+++ b/src/yang_access.cpp
@@ -0,0 +1,304 @@
+#include <boost/algorithm/string/predicate.hpp>
+#include <experimental/iterator>
+#include <iostream>
+#include <libyang/Tree_Data.hpp>
+#include <libyang/libyang.h>
+#include "UniqueResource.hpp"
+#include "libyang_utils.hpp"
+#include "utils.hpp"
+#include "yang_access.hpp"
+#include "yang_schema.hpp"
+
+namespace {
+template <typename Type> using lyPtrDeleter_type = void (*)(Type*);
+template <typename Type> const lyPtrDeleter_type<Type> lyPtrDeleter;
+template <> const auto lyPtrDeleter<ly_set> = ly_set_free;
+template <> const auto lyPtrDeleter<ly_ctx> = static_cast<lyPtrDeleter_type<ly_ctx>>([] (auto* ptr) {ly_ctx_destroy(ptr, nullptr);});
+template <> const auto lyPtrDeleter<lyd_node> = lyd_free_withsiblings;
+
+template <typename Type>
+auto lyWrap(Type* ptr)
+{
+ return std::unique_ptr<Type, lyPtrDeleter_type<Type>>{ptr, lyPtrDeleter<Type>};
+}
+
+// Convenient for functions that take m_datastore as an argument
+using DatastoreType = std::unique_ptr<lyd_node, lyPtrDeleter_type<lyd_node>>;
+}
+
+YangAccess::YangAccess()
+ : m_ctx(lyWrap(ly_ctx_new(nullptr, LY_CTX_DISABLE_SEARCHDIR_CWD)))
+ , m_datastore(lyWrap<lyd_node>(nullptr))
+ , m_schema(std::make_shared<YangSchema>(libyang::create_new_Context(m_ctx.get())))
+{
+}
+
+YangAccess::~YangAccess()
+{
+}
+
+[[noreturn]] void YangAccess::getErrorsAndThrow() const
+{
+ auto errors = libyang::get_ly_errors(libyang::create_new_Context(m_ctx.get()));
+ std::vector<DatastoreError> errorsRes;
+ for (const auto& error : errors) {
+ using namespace std::string_view_literals;
+ errorsRes.emplace_back(error->errmsg(), error->errpath() != ""sv ? std::optional{error->errpath()} : std::nullopt);
+ }
+
+ throw DatastoreException(errorsRes);
+}
+
+void YangAccess::impl_newPath(const std::string& path, const std::optional<std::string>& value)
+{
+ 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);
+ if (!newNode) {
+ getErrorsAndThrow();
+ }
+ if (!m_datastore) {
+ m_datastore = lyWrap(newNode);
+ }
+}
+
+namespace {
+void impl_unlink(DatastoreType& datastore, lyd_node* what)
+{
+ // 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)
+ if (datastore.get() == what) {
+ auto oldDatastore = datastore.release();
+ if (oldDatastore->prev != oldDatastore) {
+ datastore = lyWrap(oldDatastore->prev);
+ } else {
+ datastore = lyWrap(oldDatastore->next);
+ }
+ }
+
+ lyd_unlink(what);
+}
+}
+
+void YangAccess::impl_removeNode(const std::string& path)
+{
+ auto set = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
+ if (!set || set->number == 0) {
+ // Check if schema node exists - lyd_find_path first checks if the first argument is non-null before checking for path validity
+ if (!ly_ctx_get_node(m_ctx.get(), nullptr, path.c_str(), 0)) {
+ throw DatastoreException{{DatastoreError{"Schema node doesn't exist.", path}}};
+ }
+ // Check if libyang found another error
+ if (ly_err_first(m_ctx.get())) {
+ getErrorsAndThrow();
+ }
+
+ // Otherwise the datastore just doesn't contain the wanted node.
+ throw DatastoreException{{DatastoreError{"Data node doesn't exist.", path}}};
+ }
+
+ auto toRemove = set->set.d[0];
+
+ impl_unlink(m_datastore, toRemove);
+
+ lyd_free(toRemove);
+}
+
+void YangAccess::validate()
+{
+ auto datastore = m_datastore.release();
+ lyd_validate(&datastore, LYD_OPT_DATA | LYD_OPT_DATA_NO_YANGLIB, m_ctx.get());
+ m_datastore = lyWrap(datastore);
+}
+
+DatastoreAccess::Tree YangAccess::getItems(const std::string& path)
+{
+ DatastoreAccess::Tree res;
+ if (!m_datastore) {
+ return res;
+ }
+
+ auto set = lyWrap(lyd_find_path(m_datastore.get(), path == "/" ? "/*" : path.c_str()));
+ auto setWrapper = libyang::Set(set.get(), nullptr);
+
+ lyNodesToTree(res, setWrapper.data());
+ return res;
+}
+
+void YangAccess::setLeaf(const std::string& path, leaf_data_ value)
+{
+ auto lyValue = value.type() == typeid(empty_) ? std::nullopt : std::optional(leafDataToString(value));
+ impl_newPath(path, lyValue);
+}
+
+void YangAccess::createItem(const std::string& path)
+{
+ impl_newPath(path);
+}
+
+void YangAccess::deleteItem(const std::string& path)
+{
+ impl_removeNode(path);
+}
+
+namespace {
+struct impl_moveItem {
+ DatastoreType& m_datastore;
+ lyd_node* m_sourceNode;
+
+ void operator()(yang::move::Absolute absolute) const
+ {
+ auto set = lyWrap(lyd_find_instance(m_sourceNode, m_sourceNode->schema));
+ if (set->number == 1) { // m_sourceNode is the sole instance, do nothing
+ return;
+ }
+
+ doUnlink();
+ switch (absolute) {
+ case yang::move::Absolute::Begin:
+ if (set->set.d[0] == m_sourceNode) { // List is already at the beginning, do nothing
+ return;
+ }
+ lyd_insert_before(set->set.d[0], m_sourceNode);
+ return;
+ case yang::move::Absolute::End:
+ if (set->set.d[set->number - 1] == m_sourceNode) { // List is already at the end, do nothing
+ return;
+ }
+ lyd_insert_after(set->set.d[set->number - 1], m_sourceNode);
+ return;
+ }
+ }
+
+ void operator()(const yang::move::Relative& relative) const
+ {
+ auto keySuffix = m_sourceNode->schema->nodetype == LYS_LIST ? instanceToString(relative.m_path)
+ : leafDataToString(relative.m_path.at("."));
+ lyd_node* destNode;
+ lyd_find_sibling_val(m_sourceNode, m_sourceNode->schema, keySuffix.c_str(), &destNode);
+
+ doUnlink();
+ if (relative.m_position == yang::move::Relative::Position::After) {
+ lyd_insert_after(destNode, m_sourceNode);
+ } else {
+ lyd_insert_before(destNode, m_sourceNode);
+ }
+ }
+
+private:
+ void doUnlink() const
+ {
+ impl_unlink(m_datastore, m_sourceNode);
+ }
+};
+}
+
+void YangAccess::moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move)
+{
+ auto set = lyWrap(lyd_find_path(m_datastore.get(), source.c_str()));
+ if (!set) { // Error, the node probably doesn't exist in the schema
+ getErrorsAndThrow();
+ }
+ if (set->number == 0) {
+ return;
+ }
+ auto sourceNode = set->set.d[0];
+ std::visit(impl_moveItem{m_datastore, sourceNode}, move);
+}
+
+void YangAccess::commitChanges()
+{
+ validate();
+}
+
+void YangAccess::discardChanges()
+{
+}
+
+DatastoreAccess::Tree YangAccess::executeRpc(const std::string& path, const Tree& input)
+{
+ auto root = lyWrap(lyd_new_path(nullptr, m_ctx.get(), path.c_str(), nullptr, LYD_ANYDATA_CONSTSTRING, 0));
+ if (!root) {
+ getErrorsAndThrow();
+ }
+ for (const auto& [k, v] : input) {
+ auto node = lyd_new_path(root.get(), m_ctx.get(), joinPaths(path, k).c_str(), (void*)leafDataToString(v).c_str(), LYD_ANYDATA_CONSTSTRING, 0);
+ if (!node) {
+ getErrorsAndThrow();
+ }
+ }
+ throw std::logic_error("in-memory datastore doesn't support executing RPCs.");
+}
+
+void YangAccess::copyConfig(const Datastore source, const Datastore dest)
+{
+ if (source == Datastore::Startup && dest == Datastore::Running) {
+ m_datastore = nullptr;
+ }
+}
+
+std::shared_ptr<Schema> YangAccess::schema()
+{
+ return m_schema;
+}
+
+std::vector<ListInstance> YangAccess::listInstances(const std::string& path)
+{
+ std::vector<ListInstance> res;
+ if (!m_datastore) {
+ return res;
+ }
+
+ auto instances = lyWrap(lyd_find_path(m_datastore.get(), path.c_str()));
+ auto instancesWrapper = libyang::Set(instances.get(), nullptr);
+ for (const auto& list : instancesWrapper.data()) {
+ ListInstance instance;
+ for (const auto& child : list->child()->tree_for()) {
+ if (child->schema()->nodetype() == LYS_LEAF) {
+ libyang::Schema_Node_Leaf leafSchema(child->schema());
+ if (leafSchema.is_key()) {
+ libyang::Data_Node_Leaf_List leafData(child);
+ instance.insert({leafSchema.name(), leafValueFromValue(leafData.value(), leafSchema.type()->base())});
+ }
+ }
+ }
+ res.push_back(instance);
+ }
+ return res;
+}
+
+std::string impl_dumpConfig(const lyd_node* datastore, LYD_FORMAT format)
+{
+ char* output;
+ lyd_print_mem(&output, datastore, format, LYP_WITHSIBLINGS);
+
+ if (output) {
+ std::string res = output;
+ free(output);
+ return res;
+ }
+
+ return "";
+}
+
+std::string YangAccess::dumpXML() const
+{
+ return impl_dumpConfig(m_datastore.get(), LYD_XML);
+}
+
+std::string YangAccess::dumpJSON() const
+{
+ return impl_dumpConfig(m_datastore.get(), LYD_JSON);
+}
+
+void YangAccess::addSchemaFile(const std::string& path)
+{
+ m_schema->addSchemaFile(path.c_str());
+}
+
+void YangAccess::addSchemaDir(const std::string& path)
+{
+ m_schema->addSchemaDirectory(path.c_str());
+}
+
+void YangAccess::enableFeature(const std::string& module, const std::string& feature)
+{
+ m_schema->enableFeature(module, feature);
+}