Migrate to libyang2
libnetconf2: getSchema and getConfig were no longer used in netconf-cli,
so I deleted them. They can get readded once the bindings get split into
a separate project.
sysrepo_access: Some sr_val stuff was removed.
YangSchema: type descriptions are not available
availableNodes returns only input nodes for RPC nodes
impl_getSchemaNode: no longer disables error printing
libyang: No longer supports leafrefs without the leaf it points to.
Depends-on: https://cesnet-gerrit-czechlight/c/CzechLight/dependencies/+/5171
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/5171
Change-Id: Ie49381a003a61a7bb028be7b2fa1d9d926ac4e58
diff --git a/src/netconf-client.cpp b/src/netconf-client.cpp
index 4638bcb..0685db6 100644
--- a/src/netconf-client.cpp
+++ b/src/netconf-client.cpp
@@ -7,7 +7,8 @@
*/
#include <cstring>
-#include <libyang/Tree_Data.hpp>
+#include <libyang-cpp/Context.hpp>
+#include <libyang-cpp/DataNode.hpp>
#include <mutex>
extern "C" {
#include <nc_client.h>
@@ -58,15 +59,6 @@
static std::mutex clientOptions;
-inline void custom_free_nc_reply_data(nc_reply_data* reply)
-{
- nc_reply_free(reinterpret_cast<nc_reply*>(reply));
-}
-inline void custom_free_nc_reply_error(nc_reply_error* reply)
-{
- nc_reply_free(reinterpret_cast<nc_reply*>(reply));
-}
-
char* ssh_auth_interactive_cb(const char* auth_name, const char* instruction, const char* prompt, int echo, void* priv)
{
const auto cb = static_cast<const client::KbdInteractiveCb*>(priv);
@@ -74,24 +66,19 @@
return ::strdup(res.c_str());
}
-template <typename Type> using deleter_type_for = void (*)(Type*);
-template <typename Type> deleter_type_for<Type> const deleter_for;
-
-template <> const auto deleter_for<nc_rpc> = nc_rpc_free;
-template <> const auto deleter_for<nc_reply> = nc_reply_free;
-template <> const auto deleter_for<nc_reply_data> = custom_free_nc_reply_data;
-template <> const auto deleter_for<nc_reply_error> = custom_free_nc_reply_error;
-
-template <typename T>
-using unique_ptr_for = std::unique_ptr<T, decltype(deleter_for<T>)>;
-
-template <typename T>
-auto guarded(T* ptr)
+auto guarded(nc_rpc* ptr)
{
- return unique_ptr_for<T>(ptr, deleter_for<T>);
+ return std::unique_ptr<nc_rpc, decltype(&nc_rpc_free)>(ptr, nc_rpc_free);
}
-unique_ptr_for<struct nc_reply> do_rpc(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
+namespace {
+const auto getData_path = "/ietf-netconf-nmda:get-data/data";
+const auto get_path = "/ietf-netconf:get/data";
+}
+
+using managed_rpc = std::invoke_result_t<decltype(guarded), nc_rpc*>;
+
+std::optional<libyang::DataNode> do_rpc(client::Session* session, managed_rpc&& rpc, const char* dataIdentifier)
{
uint64_t msgid;
NC_MSG_TYPE msgtype;
@@ -104,11 +91,11 @@
throw std::runtime_error{"Timeout sending an RPC"};
}
- struct nc_reply* raw_reply;
+ lyd_node* raw_reply;
+ lyd_node* envp;
while (true) {
- msgtype = nc_recv_reply(session->session_internal(), rpc.get(), msgid, 20000, LYD_OPT_DESTRUCT | LYD_OPT_NOSIBLINGS, &raw_reply);
- auto reply = guarded(raw_reply);
- raw_reply = nullptr;
+ msgtype = nc_recv_reply(session->session_internal(), rpc.get(), msgid, 20000, &envp, &raw_reply);
+ auto replyInfo = libyang::wrapRawNode(envp);
switch (msgtype) {
case NC_MSG_ERROR:
@@ -120,76 +107,75 @@
case NC_MSG_NOTIF:
continue;
default:
- return reply;
+ if (!raw_reply) { // <ok> reply, or empty data node, or error
+ std::string msg;
+ for (const auto& child : replyInfo.child()->siblings()) {
+ if (child.asOpaque().name().name == "rpc-error") {
+ for (const auto& error : child.childrenDfs()) {
+ if (error.asOpaque().name().name == "error-message") {
+ msg += "Error: ";
+ msg += error.asOpaque().value();
+ }
+
+ if (error.asOpaque().name().name == "error-path") {
+ msg += "Path: ";
+ msg += error.asOpaque().value();
+ }
+
+ if (error.asOpaque().name().name == "error-type") {
+ msg += "Type: ";
+ msg += error.asOpaque().value();
+ }
+
+ if (error.asOpaque().name().name == "error-tag") {
+ msg += "Tag: ";
+ msg += error.asOpaque().value();
+ }
+
+ if (error.asOpaque().name().name == "error-app-tag") {
+ msg += "App-tag: ";
+ msg += error.asOpaque().value();
+ }
+ }
+
+ msg += "\n";
+ }
+ }
+
+ if (!msg.empty()) {
+ throw client::ReportedError{msg};
+ }
+
+ return std::nullopt;
+ }
+ auto wrapped = libyang::wrapRawNode(raw_reply);
+
+ // If we have a dataIdentifier, then we'll need to look for it.
+ // Some operations don't have that, and then the result data are just the wrapped node.
+ if (!dataIdentifier) {
+ return wrapped;
+ }
+
+ auto anydataValue = wrapped.findPath(dataIdentifier, libyang::OutputNodes::Yes)->asAny().releaseValue();
+
+ // If there's no anydata value, then that means we get empty (but valid) data.
+ if (!anydataValue) {
+ return std::nullopt;
+ }
+
+ return std::get<libyang::DataNode>(*anydataValue);
}
}
__builtin_unreachable();
}
-client::ReportedError make_error(unique_ptr_for<struct nc_reply>&& reply)
+void do_rpc_ok(client::Session* session, managed_rpc&& rpc)
{
- if (reply->type != NC_RPL_ERROR) {
- throw std::logic_error{"Cannot extract an error from a non-error server reply"};
- }
-
- auto errorReply = guarded(reinterpret_cast<struct nc_reply_error*>(reply.release()));
-
- // TODO: capture the error details, not just that human-readable string
- std::ostringstream ss;
- ss << "Server error:" << std::endl;
- for (uint32_t i = 0; i < errorReply->count; ++i) {
- const auto e = errorReply->err[i];
- ss << " #" << i << ": " << e.message;
- if (e.path) {
- ss << " (XPath " << e.path << ")";
- }
- ss << std::endl;
- }
- return client::ReportedError{ss.str()};
-}
-
-std::optional<unique_ptr_for<struct nc_reply_data>> do_rpc_data_or_ok(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
- auto x = do_rpc(session, std::move(rpc));
-
- switch (x->type) {
- case NC_RPL_DATA:
- return guarded(reinterpret_cast<struct nc_reply_data*>(x.release()));
- case NC_RPL_OK:
- return std::nullopt;
- case NC_RPL_ERROR:
- throw make_error(std::move(x));
- default:
- throw std::runtime_error{"Unhandled reply type"};
- }
-}
-
-unique_ptr_for<struct nc_reply_data> do_rpc_data(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
- auto x = do_rpc_data_or_ok(session, std::move(rpc));
- if (!x) {
- throw std::runtime_error{"Received OK instead of a data reply"};
- }
- return std::move(*x);
-}
-
-void do_rpc_ok(client::Session* session, unique_ptr_for<struct nc_rpc>&& rpc)
-{
- auto x = do_rpc_data_or_ok(session, std::move(rpc));
+ auto x = do_rpc(session, std::move(rpc), nullptr);
if (x) {
throw std::runtime_error{"Unexpected DATA reply"};
}
}
-
-std::shared_ptr<libyang::Data_Node> do_get(client::Session* session, unique_ptr_for<nc_rpc> rpc)
-{
- auto reply = impl::do_rpc_data(session, std::move(rpc));
- auto dataNode = libyang::create_new_Data_Node(reply->data);
- // TODO: can we do without copying?
- // If we just default-construct a new node (or use the create_new_Data_Node) and then set reply->data to nullptr,
- // there are mem leaks and even libnetconf2 complains loudly.
- return dataNode ? dataNode->dup_withsiblings(1) : nullptr;
-}
}
namespace client {
@@ -210,9 +196,9 @@
return m_session;
}
-libyang::S_Context Session::libyangContext()
+libyang::Context Session::libyangContext()
{
- return std::make_shared<libyang::Context>(nc_session_get_ctx(m_session), nullptr);
+ return libyang::createUnmanagedContext(const_cast<ly_ctx*>(nc_session_get_ctx(m_session)), nullptr);
}
Session::Session(struct nc_session* session)
@@ -298,13 +284,13 @@
return res;
}
-std::shared_ptr<libyang::Data_Node> Session::get(const std::optional<std::string>& filter)
+std::optional<libyang::DataNode> Session::get(const std::optional<std::string>& filter)
{
auto rpc = impl::guarded(nc_rpc_get(filter ? filter->c_str() : nullptr, NC_WD_ALL, NC_PARAMTYPE_CONST));
if (!rpc) {
throw std::runtime_error("Cannot create get RPC");
}
- return impl::do_get(this, std::move(rpc));
+ return impl::do_rpc(this, std::move(rpc), impl::get_path);
}
const char* datastoreToString(NmdaDatastore datastore)
@@ -322,13 +308,13 @@
__builtin_unreachable();
}
-std::shared_ptr<libyang::Data_Node> Session::getData(const NmdaDatastore datastore, const std::optional<std::string>& filter)
+std::optional<libyang::DataNode> Session::getData(const NmdaDatastore datastore, const std::optional<std::string>& filter)
{
auto rpc = impl::guarded(nc_rpc_getdata(datastoreToString(datastore), filter ? filter->c_str() : nullptr, nullptr, nullptr, 0, 0, 0, 0, NC_WD_ALL, NC_PARAMTYPE_CONST));
if (!rpc) {
throw std::runtime_error("Cannot create get RPC");
}
- return impl::do_get(this, std::move(rpc));
+ return impl::do_rpc(this, std::move(rpc), impl::getData_path);
}
void Session::editData(const NmdaDatastore datastore, const std::string& data)
@@ -340,34 +326,6 @@
return impl::do_rpc_ok(this, std::move(rpc));
}
-std::string Session::getSchema(const std::string_view identifier, const std::optional<std::string_view> version)
-{
- auto rpc = impl::guarded(nc_rpc_getschema(identifier.data(), version ? version.value().data() : nullptr, nullptr, NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create get-schema RPC");
- }
- auto reply = impl::do_rpc_data(this, std::move(rpc));
-
- auto node = libyang::create_new_Data_Node(reply->data)->dup_withsiblings(1);
- auto set = node->find_path("data");
- for (auto node : set->data()) {
- if (node->schema()->nodetype() == LYS_ANYXML) {
- libyang::Data_Node_Anydata anydata(node);
- return anydata.value().str;
- }
- }
- throw std::runtime_error("Got a reply to get-schema, but it didn't contain the required schema");
-}
-
-std::shared_ptr<libyang::Data_Node> Session::getConfig(const NC_DATASTORE datastore, const std::optional<const std::string> filter)
-{
- auto rpc = impl::guarded(nc_rpc_getconfig(datastore, filter ? filter->c_str() : nullptr, NC_WD_ALL, NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create get-config RPC");
- }
- return impl::do_get(this, std::move(rpc));
-}
-
void Session::editConfig(const NC_DATASTORE datastore,
const NC_RPC_EDIT_DFLTOP defaultOperation,
const NC_RPC_EDIT_TESTOPT testOption,
@@ -408,19 +366,14 @@
impl::do_rpc_ok(this, std::move(rpc));
}
-std::shared_ptr<libyang::Data_Node> Session::rpc_or_action(const std::string& xmlData)
+std::optional<libyang::DataNode> Session::rpc_or_action(const std::string& xmlData)
{
auto rpc = impl::guarded(nc_rpc_act_generic_xml(xmlData.c_str(), NC_PARAMTYPE_CONST));
if (!rpc) {
throw std::runtime_error("Cannot create generic RPC");
}
- auto reply = impl::do_rpc_data_or_ok(this, std::move(rpc));
- if (reply) {
- auto dataNode = libyang::create_new_Data_Node((*reply)->data);
- return dataNode->dup_withsiblings(1);
- } else {
- return nullptr;
- }
+
+ return impl::do_rpc(this, std::move(rpc), nullptr);
}
void Session::copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination)