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>
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index fb44018..2fac222 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -18,14 +18,14 @@
using OnInvalidSchemaPathMove = sysrepo::sysrepo_exception;
using OnInvalidRpcPath = sysrepo::sysrepo_exception;
using OnKeyNotFound = void;
-using OnRPC = void;
+using OnExec = void;
#elif defined(netconf_BACKEND)
using OnInvalidSchemaPathCreate = std::runtime_error;
using OnInvalidSchemaPathDelete = std::runtime_error;
using OnInvalidSchemaPathMove = std::runtime_error;
using OnInvalidRpcPath = std::runtime_error;
using OnKeyNotFound = std::runtime_error;
-using OnRPC = void;
+using OnExec = void;
#include "netconf_access.hpp"
#include "netopeer_vars.hpp"
#elif defined(yang_BACKEND)
@@ -37,7 +37,7 @@
using OnInvalidSchemaPathMove = DatastoreException;
using OnInvalidRpcPath = DatastoreException;
using OnKeyNotFound = DatastoreException;
-using OnRPC = std::logic_error;
+using OnExec = std::logic_error;
#else
#error "Unknown backend"
#endif
@@ -830,6 +830,17 @@
}
class RpcCb: public sysrepo::Callback {
+ int action(const char *xpath, [[maybe_unused]] const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void* priv) override
+ {
+ auto schema = reinterpret_cast<YangSchema*>(priv);
+ if (schema->dataPathToSchemaPath(xpath) == "/example-schema:ports/shutdown") {
+ auto buf = output->allocate(1);
+ buf->val(0)->set(joinPaths(xpath, "success").c_str(), true);
+ return SR_ERR_OK;
+ }
+ throw std::runtime_error("unrecognized RPC");
+ }
+
int rpc(const char *xpath, const ::sysrepo::S_Vals input, ::sysrepo::S_Vals_Holder output, void *) override
{
const auto nukes = "/example-schema:launch-nukes"s;
@@ -876,19 +887,8 @@
}
};
-TEST_CASE("rpc") {
+TEST_CASE("rpc/action") {
trompeloeil::sequence seq1;
- auto srConn = std::make_shared<sysrepo::Connection>("netconf-cli-test-rpc");
- auto srSession = std::make_shared<sysrepo::Session>(srConn);
- auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSession);
- auto cb = std::make_shared<RpcCb>();
- sysrepo::Logs{}.set_stderr(SR_LL_INF);
- auto doNothingCb = std::make_shared<sysrepo::Callback>();
- srSubscription->module_change_subscribe("example-schema", doNothingCb, nullptr, SR_SUBSCR_CTX_REUSE);
- // careful here, sysrepo insists on module_change CBs being registered before RPC CBs, otherwise there's a memleak
- srSubscription->rpc_subscribe("/example-schema:noop", cb, nullptr, SR_SUBSCR_CTX_REUSE);
- srSubscription->rpc_subscribe("/example-schema:launch-nukes", cb, nullptr, SR_SUBSCR_CTX_REUSE);
- srSubscription->rpc_subscribe("/example-schema:fire", cb, nullptr, SR_SUBSCR_CTX_REUSE);
#ifdef sysrepo_BACKEND
auto datastore = std::make_shared<SysrepoAccess>("netconf-cli-test", Datastore::Running);
@@ -902,88 +902,124 @@
#error "Unknown backend"
#endif
- auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>& datastore) {
- return std::make_shared<YangAccess>(std::static_pointer_cast<YangSchema>(datastore->schema()));
- };
+ auto srConn = std::make_shared<sysrepo::Connection>("netconf-cli-test-rpc");
+ auto srSession = std::make_shared<sysrepo::Session>(srConn);
+ auto srSubscription = std::make_shared<sysrepo::Subscribe>(srSession);
+ auto cb = std::make_shared<RpcCb>();
+ sysrepo::Logs{}.set_stderr(SR_LL_INF);
+ auto doNothingCb = std::make_shared<sysrepo::Callback>();
+ srSubscription->module_change_subscribe("example-schema", doNothingCb, nullptr, SR_SUBSCR_CTX_REUSE);
+ // careful here, sysrepo insists on module_change CBs being registered before RPC CBs, otherwise there's a memleak
+ srSubscription->rpc_subscribe("/example-schema:noop", cb, nullptr, SR_SUBSCR_CTX_REUSE);
+ srSubscription->rpc_subscribe("/example-schema:launch-nukes", cb, nullptr, SR_SUBSCR_CTX_REUSE);
+ srSubscription->rpc_subscribe("/example-schema:fire", cb, nullptr, SR_SUBSCR_CTX_REUSE);
+ srSubscription->action_subscribe("/example-schema:ports/shutdown", cb, datastore->schema().get(), SR_SUBSCR_CTX_REUSE);
- ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
-
- // ProxyDatastore cannot easily read DatastoreAccess::Tree, so we need to set the input via create/setLeaf/etc.
- SECTION("valid")
+ SECTION("rpc")
{
- std::string rpc;
- DatastoreAccess::Tree input, output;
+ auto createTemporaryDatastore = [](const std::shared_ptr<DatastoreAccess>& datastore) {
+ return std::make_shared<YangAccess>(std::static_pointer_cast<YangSchema>(datastore->schema()));
+ };
+ ProxyDatastore proxyDatastore(datastore, createTemporaryDatastore);
- SECTION("noop") {
- rpc = "/example-schema:noop";
- proxyDatastore.initiateRpc(rpc);
+ // ProxyDatastore cannot easily read DatastoreAccess::Tree, so we need to set the input via create/setLeaf/etc.
+ SECTION("valid")
+ {
+ std::string rpc;
+ DatastoreAccess::Tree input, output;
+
+ SECTION("noop") {
+ rpc = "/example-schema:noop";
+ proxyDatastore.initiateRpc(rpc);
+ }
+
+ SECTION("small nuke") {
+ rpc = "/example-schema:launch-nukes";
+ input = {
+ {"description", "dummy"s},
+ {"payload/kilotons", uint64_t{333'666}},
+ };
+ proxyDatastore.initiateRpc(rpc);
+ proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{333'666});
+ // no data are returned
+ }
+
+ SECTION("small nuke") {
+ rpc = "/example-schema:launch-nukes";
+ input = {
+ {"description", "dummy"s},
+ {"payload/kilotons", uint64_t{4}},
+ };
+ proxyDatastore.initiateRpc(rpc);
+ proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{4});
+
+ output = {
+ {"blast-radius", uint32_t{33'666}},
+ {"actual-yield", uint64_t{5}},
+ };
+ }
+
+ SECTION("with lists") {
+ rpc = "/example-schema:launch-nukes";
+ input = {
+ {"payload/kilotons", uint64_t{6}},
+ {"cities/targets[city='Prague']/city", "Prague"s},
+ };
+ proxyDatastore.initiateRpc(rpc);
+ proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{6});
+ proxyDatastore.createItem("/example-schema:launch-nukes/example-schema:cities/example-schema:targets[city='Prague']");
+ output = {
+ {"blast-radius", uint32_t{33'666}},
+ {"actual-yield", uint64_t{7}},
+ {"damaged-places", special_{SpecialValue::PresenceContainer}},
+ {"damaged-places/targets[city='London']", special_{SpecialValue::List}},
+ {"damaged-places/targets[city='London']/city", "London"s},
+ {"damaged-places/targets[city='Berlin']", special_{SpecialValue::List}},
+ {"damaged-places/targets[city='Berlin']/city", "Berlin"s},
+ };
+ }
+
+ SECTION("with leafref") {
+ datastore->createItem("/example-schema:person[name='Colton']");
+ datastore->commitChanges();
+
+ rpc = "/example-schema:fire";
+ input = {
+ {"whom", "Colton"s}
+ };
+ proxyDatastore.initiateRpc(rpc);
+ proxyDatastore.setLeaf("/example-schema:fire/example-schema:whom", "Colton"s);
+ }
+
+ catching<OnExec>([&] {REQUIRE(datastore->executeRpc(rpc, input) == output);});
+ catching<OnExec>([&] {REQUIRE(proxyDatastore.executeRpc() == output);});
}
- SECTION("small nuke") {
- rpc = "/example-schema:launch-nukes";
- input = {
- {"description", "dummy"s},
- {"payload/kilotons", uint64_t{333'666}},
- };
- proxyDatastore.initiateRpc(rpc);
- proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{333'666});
- // no data are returned
+ SECTION("non-existing RPC")
+ {
+ catching<OnInvalidRpcPath>([&] {datastore->executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{});});
}
-
- SECTION("small nuke") {
- rpc = "/example-schema:launch-nukes";
- input = {
- {"description", "dummy"s},
- {"payload/kilotons", uint64_t{4}},
- };
- proxyDatastore.initiateRpc(rpc);
- proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{4});
-
- output = {
- {"blast-radius", uint32_t{33'666}},
- {"actual-yield", uint64_t{5}},
- };
- }
-
- SECTION("with lists") {
- rpc = "/example-schema:launch-nukes";
- input = {
- {"payload/kilotons", uint64_t{6}},
- {"cities/targets[city='Prague']/city", "Prague"s},
- };
- proxyDatastore.initiateRpc(rpc);
- proxyDatastore.setLeaf("/example-schema:launch-nukes/example-schema:payload/example-schema:kilotons", uint64_t{6});
- proxyDatastore.createItem("/example-schema:launch-nukes/example-schema:cities/example-schema:targets[city='Prague']");
- output = {
- {"blast-radius", uint32_t{33'666}},
- {"actual-yield", uint64_t{7}},
- {"damaged-places", special_{SpecialValue::PresenceContainer}},
- {"damaged-places/targets[city='London']", special_{SpecialValue::List}},
- {"damaged-places/targets[city='London']/city", "London"s},
- {"damaged-places/targets[city='Berlin']", special_{SpecialValue::List}},
- {"damaged-places/targets[city='Berlin']/city", "Berlin"s},
- };
- }
-
- SECTION("with leafref") {
- datastore->createItem("/example-schema:person[name='Colton']");
- datastore->commitChanges();
-
- rpc = "/example-schema:fire";
- input = {
- {"whom", "Colton"s}
- };
- proxyDatastore.initiateRpc(rpc);
- proxyDatastore.setLeaf("/example-schema:fire/example-schema:whom", "Colton"s);
- }
-
- catching<OnRPC>([&] {REQUIRE(datastore->executeRpc(rpc, input) == output);});
- catching<OnRPC>([&] {REQUIRE(proxyDatastore.executeRpc() == output);});
}
- SECTION("non-existing RPC")
+ SECTION("action")
{
- catching<OnInvalidRpcPath>([&] {datastore->executeRpc("/example-schema:non-existing", DatastoreAccess::Tree{});});
+ std::string path;
+ DatastoreAccess::Tree input, output;
+
+ output = {
+#ifdef netconf_BACKEND
+ {"/example-schema:ports[name='A']", special_{SpecialValue::List}},
+ {"/example-schema:ports[name='A']/name", enum_{"A"}},
+#endif
+ {"success", true}
+ };
+ datastore->createItem("/example-schema:ports[name='A']");
+ datastore->commitChanges();
+ SECTION("shutdown") {
+ path = "/example-schema:ports[name='A']/shutdown";
+ }
+
+ catching<OnExec>([&] {REQUIRE(datastore->executeAction(path, input) == output);});
}
waitForCompletionAndBitMore(seq1);
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
index e1d365b..b8ec1b0 100644
--- a/tests/datastoreaccess_mock.hpp
+++ b/tests/datastoreaccess_mock.hpp
@@ -25,6 +25,7 @@
IMPLEMENT_MOCK1(deleteItem);
IMPLEMENT_MOCK2(moveItem);
IMPLEMENT_MOCK2(executeRpc);
+ IMPLEMENT_MOCK2(executeAction);
// Can't use IMPLEMENT_MOCK for private methods - IMPLEMENT_MOCK needs full visibility of the method
MAKE_MOCK1(listInstances, std::vector<ListInstance>(const std::string&), override);
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index 04ae22d..da45afd 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -1,4 +1,5 @@
module example-schema {
+ yang-version 1.1;
prefix aha;
namespace "http://example.com";
@@ -184,6 +185,15 @@
enum E;
}
}
+
+ action shutdown {
+ output {
+ leaf success {
+ mandatory true;
+ type boolean;
+ }
+ }
+ }
}
list org {
diff --git a/tests/pretty_printers.hpp b/tests/pretty_printers.hpp
index 65e28a6..6864443 100644
--- a/tests/pretty_printers.hpp
+++ b/tests/pretty_printers.hpp
@@ -42,7 +42,7 @@
{
s << "DatastoreAccess::Tree {\n";
for (const auto& [xpath, value] : tree) {
- s << " {" << xpath << ", " << leafDataToString(value) << "}\n";
+ s << " {" << xpath << ", " << leafDataToString(value) << "},\n";
}
s << "}\n";
return s;
diff --git a/tests/yang.cpp b/tests/yang.cpp
index cee9e9a..f226778 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -334,6 +334,13 @@
enum eth2;
}
}
+ action shutdown {
+ output {
+ leaf success {
+ type boolean;
+ }
+ }
+ }
}
feature weirdPortNames;
@@ -904,6 +911,10 @@
{boost::none, "/example-schema:portMapping/port"},
{boost::none, "/example-schema:portSettings"},
{boost::none, "/example-schema:portSettings/port"},
+ {boost::none, "/example-schema:portSettings/shutdown"},
+ {boost::none, "/example-schema:portSettings/shutdown/input"},
+ {boost::none, "/example-schema:portSettings/shutdown/output"},
+ {boost::none, "/example-schema:portSettings/shutdown/output/success"},
{boost::none, "/example-schema:systemStats"},
{boost::none, "/example-schema:systemStats/upTime"},
{boost::none, "/example-schema:subLeaf"},
@@ -1099,6 +1110,12 @@
REQUIRE(ys.leafTypeName("/example-schema:leafEnumTypedefRestricted") == "enumTypedef");
REQUIRE(ys.leafTypeName("/example-schema:leafInt32") == std::nullopt);
}
+
+ SECTION("dataPathToSchemaPath")
+ {
+ REQUIRE(ys.dataPathToSchemaPath("/example-schema:portSettings[port='eth0']") == "/example-schema:portSettings");
+ REQUIRE(ys.dataPathToSchemaPath("/example-schema:portSettings[port='eth0']/shutdown") == "/example-schema:portSettings/shutdown");
+ }
}
SECTION("negative")