DatastoreAccess: add support for generic user-defined RPCs
I was lazy and in the NETCONF backend, the fillMap patch does not check
for actual string prefix match, it just optimistically trims the RPC
prefix. I think this is safe (definitely in the "won't crash" department
due to use of std::string::substr(), but also in the "won't produce
garbage" context because libyang is expected to validate everything).
In the sysrepo backend, I had to introduce some duplication into that
visitor which converts from our data types to sysrepo. It tirns out that
sysrepo::Session::set_item requires a separate S_Val, whereas in context
of handling an RPC's output, we have a Vals_Holder which, after
reallocation, becomes a Vals instance, and that one does not support
replacing the individual Val instances by "something" -- one has to call
a Val::set, and these methods are different from Val::Val constructors.
I'm explicitly testing for lists and containers because the
documentation looked a bit scary -- I understood it in a way which make
me spend extra effort to make sure that all that has to be created gets
created.
Change-Id: I717af71d69b209c444e1c5fe6d8ec2c2fcbdde8b
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index 9e5c317..ae0d12a 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -46,6 +46,7 @@
virtual void deletePresenceContainer(const std::string& path) = 0;
virtual void createListInstance(const std::string& path) = 0;
virtual void deleteListInstance(const std::string& path) = 0;
+ virtual Tree executeRpc(const std::string& path, const Tree& input) = 0;
virtual std::shared_ptr<Schema> schema() = 0;
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index fc35be6..7387231 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -18,8 +18,12 @@
// This is very similar to the fillMap lambda in SysrepoAccess, however,
// Sysrepo returns a weird array-like structure, while libnetconf
// returns libyang::Data_Node
-void fillMap(DatastoreAccess::Tree& res, const std::vector<std::shared_ptr<libyang::Data_Node>> items)
+void fillMap(DatastoreAccess::Tree& res, const std::vector<std::shared_ptr<libyang::Data_Node>> items, std::optional<std::string> ignoredXPathPrefix = std::nullopt)
{
+ auto stripXPathPrefix = [&ignoredXPathPrefix] (auto path) {
+ return ignoredXPathPrefix ? path.substr(ignoredXPathPrefix->size()) : path;
+ };
+
for (const auto& it : items) {
if (!it)
continue;
@@ -28,15 +32,15 @@
// The fact that the container is included in the data tree
// means that it is present and I don't need to check any
// value.
- res.emplace(it->path(), special_{SpecialValue::PresenceContainer});
+ res.emplace(stripXPathPrefix(it->path()), special_{SpecialValue::PresenceContainer});
}
}
if (it->schema()->nodetype() == LYS_LIST) {
- res.emplace(it->path(), special_{SpecialValue::List});
+ res.emplace(stripXPathPrefix(it->path()), special_{SpecialValue::List});
}
if (it->schema()->nodetype() == LYS_LEAF) {
libyang::Data_Node_Leaf_List leaf(it);
- res.emplace(leaf.path(), leafValueFromValue(leaf.value(), leaf.leaf_type()->base()));
+ res.emplace(stripXPathPrefix(it->path()), leafValueFromValue(leaf.value(), leaf.leaf_type()->base()));
}
}
}
@@ -141,6 +145,25 @@
m_session->discard();
}
+DatastoreAccess::Tree NetconfAccess::executeRpc(const std::string& path, const Tree& input)
+{
+ auto root = m_schema->dataNodeFromPath(path);
+ for (const auto& [k, v] : input) {
+ auto node = m_schema->dataNodeFromPath(joinPaths(path, k), leafDataToString(v));
+ root->merge(node, 0);
+ }
+ auto data = root->print_mem(LYD_XML, 0);
+
+ Tree res;
+ auto output = m_session->rpc(data);
+ if (output) {
+ for (auto it : output->tree_for()) {
+ fillMap(res, it->tree_dfs(), joinPaths(path, "/"));
+ }
+ }
+ return res;
+}
+
std::string NetconfAccess::fetchSchema(const std::string_view module, const
std::optional<std::string_view> revision, const
std::optional<std::string_view> submodule, const
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index a5e0485..f43976a 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -41,6 +41,7 @@
void deleteListInstance(const std::string& path) override;
void commitChanges() override;
void discardChanges() override;
+ Tree executeRpc(const std::string& path, const Tree& input) override;
std::shared_ptr<Schema> schema() override;
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 499597f..cf19407 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -8,6 +8,7 @@
#include <sysrepo-cpp/Session.hpp>
#include "sysrepo_access.hpp"
+#include "utils.hpp"
#include "yang_schema.hpp"
leaf_data_ leafValueFromVal(const sysrepo::S_Val& value)
@@ -83,6 +84,47 @@
}
};
+struct updateSrValFromValue : boost::static_visitor<void> {
+ std::string xpath;
+ sysrepo::S_Val v;
+ updateSrValFromValue(const std::string& xpath, sysrepo::S_Val v)
+ : xpath(xpath)
+ , v(v)
+ {
+ }
+
+ void operator()(const enum_& value) const
+ {
+ v->set(xpath.c_str(), value.m_value.c_str(), SR_ENUM_T);
+ }
+
+ void operator()(const binary_& value) const
+ {
+ v->set(xpath.c_str(), value.m_value.c_str(), SR_BINARY_T);
+ }
+
+ void operator()(const identityRef_& value) const
+ {
+ v->set(xpath.c_str(), (value.m_prefix.value().m_name + ":" + value.m_value).c_str(), SR_IDENTITYREF_T);
+ }
+
+ void operator()(const special_& value) const
+ {
+ throw std::runtime_error("Tried constructing S_Val from a " + specialValueToString(value));
+ }
+
+ void operator()(const std::string& value) const
+ {
+ v->set(xpath.c_str(), value.c_str(), SR_STRING_T);
+ }
+
+ template <typename T>
+ void operator()(const T value) const
+ {
+ v->set(xpath.c_str(), value);
+ }
+};
+
SysrepoAccess::~SysrepoAccess() = default;
SysrepoAccess::SysrepoAccess(const std::string& appname)
@@ -203,6 +245,25 @@
}
}
+DatastoreAccess::Tree SysrepoAccess::executeRpc(const std::string &path, const Tree &input)
+{
+ auto srInput = std::make_shared<sysrepo::Vals>(input.size());
+ {
+ size_t i = 0;
+ for (const auto& [k, v] : input) {
+ boost::apply_visitor(updateSrValFromValue(joinPaths(path, k), srInput->val(i)), v);
+ ++i;
+ }
+ }
+ auto output = m_session->rpc_send(path.c_str(), srInput);
+ Tree res;
+ for (size_t i = 0; i < output->val_cnt(); ++i) {
+ const auto& v = output->val(i);
+ res.emplace(std::string(v->xpath()).substr(joinPaths(path, "/").size()), leafValueFromVal(v));
+ }
+ return res;
+}
+
std::string SysrepoAccess::fetchSchema(const char* module, const char* revision, const char* submodule)
{
std::string schema;
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index ff26e7a..c2ce909 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -34,6 +34,7 @@
void deletePresenceContainer(const std::string& path) override;
void createListInstance(const std::string& path) override;
void deleteListInstance(const std::string& path) override;
+ Tree executeRpc(const std::string& path, const Tree& input) override;
std::shared_ptr<Schema> schema() override;