Use libnetconf2-cpp
Change-Id: I45dd7f24136edc57235fbb6abe14f2d48a5388c0
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/5432
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/br2-external/+/5629
diff --git a/src/cli.cpp b/src/cli.cpp
index 24ed37e..3ed40e8 100644
--- a/src/cli.cpp
+++ b/src/cli.cpp
@@ -170,7 +170,7 @@
#elif defined(NETCONF_CLI)
auto verbose = args.at("-v").asBool();
if (verbose) {
- NetconfAccess::setNcLogLevel(NC_VERB_DEBUG);
+ NetconfAccess::setNcLogLevel(libnetconf::LogLevel::Debug);
}
SshProcess process;
diff --git a/src/netconf-client.cpp b/src/netconf-client.cpp
deleted file mode 100644
index 7171c01..0000000
--- a/src/netconf-client.cpp
+++ /dev/null
@@ -1,395 +0,0 @@
-/*
- * Copyright (C) 2019 CESNET, https://photonics.cesnet.cz/
- *
- * Written by Václav Kubernát <kubernat@cesnet.cz>
- * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
- *
-*/
-
-#include <cstring>
-#include <libyang-cpp/Context.hpp>
-#include <libyang-cpp/DataNode.hpp>
-#include <mutex>
-extern "C" {
-#include <nc_client.h>
-}
-#include <sstream>
-#include "UniqueResource.hpp"
-#include "netconf-client.hpp"
-
-namespace libnetconf {
-
-namespace impl {
-
-static client::LogCb logCallback;
-
-static void logViaCallback(NC_VERB_LEVEL level, const char* message)
-{
- logCallback(level, message);
-}
-
-
-/** @short Initialization of the libnetconf2 library client
-
-Just a safe wrapper over nc_client_init and nc_client_destroy, really.
-*/
-class ClientInit {
- ClientInit()
- {
- nc_client_init();
- }
-
- ~ClientInit()
- {
- nc_client_destroy();
- }
-
-public:
- static ClientInit& instance()
- {
- static ClientInit lib;
- return lib;
- }
-
- ClientInit(const ClientInit&) = delete;
- ClientInit(ClientInit&&) = delete;
- ClientInit& operator=(const ClientInit&) = delete;
- ClientInit& operator=(ClientInit&&) = delete;
-};
-
-static std::mutex clientOptions;
-
-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);
- auto res = (*cb)(auth_name, instruction, prompt, echo);
- return ::strdup(res.c_str());
-}
-
-auto guarded(nc_rpc* ptr)
-{
- return std::unique_ptr<nc_rpc, decltype(&nc_rpc_free)>(ptr, nc_rpc_free);
-}
-
-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;
-
- msgtype = nc_send_rpc(session->session_internal(), rpc.get(), 1000, &msgid);
- if (msgtype == NC_MSG_ERROR) {
- throw std::runtime_error{"Failed to send RPC"};
- }
- if (msgtype == NC_MSG_WOULDBLOCK) {
- throw std::runtime_error{"Timeout sending an RPC"};
- }
-
- lyd_node* raw_reply;
- lyd_node* envp;
- while (true) {
- 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:
- throw std::runtime_error{"Failed to receive an RPC reply"};
- case NC_MSG_WOULDBLOCK:
- throw std::runtime_error{"Timed out waiting for RPC reply"};
- case NC_MSG_REPLY_ERR_MSGID:
- throw std::runtime_error{"Received a wrong reply -- msgid mismatch"};
- case NC_MSG_NOTIF:
- continue;
- default:
- 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();
-}
-
-void do_rpc_ok(client::Session* session, managed_rpc&& rpc)
-{
- auto x = do_rpc(session, std::move(rpc), nullptr);
- if (x) {
- throw std::runtime_error{"Unexpected DATA reply"};
- }
-}
-}
-
-namespace client {
-
-void setLogLevel(NC_VERB_LEVEL level)
-{
- nc_verbosity(level);
-}
-
-void setLogCallback(const client::LogCb& callback)
-{
- impl::logCallback = callback;
- nc_set_print_clb(impl::logViaCallback);
-}
-
-struct nc_session* Session::session_internal()
-{
- return m_session;
-}
-
-libyang::Context Session::libyangContext()
-{
- return libyang::createUnmanagedContext(const_cast<ly_ctx*>(nc_session_get_ctx(m_session)), nullptr);
-}
-
-Session::Session(struct nc_session* session)
- : m_session(session)
-{
- impl::ClientInit::instance();
-}
-
-Session::~Session()
-{
- ::nc_session_free(m_session, nullptr);
-}
-
-std::unique_ptr<Session> Session::connectPubkey(const std::string& host, const uint16_t port, const std::string& user, const std::string& pubPath, const std::string& privPath, std::optional<libyang::Context> ctx)
-{
- impl::ClientInit::instance();
-
- {
- // FIXME: this is still horribly not enough. libnetconf *must* provide us with something better.
- std::lock_guard lk(impl::clientOptions);
- nc_client_ssh_set_username(user.c_str());
- nc_client_ssh_set_auth_pref(NC_SSH_AUTH_PUBLICKEY, 5);
- nc_client_ssh_add_keypair(pubPath.c_str(), privPath.c_str());
- }
- auto session = std::make_unique<Session>(nc_connect_ssh(host.c_str(), port, ctx ? libyang::retrieveContext(*ctx) : nullptr));
- if (!session->m_session) {
- throw std::runtime_error{"nc_connect_ssh failed"};
- }
- return session;
-}
-
-std::unique_ptr<Session> Session::connectKbdInteractive(const std::string& host, const uint16_t port, const std::string& user, const KbdInteractiveCb& callback, std::optional<libyang::Context> ctx)
-{
- impl::ClientInit::instance();
-
- std::lock_guard lk(impl::clientOptions);
- auto cb_guard = make_unique_resource([user, &callback]() {
- nc_client_ssh_set_username(user.c_str());
- nc_client_ssh_set_auth_pref(NC_SSH_AUTH_INTERACTIVE, 5);
- nc_client_ssh_set_auth_interactive_clb(impl::ssh_auth_interactive_cb, static_cast<void *>(&const_cast<KbdInteractiveCb&>(callback)));
- }, []() {
- nc_client_ssh_set_auth_interactive_clb(nullptr, nullptr);
- nc_client_ssh_set_username(nullptr);
- });
-
- auto session = std::make_unique<Session>(nc_connect_ssh(host.c_str(), port, ctx ? libyang::retrieveContext(*ctx) : nullptr));
- if (!session->m_session) {
- throw std::runtime_error{"nc_connect_ssh failed"};
- }
- return session;
-}
-
-std::unique_ptr<Session> Session::connectFd(const int source, const int sink, std::optional<libyang::Context> ctx)
-{
- impl::ClientInit::instance();
-
- auto session = std::make_unique<Session>(nc_connect_inout(source, sink, ctx ? libyang::retrieveContext(*ctx) : nullptr));
- if (!session->m_session) {
- throw std::runtime_error{"nc_connect_inout failed"};
- }
- return session;
-}
-
-std::unique_ptr<Session> Session::connectSocket(const std::string& path, std::optional<libyang::Context> ctx)
-{
- impl::ClientInit::instance();
-
- auto session = std::make_unique<Session>(nc_connect_unix(path.c_str(), ctx ? libyang::retrieveContext(*ctx) : nullptr));
- if (!session->m_session) {
- throw std::runtime_error{"nc_connect_unix failed"};
- }
- return session;
-}
-
-std::vector<std::string_view> Session::capabilities() const
-{
- std::vector<std::string_view> res;
- auto caps = nc_session_get_cpblts(m_session);
- while (*caps) {
- res.emplace_back(*caps);
- ++caps;
- }
- return res;
-}
-
-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_rpc(this, std::move(rpc), impl::get_path);
-}
-
-const char* datastoreToString(NmdaDatastore datastore)
-{
- switch (datastore) {
- case NmdaDatastore::Startup:
- return "ietf-datastores:startup";
- case NmdaDatastore::Running:
- return "ietf-datastores:running";
- case NmdaDatastore::Candidate:
- return "ietf-datastores:candidate";
- case NmdaDatastore::Operational:
- return "ietf-datastores:operational";
- }
- __builtin_unreachable();
-}
-
-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_rpc(this, std::move(rpc), impl::getData_path);
-}
-
-void Session::editData(const NmdaDatastore datastore, const std::string& data)
-{
- auto rpc = impl::guarded(nc_rpc_editdata(datastoreToString(datastore), NC_RPC_EDIT_DFLTOP_MERGE, data.c_str(), NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create get RPC");
- }
- return impl::do_rpc_ok(this, std::move(rpc));
-}
-
-void Session::editConfig(const NC_DATASTORE datastore,
- const NC_RPC_EDIT_DFLTOP defaultOperation,
- const NC_RPC_EDIT_TESTOPT testOption,
- const NC_RPC_EDIT_ERROPT errorOption,
- const std::string& data)
-{
- auto rpc = impl::guarded(nc_rpc_edit(datastore, defaultOperation, testOption, errorOption, data.c_str(), NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create edit-config RPC");
- }
- impl::do_rpc_ok(this, std::move(rpc));
-}
-
-void Session::copyConfigFromString(const NC_DATASTORE target, const std::string& data)
-{
- auto rpc = impl::guarded(nc_rpc_copy(target, nullptr, target /* yeah, cannot be 0... */, data.c_str(), NC_WD_UNKNOWN, NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create copy-config RPC");
- }
- impl::do_rpc_ok(this, std::move(rpc));
-}
-
-void Session::commit()
-{
- auto rpc = impl::guarded(nc_rpc_commit(0, /* "Optional confirm timeout" how do you optional an uint32_t? */ 0, nullptr, nullptr, NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create commit RPC");
- }
- impl::do_rpc_ok(this, std::move(rpc));
-}
-
-void Session::discard()
-{
- auto rpc = impl::guarded(nc_rpc_discard());
- if (!rpc) {
- throw std::runtime_error("Cannot create discard RPC");
- }
- impl::do_rpc_ok(this, std::move(rpc));
-}
-
-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");
- }
-
- return impl::do_rpc(this, std::move(rpc), nullptr);
-}
-
-void Session::copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination)
-{
- auto rpc = impl::guarded(nc_rpc_copy(destination, nullptr, source, nullptr, NC_WD_UNKNOWN, NC_PARAMTYPE_CONST));
- if (!rpc) {
- throw std::runtime_error("Cannot create copy-config RPC");
- }
- impl::do_rpc_ok(this, std::move(rpc));
-}
-
-ReportedError::ReportedError(const std::string& what)
- : std::runtime_error(what)
-{
-}
-
-ReportedError::~ReportedError() = default;
-}
-}
diff --git a/src/netconf-client.hpp b/src/netconf-client.hpp
deleted file mode 100644
index 719a21d..0000000
--- a/src/netconf-client.hpp
+++ /dev/null
@@ -1,71 +0,0 @@
-#pragma once
-
-#include <functional>
-#include <libnetconf2/log.h>
-#include <libnetconf2/messages_client.h>
-#include <libyang-cpp/Context.hpp>
-#include <memory>
-#include <optional>
-#include <string>
-#include <string_view>
-#include <vector>
-
-struct nc_session;
-struct ly_ctx;
-
-namespace libyang {
-class Context;
-class DataNode;
-}
-
-namespace libnetconf {
-enum class NmdaDatastore {
- Startup,
- Running,
- Candidate,
- Operational
-};
-namespace client {
-
-class ReportedError : public std::runtime_error {
-public:
- ReportedError(const std::string& what);
- ~ReportedError() override;
-};
-
-using KbdInteractiveCb = std::function<std::string(const std::string&, const std::string&, const std::string&, bool)>;
-using LogCb = std::function<void(NC_VERB_LEVEL, const char*)>;
-
-void setLogLevel(NC_VERB_LEVEL level);
-void setLogCallback(const LogCb& callback);
-
-class Session {
-public:
- Session(struct nc_session* session);
- ~Session();
- static std::unique_ptr<Session> connectPubkey(const std::string& host, const uint16_t port, const std::string& user, const std::string& pubPath, const std::string& privPath, std::optional<libyang::Context> ctx = std::nullopt);
- static std::unique_ptr<Session> connectKbdInteractive(const std::string& host, const uint16_t port, const std::string& user, const KbdInteractiveCb& callback, std::optional<libyang::Context> ctx = std::nullopt);
- static std::unique_ptr<Session> connectSocket(const std::string& path, std::optional<libyang::Context> ctx = std::nullopt);
- static std::unique_ptr<Session> connectFd(const int source, const int sink, std::optional<libyang::Context> ctx = std::nullopt);
- [[nodiscard]] std::vector<std::string_view> capabilities() const;
- std::optional<libyang::DataNode> get(const std::optional<std::string>& filter = std::nullopt);
- std::optional<libyang::DataNode> getData(const NmdaDatastore datastore, const std::optional<std::string>& filter = std::nullopt);
- void editConfig(const NC_DATASTORE datastore,
- const NC_RPC_EDIT_DFLTOP defaultOperation,
- const NC_RPC_EDIT_TESTOPT testOption,
- const NC_RPC_EDIT_ERROPT errorOption,
- const std::string& data);
- void editData(const NmdaDatastore datastore, const std::string& data);
- void copyConfigFromString(const NC_DATASTORE target, const std::string& data);
- std::optional<libyang::DataNode> rpc_or_action(const std::string& xmlData);
- void copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination);
- void commit();
- void discard();
-
- libyang::Context libyangContext();
- struct nc_session* session_internal(); // FIXME: remove me
-protected:
- struct nc_session* m_session;
-};
-}
-}
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index a3d30ba..8fc3e55 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -5,8 +5,8 @@
*
*/
+#include <libnetconf2-cpp/netconf-client.hpp>
#include "libyang_utils.hpp"
-#include "netconf-client.hpp"
#include "netconf_access.hpp"
#include "utils.hpp"
#include "yang_schema.hpp"
@@ -95,7 +95,7 @@
m_serverHasNMDA = nmdaMod && nmdaMod->implemented();
}
-void NetconfAccess::setNcLogLevel(NC_VERB_LEVEL level)
+void NetconfAccess::setNcLogLevel(libnetconf::LogLevel level)
{
libnetconf::client::setLogLevel(level);
}
@@ -172,7 +172,12 @@
if (m_serverHasNMDA) {
m_session->editData(targetToDs_set(m_target), std::string{*data});
} else {
- m_session->editConfig(NC_DATASTORE_CANDIDATE, NC_RPC_EDIT_DFLTOP_MERGE, NC_RPC_EDIT_TESTOPT_TESTSET, NC_RPC_EDIT_ERROPT_STOP, std::string{*data});
+ m_session->editConfig(
+ libnetconf::Datastore::Candidate,
+ libnetconf::EditDefaultOp::Merge,
+ libnetconf::EditTestOpt::TestSet,
+ libnetconf::EditErrorOpt::Stop,
+ std::string{*data});
}
}
@@ -198,13 +203,13 @@
return rpcOutputToTree(*output);
}
-NC_DATASTORE toNcDatastore(Datastore datastore)
+libnetconf::Datastore toNcDatastore(Datastore datastore)
{
switch (datastore) {
case Datastore::Running:
- return NC_DATASTORE_RUNNING;
+ return libnetconf::Datastore::Running;
case Datastore::Startup:
- return NC_DATASTORE_STARTUP;
+ return libnetconf::Datastore::Startup;
}
__builtin_unreachable();
}
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index 1534dba..cee092f 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -7,8 +7,8 @@
#pragma once
-#include <libnetconf2/log.h>
#include <libyang-cpp/Context.hpp>
+#include <libnetconf2-cpp/Enum.hpp>
#include <string>
#include "datastore_access.hpp"
@@ -25,7 +25,7 @@
class Schema;
class YangSchema;
-using LogCb = std::function<void(NC_VERB_LEVEL, const char*)>;
+using LogCb = std::function<void(libnetconf::LogLevel, const char*)>;
class NetconfAccess : public DatastoreAccess {
public:
@@ -36,7 +36,7 @@
~NetconfAccess() override;
[[nodiscard]] Tree getItems(const std::string& path) const override;
- static void setNcLogLevel(NC_VERB_LEVEL level);
+ static void setNcLogLevel(libnetconf::LogLevel level);
static void setNcLogCallback(const LogCb& callback);
void setLeaf(const std::string& path, leaf_data_ value) override;
void createItem(const std::string& path) override;
diff --git a/src/python_netconf.cpp b/src/python_netconf.cpp
index c6e3c44..f60b38c 100644
--- a/src/python_netconf.cpp
+++ b/src/python_netconf.cpp
@@ -8,7 +8,7 @@
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
-#include "netconf-client.hpp"
+#include <libnetconf2-cpp/netconf-client.hpp>
#include "netconf_access.hpp"
using namespace std::literals;