Add datastore support for YANG actions

Change-Id: I15b96f70ce89b7bbe3ac0fefb7b018374eeabd84
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index 8ff7658..63df0bb 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -50,6 +50,7 @@
     virtual void deleteItem(const std::string& path) = 0;
     virtual void moveItem(const std::string& path, std::variant<yang::move::Absolute, yang::move::Relative> move) = 0;
     virtual Tree executeRpc(const std::string& path, const Tree& input) = 0;
+    virtual Tree executeAction(const std::string& path, const Tree& input) = 0;
 
     virtual std::shared_ptr<Schema> schema() = 0;
 
diff --git a/src/libyang_utils.cpp b/src/libyang_utils.cpp
index 756f88c..456fa62 100644
--- a/src/libyang_utils.cpp
+++ b/src/libyang_utils.cpp
@@ -55,7 +55,7 @@
 void impl_lyNodesToTree(DatastoreAccess::Tree& res, const std::vector<std::shared_ptr<libyang::Data_Node>> items, std::optional<std::string> ignoredXPathPrefix)
 {
     auto stripXPathPrefix = [&ignoredXPathPrefix] (auto path) {
-        return ignoredXPathPrefix ? path.substr(ignoredXPathPrefix->size()) : path;
+        return ignoredXPathPrefix && path.find(*ignoredXPathPrefix) != std::string::npos ? path.substr(ignoredXPathPrefix->size()) : path;
     };
 
     for (const auto& it : items) {
diff --git a/src/netconf-client.cpp b/src/netconf-client.cpp
index 63fbb20..1ff83de 100644
--- a/src/netconf-client.cpp
+++ b/src/netconf-client.cpp
@@ -345,7 +345,7 @@
     impl::do_rpc_ok(this, std::move(rpc));
 }
 
-std::shared_ptr<libyang::Data_Node> Session::rpc(const std::string& xmlData)
+std::shared_ptr<libyang::Data_Node> 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) {
diff --git a/src/netconf-client.hpp b/src/netconf-client.hpp
index 5949704..db004e3 100644
--- a/src/netconf-client.hpp
+++ b/src/netconf-client.hpp
@@ -44,7 +44,7 @@
                     const NC_RPC_EDIT_ERROPT errorOption,
                     const std::string& data);
     void copyConfigFromString(const NC_DATASTORE target, const std::string& data);
-    std::shared_ptr<libyang::Data_Node> rpc(const std::string& xmlData);
+    std::shared_ptr<libyang::Data_Node> rpc_or_action(const std::string& xmlData);
     void copyConfig(const NC_DATASTORE source, const NC_DATASTORE destination);
     void commit();
     void discard();
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 689b2b5..4b151d3 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -116,7 +116,7 @@
     m_session->discard();
 }
 
-DatastoreAccess::Tree NetconfAccess::executeRpc(const std::string& path, const Tree& input)
+DatastoreAccess::Tree NetconfAccess::impl_execute(const std::string& path, const Tree& input)
 {
     auto root = m_schema->dataNodeFromPath(path);
     for (const auto& [k, v] : input) {
@@ -126,13 +126,23 @@
     auto data = root->print_mem(LYD_XML, 0);
 
     Tree res;
-    auto output = m_session->rpc(data);
+    auto output = m_session->rpc_or_action(data);
     if (output) {
         lyNodesToTree(res, output->tree_for(), joinPaths(path, "/"));
     }
     return res;
 }
 
+DatastoreAccess::Tree NetconfAccess::executeRpc(const std::string& path, const Tree& input)
+{
+    return impl_execute(path, input);
+}
+
+DatastoreAccess::Tree NetconfAccess::executeAction(const std::string& path, const Tree& input)
+{
+    return impl_execute(path, input);
+}
+
 NC_DATASTORE toNcDatastore(Datastore datastore)
 {
     switch (datastore) {
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index 586840f..d6100a7 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -41,6 +41,7 @@
     void commitChanges() override;
     void discardChanges() override;
     Tree executeRpc(const std::string& path, const Tree& input) override;
+    Tree executeAction(const std::string& path, const Tree& input) override;
     void copyConfig(const Datastore source, const Datastore destination) override;
 
     std::shared_ptr<Schema> schema() override;
@@ -49,6 +50,7 @@
 
 private:
     std::vector<ListInstance> listInstances(const std::string& path) override;
+    DatastoreAccess::Tree impl_execute(const std::string& path, const Tree& input);
 
     std::string fetchSchema(const std::string_view module, const
             std::optional<std::string_view> revision, const
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 510c530..e792ed8 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -311,24 +311,44 @@
     }
 }
 
-DatastoreAccess::Tree SysrepoAccess::executeRpc(const std::string &path, const Tree &input)
+namespace {
+std::shared_ptr<sysrepo::Vals> toSrVals(const std::string& path, const DatastoreAccess::Tree& input)
 {
-    auto srInput = std::make_shared<sysrepo::Vals>(input.size());
+    auto res = 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);
+            boost::apply_visitor(updateSrValFromValue(joinPaths(path, k), res->val(i)), v);
             ++i;
         }
     }
-    auto output = m_session->rpc_send(path.c_str(), srInput);
-    Tree res;
+    return res;
+}
+
+DatastoreAccess::Tree toTree(const std::string& path, const std::shared_ptr<sysrepo::Vals>& output)
+{
+    DatastoreAccess::Tree res;
     for (size_t i = 0; i < output->val_cnt(); ++i) {
         const auto& v = output->val(i);
         res.emplace_back(std::string(v->xpath()).substr(joinPaths(path, "/").size()), leafValueFromVal(v));
     }
     return res;
 }
+}
+
+DatastoreAccess::Tree SysrepoAccess::executeRpc(const std::string &path, const Tree &input)
+{
+    auto srInput = toSrVals(path, input);
+    auto output = m_session->rpc_send(path.c_str(), srInput);
+    return toTree(path, output);
+}
+
+DatastoreAccess::Tree SysrepoAccess::executeAction(const std::string& path, const Tree& input)
+{
+    auto srInput = toSrVals(path, input);
+    auto output = m_session->action_send(path.c_str(), srInput);
+    return toTree(path, output);
+}
 
 void SysrepoAccess::copyConfig(const Datastore source, const Datastore destination)
 {
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index a3db4c1..55b1b67 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -34,6 +34,7 @@
     void deleteItem(const std::string& path) override;
     void moveItem(const std::string& source, std::variant<yang::move::Absolute, yang::move::Relative> move) override;
     Tree executeRpc(const std::string& path, const Tree& input) override;
+    Tree executeAction(const std::string& path, const Tree& input) override;
 
     std::shared_ptr<Schema> schema() override;
 
diff --git a/src/yang_access.cpp b/src/yang_access.cpp
index ebe39f1..8171c89 100644
--- a/src/yang_access.cpp
+++ b/src/yang_access.cpp
@@ -229,7 +229,7 @@
 {
 }
 
-DatastoreAccess::Tree YangAccess::executeRpc(const std::string& path, const Tree& input)
+[[noreturn]] void YangAccess::impl_execute(const std::string& type, 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) {
@@ -244,7 +244,17 @@
             getErrorsAndThrow();
         }
     }
-    throw std::logic_error("in-memory datastore doesn't support executing RPCs.");
+    throw std::logic_error("in-memory datastore doesn't support executing " + type + "s");
+}
+
+DatastoreAccess::Tree YangAccess::executeRpc(const std::string& path, const Tree& input)
+{
+    impl_execute("RPC", path, input);
+}
+
+DatastoreAccess::Tree YangAccess::executeAction(const std::string& path, const Tree& input)
+{
+    impl_execute("action", path, input);
 }
 
 void YangAccess::copyConfig(const Datastore source, const Datastore dest)
diff --git a/src/yang_access.hpp b/src/yang_access.hpp
index 503a077..ab948cf 100644
--- a/src/yang_access.hpp
+++ b/src/yang_access.hpp
@@ -31,6 +31,7 @@
     void commitChanges() override;
     void discardChanges() override;
     Tree executeRpc(const std::string& path, const Tree& input) override;
+    Tree executeAction(const std::string& path, const Tree& input) override;
     void copyConfig(const Datastore source, const Datastore destination) override;
 
     std::shared_ptr<Schema> schema() override;
@@ -45,6 +46,7 @@
 
 private:
     std::vector<ListInstance> listInstances(const std::string& path) override;
+    [[noreturn]] void impl_execute(const std::string& type, const std::string& path, const Tree& input);
 
     [[noreturn]] void getErrorsAndThrow() const;
     void impl_newPath(const std::string& path, const std::optional<std::string>& value = std::nullopt);
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 05195b9..681861e 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -512,3 +512,8 @@
 
     return std::nullopt;
 }
+
+std::string YangSchema::dataPathToSchemaPath(const std::string& path)
+{
+    return getSchemaNode(path)->path(LYS_PATH_FIRST_PREFIX);
+}
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index 5f3aa20..3d1dc81 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -69,6 +69,7 @@
     [[nodiscard]] std::shared_ptr<libyang::Data_Node> dataNodeFromPath(const std::string& path, const std::optional<const std::string> value = std::nullopt) const;
     std::shared_ptr<libyang::Module> getYangModule(const std::string& name);
 
+    [[nodiscard]] std::string dataPathToSchemaPath(const std::string& path);
 private:
     friend class YangAccess;
     template <typename NodeType>