Implement DataQuery
For now, the DataQuery class includes the listKeys method, which will be
used in the parser for tab-completion of values of keys.
The listKeys method uses similar arguments as many methods from the
Schema interface do. This is due to how parsing works: when I parse
lists I won't get the full list instance until all keys are typed.
That's why I can't get the full path to the list: it can't be a full
data path until all keys are known.
I tried many different return types of the method. The first iteration
used just a string to string map. That didn't work well, because
discarding type info meant that I needed to quote strings in THIS
method. And that didn't go well when trying to integrate this to the
parser, because I couldn't compare quoted strings with unquoted strings.
The next return type I tried - a set of string to leaf_data_ maps - was
better. It doesn't discard type info and that makes it possible to
compare in the parser. However, I didn't really make use of an ordered
container, so I changed the type to a vector.
The various implementations of this method were pretty straightforward.
They usually just retrieved all the instances from the datastore with
DatastoreAccess::getItems and then used some sort of an algorithm to
transform the instances into the desired return type. However, there was
a problem with this approach: it is difficult to get ONLY the instances,
and no other data. This means that it is unsuitable to use getItems for this, because:
a) it always works recursively
b) even if it didn't Netconf would still give me more data than I
wanted.
It is possible to create an XML filter, so that no data has to be
discarded, but it's only possible in Netconf and not sysrepo. This
difference is going to be (hopefully) removed with sysrepo2. I decided
to implement a datastore-specific method, where both sysrepo and netconf
use their own best implementation.
Change-Id: I2c978762d3a78286b4f7fb97e16118772ddeb7bc
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a86f288..513c039 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -78,7 +78,7 @@
add_library(utils STATIC
src/utils.cpp
)
-target_link_libraries(utils path Boost::boost)
+target_link_libraries(utils path ast_values Boost::boost)
add_library(schemas STATIC
src/static_schema.cpp
@@ -88,6 +88,7 @@
add_library(datastoreaccess STATIC
src/datastore_access.cpp
+ src/data_query.cpp
)
target_link_libraries(datastoreaccess PUBLIC Boost::boost)
@@ -112,7 +113,7 @@
src/yang_schema.cpp
src/libyang_utils.cpp
)
-target_link_libraries(yangschema schemas ${LIBYANG_LIBRARIES})
+target_link_libraries(yangschema schemas utils ${LIBYANG_LIBRARIES})
# Ensure that this doesn't override Boost's -isystem -- see the log for details.
target_include_directories(yangschema SYSTEM PRIVATE ${LIBYANG_INCLUDEDIR})
link_directories(${LIBYANG_LIBRARY_DIRS})
@@ -283,6 +284,7 @@
setup_datastore_tests()
datastore_test(datastore_access example-schema)
+ datastore_test(data_query example-schema)
endif()
option(WITH_PYTHON_BINDINGS "Create and install Python3 bindings for accessing datastores" OFF)
diff --git a/src/ast_values.cpp b/src/ast_values.cpp
index 8d9c40c..ae5aa6c 100644
--- a/src/ast_values.cpp
+++ b/src/ast_values.cpp
@@ -33,26 +33,51 @@
{
}
+bool module_::operator<(const module_& b) const
+{
+ return this->m_name < b.m_name;
+}
+
bool identityRef_::operator==(const identityRef_& b) const
{
return this->m_prefix == b.m_prefix && this->m_value == b.m_value;
}
+bool identityRef_::operator<(const identityRef_& b) const
+{
+ return std::tie(this->m_prefix, this->m_value) < std::tie(b.m_prefix, b.m_value);
+}
+
bool binary_::operator==(const binary_& b) const
{
return this->m_value == b.m_value;
}
+bool binary_::operator<(const binary_& b) const
+{
+ return this->m_value < b.m_value;
+}
+
bool enum_::operator==(const enum_& b) const
{
return this->m_value == b.m_value;
}
+bool enum_::operator<(const enum_& b) const
+{
+ return this->m_value < b.m_value;
+}
+
bool special_::operator==(const special_& b) const
{
return this->m_value == b.m_value;
}
+bool special_::operator<(const special_& b) const
+{
+ return this->m_value < b.m_value;
+}
+
std::string specialValueToString(const special_& value)
{
switch (value.m_value) {
diff --git a/src/ast_values.hpp b/src/ast_values.hpp
index b2f2c3a..b79ac84 100644
--- a/src/ast_values.hpp
+++ b/src/ast_values.hpp
@@ -14,6 +14,7 @@
enum_();
enum_(const std::string& value);
bool operator==(const enum_& b) const;
+ bool operator<(const enum_& b) const;
std::string m_value;
};
@@ -21,11 +22,13 @@
binary_();
binary_(const std::string& value);
bool operator==(const binary_& b) const;
+ bool operator<(const binary_& b) const;
std::string m_value;
};
struct module_ {
bool operator==(const module_& b) const;
+ bool operator<(const module_& b) const;
std::string m_name;
};
@@ -34,6 +37,7 @@
identityRef_(const std::string& module, const std::string& value);
identityRef_(const std::string& value);
bool operator==(const identityRef_& b) const;
+ bool operator<(const identityRef_& b) const;
boost::optional<module_> m_prefix;
std::string m_value;
};
@@ -46,6 +50,7 @@
struct special_ {
bool operator==(const special_& b) const;
+ bool operator<(const special_& b) const;
SpecialValue m_value;
};
diff --git a/src/data_query.cpp b/src/data_query.cpp
new file mode 100644
index 0000000..be979ff
--- /dev/null
+++ b/src/data_query.cpp
@@ -0,0 +1,18 @@
+#include "data_query.hpp"
+#include "datastore_access.hpp"
+#include "schema.hpp"
+#include "utils.hpp"
+
+
+DataQuery::DataQuery(DatastoreAccess& datastore)
+ : m_datastore(datastore)
+{
+ m_schema = m_datastore.schema();
+}
+
+std::vector<ListInstance> DataQuery::listKeys(const dataPath_& location, const ModuleNodePair& node) const
+{
+ auto listPath = joinPaths(pathToDataString(location, Prefixes::Always), fullNodeName(location, node));
+
+ return m_datastore.listInstances(listPath);
+}
diff --git a/src/data_query.hpp b/src/data_query.hpp
new file mode 100644
index 0000000..b2f4a33
--- /dev/null
+++ b/src/data_query.hpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2019 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubernat@cesnet.cz>
+ *
+*/
+
+#pragma once
+
+#include <boost/optional.hpp>
+#include <map>
+#include <memory>
+#include <set>
+#include "ast_values.hpp"
+#include "datastore_access.hpp"
+#include "schema.hpp"
+
+
+/*! \class DataQuery
+ * \brief A class used for querying data from a datastore for usage in tab-completion.
+ *
+*/
+class DataQuery {
+public:
+ DataQuery(DatastoreAccess& datastore);
+ /*! \brief Lists all possible instances of key/value pairs for a list specified by the arguments.
+ * \param location Location of the list.
+ * \param node Name (and optional module name) of the list.
+ * \return A vector of maps, which represent the instances. The map is keyed by the name of the list key. The values in the map, are values of the list keys.
+ */
+ std::vector<ListInstance> listKeys(const dataPath_& location, const ModuleNodePair& node) const;
+
+private:
+ DatastoreAccess& m_datastore;
+ std::shared_ptr<Schema> m_schema;
+};
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index ae0d12a..6686b92 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -36,6 +36,8 @@
class Schema;
+using ListInstance = std::map<std::string, leaf_data_>;
+
class DatastoreAccess {
public:
using Tree = std::map<std::string, leaf_data_>;
@@ -52,4 +54,8 @@
virtual void commitChanges() = 0;
virtual void discardChanges() = 0;
+
+private:
+ friend class DataQuery;
+ virtual std::vector<ListInstance> listInstances(const std::string& path) = 0;
};
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 7387231..91b1874 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -194,3 +194,36 @@
{
return m_schema;
}
+
+std::vector<ListInstance> NetconfAccess::listInstances(const std::string& path)
+{
+ std::vector<ListInstance> res;
+ auto list = m_schema->dataNodeFromPath(path);
+
+ // This inserts selection nodes - I only want keys not other data
+ // To get the keys, I have to call find_path here - otherwise I would get keys of a top-level node (which might not even be a list)
+ auto keys = libyang::Schema_Node_List{(*(list->find_path(path.c_str())->data().begin()))->schema()}.keys();
+ for (const auto& keyLeaf : keys) {
+ // Have to call find_path here - otherwise I'll have the list, not the leaf inside it
+ auto selectionLeaf = *(m_schema->dataNodeFromPath(keyLeaf->path())->find_path(keyLeaf->path().c_str())->data().begin());
+ auto actualList = *(list->find_path(path.c_str())->data().begin());
+ actualList->insert(selectionLeaf);
+ }
+
+ auto instances = m_session->getConfig(NC_DATASTORE_RUNNING, list->print_mem(LYD_XML, 0));
+
+
+ for (const auto& instance : instances->find_path(path.c_str())->data()) {
+ ListInstance instanceRes;
+
+ // I take the first child here, because the first element (the parent of the child()) will be the list
+ for (const auto& keyLeaf : instance->child()->tree_for()) {
+ auto leafData = libyang::Data_Node_Leaf_List{keyLeaf};
+ auto leafSchema = libyang::Schema_Node_Leaf{leafData.schema()};
+ instanceRes.insert({ leafSchema.name(), leafValueFromValue(leafData.value(), leafSchema.type()->base())});
+ }
+ res.push_back(instanceRes);
+ }
+
+ return res;
+}
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index f43976a..aa13312 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -46,6 +46,8 @@
std::shared_ptr<Schema> schema() override;
private:
+ std::vector<std::map<std::string, leaf_data_>> listInstances(const std::string& path) override;
+
std::string 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/sysrepo_access.cpp b/src/sysrepo_access.cpp
index cf19407..b681893 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -6,7 +6,10 @@
*
*/
+#include <libyang/Tree_Data.hpp>
+#include <libyang/Tree_Schema.hpp>
#include <sysrepo-cpp/Session.hpp>
+#include "libyang_utils.hpp"
#include "sysrepo_access.hpp"
#include "utils.hpp"
#include "yang_schema.hpp"
@@ -317,3 +320,54 @@
throw DatastoreException(res);
}
+
+std::vector<ListInstance> SysrepoAccess::listInstances(const std::string& path)
+{
+ std::vector<ListInstance> res;
+ auto lists = getItems(path);
+
+ decltype(lists) instances;
+ auto wantedTree = *(m_schema->dataNodeFromPath(path)->find_path(path.c_str())->data().begin());
+ std::copy_if(lists.begin(), lists.end(), std::inserter(instances, instances.end()), [this, pathToCheck=wantedTree->schema()->path()](const auto& item) {
+ // This filters out non-instances.
+ if (item.second.type() != typeid(special_) || boost::get<special_>(item.second).m_value != SpecialValue::List) {
+ return false;
+ }
+
+ // Now, getItems is recursive: it gives everything including nested lists. So I try create a tree from the instance...
+ auto instanceTree = *(m_schema->dataNodeFromPath(item.first)->find_path(item.first.c_str())->data().begin());
+ // And then check if its schema path matches the list we actually want. This filters out lists which are not the ones I requested.
+ return instanceTree->schema()->path() == pathToCheck;
+ });
+
+ // If there are no instances, then just return
+ if (instances.empty()) {
+ return res;
+ }
+
+ // I need to find out which keys does the list have. To do that, I create a
+ // tree from the first instance. This is gives me some top level node,
+ // which will be our list in case out list is a top-level node. In case it
+ // isn't, we have call find_path on the top level node. After that, I just
+ // retrieve the keys.
+ auto topLevelTree = m_schema->dataNodeFromPath(instances.begin()->first);
+ auto list = *(topLevelTree->find_path(path.c_str())->data().begin());
+ auto keys = libyang::Schema_Node_List{list->schema()}.keys();
+
+ // Creating a full tree at the same time from the values sysrepo gives me
+ // would be a pain (and after sysrepo switches to libyang meaningless), so
+ // I just use this algorithm to create data nodes one by one and get the
+ // key values from them.
+ for (const auto& instance : instances) {
+ auto wantedList = *(m_schema->dataNodeFromPath(instance.first)->find_path(path.c_str())->data().begin());
+ ListInstance instanceRes;
+ for (const auto& key : keys) {
+ auto vec = wantedList->find_path(key->name())->data();
+ auto leaf = libyang::Data_Node_Leaf_List{*(vec.begin())};
+ instanceRes.emplace(key->name(), leafValueFromValue(leaf.value(), leaf.leaf_type()->base()));
+ }
+ res.push_back(instanceRes);
+ }
+
+ return res;
+}
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index c2ce909..cebb564 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -42,6 +42,7 @@
void discardChanges() override;
private:
+ std::vector<std::map<std::string, leaf_data_>> listInstances(const std::string& path) override;
[[noreturn]] void reportErrors();
std::string fetchSchema(const char* module, const char* revision, const char* submodule);
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 06259b9..999f33a 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -399,7 +399,7 @@
path.c_str(),
value ? value.value().c_str() : nullptr,
LYD_ANYDATA_CONSTSTRING,
- 0);
+ LYD_PATH_OPT_EDIT);
}
std::shared_ptr<libyang::Module> YangSchema::getYangModule(const std::string& name)
diff --git a/tests/data_query.cpp b/tests/data_query.cpp
new file mode 100644
index 0000000..c62c240
--- /dev/null
+++ b/tests/data_query.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 CESNET, https://photonics.cesnet.cz/
+ *
+ * Written by Václav Kubernát <kubervac@fit.cvut.cz>
+ *
+*/
+
+#include <experimental/iterator>
+#include "trompeloeil_doctest.h"
+
+#ifdef sysrepo_BACKEND
+#include "sysrepo_access.hpp"
+#elif defined(netconf_BACKEND)
+#include "netconf_access.hpp"
+#include "netopeer_vars.hpp"
+#else
+#error "Unknown backend"
+#endif
+#include "data_query.hpp"
+#include "sysrepo_subscription.hpp"
+#include "utils.hpp"
+
+namespace std {
+std::ostream& operator<<(std::ostream& s, const std::vector<std::map<std::string, leaf_data_>> set)
+{
+ s << std::endl << "{" << std::endl;
+ std::transform(set.begin(), set.end(), std::experimental::make_ostream_joiner(s, ", \n"), [](const auto& map) {
+ std::ostringstream ss;
+ ss << " {" << std::endl << " ";
+ std::transform(map.begin(), map.end(), std::experimental::make_ostream_joiner(ss, ", \n "), [](const auto& keyValue){
+ return "{" + keyValue.first + "{" + boost::core::demangle(keyValue.second.type().name()) + "}" + ", " + leafDataToString(keyValue.second) + "}";
+ });
+ ss << std::endl << " }";
+ return ss.str();
+ });
+ s << std::endl << "}" << std::endl;
+ return s;
+}
+}
+
+TEST_CASE("data query")
+{
+ trompeloeil::sequence seq1;
+ SysrepoSubscription subscriptionExample("example-schema");
+ SysrepoSubscription subscriptionOther("other-module");
+
+#ifdef sysrepo_BACKEND
+ SysrepoAccess datastore("netconf-cli-test");
+#elif defined(netconf_BACKEND)
+ NetconfAccess datastore(NETOPEER_SOCKET_PATH);
+#else
+#error "Unknown backend"
+#endif
+
+ DataQuery dataquery(datastore);
+
+ SECTION("listKeys")
+ {
+ dataPath_ location;
+ location.m_scope = Scope::Absolute;
+ ModuleNodePair node;
+ std::vector<std::map<std::string, leaf_data_>> expected;
+
+ SECTION("example-schema:person")
+ {
+ datastore.createListInstance("/example-schema:person[name='Vaclav']");
+ datastore.createListInstance("/example-schema:person[name='Tomas']");
+ datastore.createListInstance("/example-schema:person[name='Jan Novak']");
+ node.first = "example-schema";
+ node.second = "person";
+ expected = {
+ {{"name", std::string{"Jan Novak"}}},
+ {{"name", std::string{"Tomas"}}},
+ {{"name", std::string{"Vaclav"}}}
+ };
+ }
+
+ SECTION("example-schema:selectedNumbers")
+ {
+ datastore.createListInstance("/example-schema:selectedNumbers[value='45']");
+ datastore.createListInstance("/example-schema:selectedNumbers[value='99']");
+ datastore.createListInstance("/example-schema:selectedNumbers[value='127']");
+ node.first = "example-schema";
+ node.second = "selectedNumbers";
+ expected = {
+ {{"value", int8_t{127}}},
+ {{"value", int8_t{45}}},
+ {{"value", int8_t{99}}}
+ };
+ }
+
+ SECTION("example-schema:animalWithColor")
+ {
+ datastore.createListInstance("/example-schema:animalWithColor[name='Dog'][color='brown']");
+ datastore.createListInstance("/example-schema:animalWithColor[name='Dog'][color='white']");
+ datastore.createListInstance("/example-schema:animalWithColor[name='Cat'][color='grey']");
+ node.first = "example-schema";
+ node.second = "animalWithColor";
+ expected = {
+ {{"name", std::string{"Cat"}}, {"color", std::string{"grey"}}},
+ {{"name", std::string{"Dog"}}, {"color", std::string{"brown"}}},
+ {{"name", std::string{"Dog"}}, {"color", std::string{"white"}}}
+ };
+ }
+
+ SECTION("example-schema:animalWithColor - quotes in values")
+ {
+ datastore.createListInstance("/example-schema:animalWithColor[name='D\"o\"g'][color=\"b'r'own\"]");
+ node.first = "example-schema";
+ node.second = "animalWithColor";
+ expected = {
+ {{"name", std::string{"D\"o\"g"}}, {"color", std::string{"b'r'own"}}}
+ };
+ }
+
+ SECTION("example-schema:ports")
+ {
+ datastore.createListInstance("/example-schema:ports[name='A']");
+ datastore.createListInstance("/example-schema:ports[name='B']");
+ datastore.createListInstance("/example-schema:ports[name='E']");
+ node.first = "example-schema";
+ node.second = "ports";
+ expected = {
+ {{"name", enum_{"A"}}},
+ {{"name", enum_{"B"}}},
+ {{"name", enum_{"E"}}},
+ };
+ }
+
+ SECTION("example-schema:org/example:people - nested list")
+ {
+ datastore.createListInstance("/example-schema:org[department='accounting']");
+ datastore.createListInstance("/example-schema:org[department='sales']");
+ datastore.createListInstance("/example-schema:org[department='programmers']");
+ datastore.createListInstance("/example-schema:org[department='accounting']/people[name='Alice']");
+ datastore.createListInstance("/example-schema:org[department='accounting']/people[name='Bob']");
+ datastore.createListInstance("/example-schema:org[department='sales']/people[name='Alice']");
+ datastore.createListInstance("/example-schema:org[department='sales']/people[name='Cyril']");
+ datastore.createListInstance("/example-schema:org[department='sales']/people[name='Alice']/computers[type='laptop']");
+ datastore.createListInstance("/example-schema:org[department='sales']/people[name='Alice']/computers[type='server']");
+ datastore.createListInstance("/example-schema:org[department='sales']/people[name='Cyril']/computers[type='PC']");
+ datastore.createListInstance("/example-schema:org[department='sales']/people[name='Cyril']/computers[type='server']");
+
+ SECTION("outer list")
+ {
+ node.first = "example-schema";
+ node.second = "org";
+ expected = {
+ {{"department", std::string{"accounting"}}},
+ {{"department", std::string{"sales"}}},
+ {{"department", std::string{"programmers"}}},
+ };
+ }
+
+ SECTION("nested list")
+ {
+ listElement_ list;
+ list.m_name = "org";
+ node.second = "people";
+ SECTION("accounting department")
+ {
+ list.m_keys = {
+ {"department", std::string{"accounting"}}
+ };
+ expected = {
+ {{"name", std::string{"Alice"}}},
+ {{"name", std::string{"Bob"}}},
+ };
+ }
+ SECTION("sales department")
+ {
+ list.m_keys = {
+ {"department", std::string{"sales"}}
+ };
+ expected = {
+ {{"name", std::string{"Alice"}}},
+ {{"name", std::string{"Cyril"}}},
+ };
+ }
+ SECTION("programmers department")
+ {
+ list.m_keys = {
+ {"department", std::string{"programmers"}}
+ };
+ expected = {
+ };
+ }
+ location.m_nodes.push_back(dataNode_{{"example-schema"}, list});
+ }
+
+ SECTION("THREE MF NESTED LISTS")
+ {
+ listElement_ listOrg;
+ listOrg.m_name = "org";
+ listOrg.m_keys = {
+ {"department", std::string{"sales"}}
+ };
+
+ listElement_ listPeople;
+ node.second = "computers";
+
+ SECTION("alice computers")
+ {
+ listPeople.m_name = "people";
+ listPeople.m_keys = {
+ {"name", std::string{"Alice"}}
+ };
+ expected = {
+ {{"type", enum_{"laptop"}}},
+ {{"type", enum_{"server"}}},
+ };
+
+ }
+
+ SECTION("cyril computers")
+ {
+ listPeople.m_name = "people";
+ listPeople.m_keys = {
+ {"name", std::string{"Cyril"}}
+ };
+ expected = {
+ {{"type", enum_{"PC"}}},
+ {{"type", enum_{"server"}}},
+ };
+ }
+
+ location.m_nodes.push_back(dataNode_{{"example-schema"}, listOrg});
+ location.m_nodes.push_back(dataNode_{listPeople});
+
+ }
+ }
+
+ SECTION("/other-module:parking-lot/example-schema:cars - list coming from an augment")
+ {
+ datastore.createListInstance("/other-module:parking-lot/example-schema:cars[id='1']");
+ datastore.createListInstance("/other-module:parking-lot/example-schema:cars[id='2']");
+
+ location.m_nodes.push_back(dataNode_{{"other-module"}, container_{"parking-lot"}});
+ node.first = "example-schema";
+ node.second = "cars";
+ expected = {
+ {{"id", int32_t{1}}},
+ {{"id", int32_t{2}}},
+ };
+
+ }
+
+ datastore.commitChanges();
+ std::sort(expected.begin(), expected.end());
+ auto keys = dataquery.listKeys(location, node);
+ std::sort(keys.begin(), keys.end());
+ REQUIRE(keys == expected);
+ }
+
+ waitForCompletionAndBitMore(seq1);
+}
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 112ace9..88b05c3 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -50,7 +50,7 @@
{
trompeloeil::sequence seq1;
MockRecorder mock;
- SysrepoSubscription subscription(&mock);
+ SysrepoSubscription subscription("example-schema", &mock);
#ifdef sysrepo_BACKEND
SysrepoAccess datastore("netconf-cli-test");
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index d993781..a7ff3d8 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -2,6 +2,10 @@
prefix aha;
namespace "http://example.com";
+ import other-module {
+ prefix other;
+ }
+
leaf leafUInt8 {
type uint8;
}
@@ -127,4 +131,68 @@
}
rpc noop {}
+
+ list selectedNumbers {
+ key 'value';
+ leaf value {
+ type int8;
+ }
+ }
+
+ list animalWithColor {
+ key 'name color';
+ leaf name {
+ type string;
+ }
+ leaf color {
+ type string;
+ }
+ }
+
+ list ports {
+ key 'name';
+ leaf name {
+ type enumeration {
+ enum A;
+ enum B;
+ enum C;
+ enum D;
+ enum E;
+ }
+ }
+ }
+
+ list org {
+ key 'department';
+ leaf department {
+ type string;
+ }
+
+ list people {
+ key 'name';
+ leaf name {
+ type string;
+ }
+
+ list computers {
+ key 'type';
+ leaf type {
+ type enumeration {
+ enum PC;
+ enum laptop;
+ enum server;
+ }
+ }
+ }
+ }
+ }
+
+ augment "/other:parking-lot" {
+ list cars {
+ key 'id';
+ leaf id {
+ type int32;
+ }
+ }
+ }
}
diff --git a/tests/mock/sysrepo_subscription.cpp b/tests/mock/sysrepo_subscription.cpp
index 7cd5cf3..c50e19e 100644
--- a/tests/mock/sysrepo_subscription.cpp
+++ b/tests/mock/sysrepo_subscription.cpp
@@ -45,13 +45,16 @@
Recorder::~Recorder() = default;
-SysrepoSubscription::SysrepoSubscription(Recorder* rec)
+SysrepoSubscription::SysrepoSubscription(const std::string& moduleName, Recorder* rec)
: m_connection(new sysrepo::Connection("netconf-cli-test-subscription"))
{
m_session = std::make_shared<sysrepo::Session>(m_connection);
m_subscription = std::make_shared<sysrepo::Subscribe>(m_session);
- const char* modName = "example-schema";
- m_callback = std::make_shared<MyCallback>(modName, rec);
+ if (rec) {
+ m_callback = std::make_shared<MyCallback>(moduleName, rec);
+ } else {
+ m_callback = std::make_shared<sysrepo::Callback>();
+ }
- m_subscription->module_change_subscribe(modName, m_callback);
+ m_subscription->module_change_subscribe(moduleName.c_str(), m_callback);
}
diff --git a/tests/mock/sysrepo_subscription.hpp b/tests/mock/sysrepo_subscription.hpp
index dce6506..0102d1b 100644
--- a/tests/mock/sysrepo_subscription.hpp
+++ b/tests/mock/sysrepo_subscription.hpp
@@ -27,7 +27,7 @@
class SysrepoSubscription {
public:
- SysrepoSubscription(Recorder* rec);
+ SysrepoSubscription(const std::string& moduleName, Recorder* rec = nullptr);
private:
std::shared_ptr<sysrepo::Connection> m_connection;
diff --git a/tests/other-module.yang b/tests/other-module.yang
new file mode 100644
index 0000000..ca1ca06
--- /dev/null
+++ b/tests/other-module.yang
@@ -0,0 +1,7 @@
+module other-module {
+ prefix aha;
+ namespace "http://other.com";
+
+ container parking-lot;
+
+}