Merge changes I0d08d82a,Ie72ef296,I55447e43
* changes:
Python: use the same order of definitions as in the abstract base class
tests: run the Python test suite with the example-module enabled
tests: register kill_daemons as a resource cleanup complementing start_daemons
diff --git a/src/ast_commands.cpp b/src/ast_commands.cpp
index 3af3ff0..c349e8c 100644
--- a/src/ast_commands.cpp
+++ b/src/ast_commands.cpp
@@ -12,6 +12,11 @@
return this->m_path == b.m_path && this->m_data == b.m_data;
}
+bool get_::operator==(const get_& b) const
+{
+ return this->m_path == b.m_path;
+}
+
bool cd_::operator==(const cd_& b) const
{
return this->m_path == b.m_path;
diff --git a/src/ast_handlers.hpp b/src/ast_handlers.hpp
index 33715a4..b9cf883 100644
--- a/src/ast_handlers.hpp
+++ b/src/ast_handlers.hpp
@@ -210,6 +210,18 @@
}
};
+struct leafListElementPath_class {
+ template <typename T, typename Iterator, typename Context>
+ void on_success(Iterator const&, Iterator const&, T& ast, Context const& context)
+ {
+ auto& parserContext = x3::get<parser_context_tag>(context);
+ if (ast.m_nodes.empty() || ast.m_nodes.back().m_suffix.type() != typeid(leafListElement_)) {
+ parserContext.m_errorMsg = "This is not a leaf list element.";
+ _pass(context) = false;
+ }
+ }
+};
+
struct space_separator_class {
template <typename T, typename Iterator, typename Context>
void on_success(Iterator const&, Iterator const&, T&, Context const& context)
diff --git a/src/ast_path.cpp b/src/ast_path.cpp
index 0aa839c..b9b9b5a 100644
--- a/src/ast_path.cpp
+++ b/src/ast_path.cpp
@@ -26,6 +26,23 @@
{
}
+bool leafListElement_::operator==(const leafListElement_& b) const
+{
+ return this->m_name == b.m_name && this->m_value == b.m_value;
+}
+
+leafList_::leafList_() = default;
+
+leafList_::leafList_(const std::string& name)
+ : m_name(name)
+{
+}
+
+bool leafList_::operator==(const leafList_& b) const
+{
+ return this->m_name == b.m_name;
+}
+
bool module_::operator==(const module_& b) const
{
return this->m_name == b.m_name;
@@ -142,6 +159,10 @@
res << "]";
return res.str();
}
+ std::string operator()(const leafListElement_& node) const
+ {
+ return node.m_name + "[.=" + escapeListKeyString(leafDataToString(node.m_value)) + "]";
+ }
std::string operator()(const nodeup_&) const
{
return "..";
@@ -202,6 +223,11 @@
return list_{listElement.m_name};
}
+ auto operator()(const leafListElement_& leafListElement) const
+ {
+ return leafList_{leafListElement.m_name};
+ }
+
template <typename T>
auto operator()(const T& suffix) const
{
diff --git a/src/ast_path.hpp b/src/ast_path.hpp
index 966f771..f768461 100644
--- a/src/ast_path.hpp
+++ b/src/ast_path.hpp
@@ -46,6 +46,22 @@
std::string m_name;
};
+struct leafList_ {
+ leafList_();
+ leafList_(const std::string& name);
+
+ bool operator==(const leafList_& b) const;
+
+ std::string m_name;
+};
+
+struct leafListElement_ {
+ bool operator==(const leafListElement_& b) const;
+
+ std::string m_name;
+ leaf_data_ m_value;
+};
+
struct listElement_ {
listElement_() {}
listElement_(const std::string& listName, const std::map<std::string, leaf_data_>& keys);
@@ -67,7 +83,7 @@
struct schemaNode_ {
boost::optional<module_> m_prefix;
- boost::variant<container_, list_, nodeup_, leaf_> m_suffix;
+ boost::variant<container_, list_, nodeup_, leaf_, leafList_> m_suffix;
schemaNode_();
schemaNode_(decltype(m_suffix) node);
@@ -77,7 +93,7 @@
struct dataNode_ {
boost::optional<module_> m_prefix;
- boost::variant<container_, listElement_, nodeup_, leaf_, list_> m_suffix;
+ boost::variant<container_, listElement_, nodeup_, leaf_, leafListElement_, leafList_, list_> m_suffix;
dataNode_();
dataNode_(decltype(m_suffix) node);
@@ -120,6 +136,7 @@
BOOST_FUSION_ADAPT_STRUCT(container_, m_name)
BOOST_FUSION_ADAPT_STRUCT(listElement_, m_name, m_keys)
+BOOST_FUSION_ADAPT_STRUCT(leafListElement_, m_name, m_value)
BOOST_FUSION_ADAPT_STRUCT(module_, m_name)
BOOST_FUSION_ADAPT_STRUCT(dataNode_, m_prefix, m_suffix)
BOOST_FUSION_ADAPT_STRUCT(schemaNode_, m_prefix, m_suffix)
diff --git a/src/ast_values.cpp b/src/ast_values.cpp
index a381c1a..c3f2ce7 100644
--- a/src/ast_values.cpp
+++ b/src/ast_values.cpp
@@ -99,6 +99,8 @@
return "(presence container)";
case SpecialValue::List:
return "(list)";
+ case SpecialValue::LeafList:
+ return "(leaflist)";
}
__builtin_unreachable();
diff --git a/src/ast_values.hpp b/src/ast_values.hpp
index 9ec8389..c1d8f63 100644
--- a/src/ast_values.hpp
+++ b/src/ast_values.hpp
@@ -50,6 +50,7 @@
enum class SpecialValue {
List,
+ LeafList,
Container,
PresenceContainer
};
diff --git a/src/datastore_access.hpp b/src/datastore_access.hpp
index e70d9c9..6412037 100644
--- a/src/datastore_access.hpp
+++ b/src/datastore_access.hpp
@@ -48,6 +48,8 @@
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 void createLeafListInstance(const std::string& path) = 0;
+ virtual void deleteLeafListInstance(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/grammars.hpp b/src/grammars.hpp
index 1b4b49d..02d6666 100644
--- a/src/grammars.hpp
+++ b/src/grammars.hpp
@@ -53,10 +53,10 @@
cd_::name >> space_separator > dataPath;
auto const create_def =
- create_::name >> space_separator > (presenceContainerPath | listInstancePath);
+ create_::name >> space_separator > (presenceContainerPath | listInstancePath | leafListElementPath);
auto const delete_rule_def =
- delete_::name >> space_separator > (presenceContainerPath | listInstancePath);
+ delete_::name >> space_separator > (presenceContainerPath | listInstancePath | leafListElementPath);
auto const get_def =
get_::name >> -(space_separator >> ((dataPathListEnd | dataPath) | (module >> "*")));
diff --git a/src/interpreter.cpp b/src/interpreter.cpp
index eb0fd66..909d863 100644
--- a/src/interpreter.cpp
+++ b/src/interpreter.cpp
@@ -6,6 +6,7 @@
*
*/
+#include <boost/algorithm/string/predicate.hpp>
#include <boost/mpl/for_each.hpp>
#include <iostream>
#include <sstream>
@@ -41,8 +42,19 @@
void Interpreter::operator()(const get_& get) const
{
auto items = m_datastore.getItems(absolutePathFromCommand(get));
- for (auto it : items) {
- std::cout << it.first << " = " << leafDataToString(it.second) << std::endl;
+ for (auto it = items.begin(); it != items.end(); it++) {
+ auto [path, value] = *it;
+ if (value.type() == typeid(special_) && boost::get<special_>(value).m_value == SpecialValue::LeafList) {
+ auto leafListPrefix = path;
+ std::cout << path << " = " << leafDataToString(value) << std::endl;
+ ++it;
+ while (boost::starts_with(it->first, leafListPrefix)) {
+ std::cout << stripLeafListValueFromPath(it->first) << " = " << leafDataToString(it->second) << std::endl;
+ ++it;
+ }
+ } else {
+ std::cout << path << " = " << leafDataToString(value) << std::endl;
+ }
}
}
@@ -55,6 +67,8 @@
{
if (create.m_path.m_nodes.back().m_suffix.type() == typeid(listElement_))
m_datastore.createListInstance(absolutePathFromCommand(create));
+ else if (create.m_path.m_nodes.back().m_suffix.type() == typeid(leafListElement_))
+ m_datastore.createLeafListInstance(absolutePathFromCommand(create));
else
m_datastore.createPresenceContainer(absolutePathFromCommand(create));
}
@@ -63,6 +77,8 @@
{
if (delet.m_path.m_nodes.back().m_suffix.type() == typeid(container_))
m_datastore.deletePresenceContainer(absolutePathFromCommand(delet));
+ else if (delet.m_path.m_nodes.back().m_suffix.type() == typeid(leafListElement_))
+ m_datastore.deleteLeafListInstance(absolutePathFromCommand(delet));
else
m_datastore.deleteListInstance(absolutePathFromCommand(delet));
}
diff --git a/src/netconf_access.cpp b/src/netconf_access.cpp
index 1340332..6fd3f69 100644
--- a/src/netconf_access.cpp
+++ b/src/netconf_access.cpp
@@ -38,9 +38,15 @@
if (it->schema()->nodetype() == LYS_LIST) {
res.emplace(stripXPathPrefix(it->path()), special_{SpecialValue::List});
}
- if (it->schema()->nodetype() == LYS_LEAF) {
+ if (it->schema()->nodetype() == LYS_LEAF || it->schema()->nodetype() == LYS_LEAFLIST) {
+ using namespace std::string_literals;
libyang::Data_Node_Leaf_List leaf(it);
- res.emplace(stripXPathPrefix(it->path()), leafValueFromValue(leaf.value(), leaf.leaf_type()->base()));
+ auto value = leafValueFromValue(leaf.value(), leaf.leaf_type()->base());
+ if (it->schema()->nodetype() == LYS_LEAFLIST) {
+ std::string strippedLeafListValue = stripLeafListValueFromPath(it->path());
+ res.emplace(stripXPathPrefix(strippedLeafListValue), special_{SpecialValue::LeafList});
+ }
+ res.emplace(stripXPathPrefix(it->path()), value);
}
}
}
@@ -115,6 +121,19 @@
doEditFromDataNode(node);
}
+void NetconfAccess::createLeafListInstance(const std::string& path)
+{
+ auto node = m_schema->dataNodeFromPath(path);
+ doEditFromDataNode(node);
+}
+void NetconfAccess::deleteLeafListInstance(const std::string& path)
+{
+ auto node = m_schema->dataNodeFromPath(path);
+ auto list = *(node->find_path(path.c_str())->data().begin());
+ list->insert_attr(m_schema->getYangModule("ietf-netconf"), "operation", "delete");
+ doEditFromDataNode(node);
+}
+
void NetconfAccess::doEditFromDataNode(std::shared_ptr<libyang::Data_Node> dataNode)
{
auto data = dataNode->print_mem(LYD_XML, 0);
diff --git a/src/netconf_access.hpp b/src/netconf_access.hpp
index 107fcd3..964fa54 100644
--- a/src/netconf_access.hpp
+++ b/src/netconf_access.hpp
@@ -39,6 +39,8 @@
void deletePresenceContainer(const std::string& path) override;
void createListInstance(const std::string& path) override;
void deleteListInstance(const std::string& path) override;
+ void createLeafListInstance(const std::string& path) override;
+ void deleteLeafListInstance(const std::string& path) override;
void commitChanges() override;
void discardChanges() override;
Tree executeRpc(const std::string& path, const Tree& input) override;
diff --git a/src/path_parser.hpp b/src/path_parser.hpp
index 15655d7..ecd3bd7 100644
--- a/src/path_parser.hpp
+++ b/src/path_parser.hpp
@@ -17,6 +17,7 @@
x3::rule<writable_leaf_path_class, dataPath_> const writableLeafPath = "writableLeafPath";
x3::rule<presenceContainerPath_class, dataPath_> const presenceContainerPath = "presenceContainerPath";
x3::rule<listInstancePath_class, dataPath_> const listInstancePath = "listInstancePath";
+x3::rule<leafListElementPath_class, dataPath_> const leafListElementPath = "leafListElementPath";
x3::rule<initializePath_class, x3::unused_type> const initializePath = "initializePath";
x3::rule<trailingSlash_class, TrailingSlash> const trailingSlash = "trailingSlash";
x3::rule<absoluteStart_class, Scope> const absoluteStart = "absoluteStart";
@@ -26,6 +27,7 @@
x3::rule<createKeySuggestions_class, x3::unused_type> const createKeySuggestions = "createKeySuggestions";
x3::rule<createValueSuggestions_class, x3::unused_type> const createValueSuggestions = "createValueSuggestions";
x3::rule<suggestKeysEnd_class, x3::unused_type> const suggestKeysEnd = "suggestKeysEnd";
+x3::rule<class leafListValue_class, leaf_data_> const leafListValue = "leafListValue";
enum class NodeParserMode {
CompleteDataNode,
@@ -56,6 +58,7 @@
using type = dataNode_;
};
+
template <NodeParserMode PARSER_MODE>
struct NodeParser : x3::parser<NodeParser<PARSER_MODE>> {
using attribute_type = typename ModeToAttribute<PARSER_MODE>::type;
@@ -113,9 +116,16 @@
}
parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
break;
+ case yang::NodeTypes::LeafList:
+ if constexpr (std::is_same<attribute_type, schemaNode_>()) {
+ out.m_suffix = leafList_{child.second};
+ } else {
+ out.m_suffix = leafListElement_{child.second, {}};
+ }
+ parserContext.m_suggestions.emplace(Completion{parseString, "[", Completion::WhenToAdd::IfFullMatch});
+ break;
case yang::NodeTypes::Action:
case yang::NodeTypes::AnyXml:
- case yang::NodeTypes::LeafList:
case yang::NodeTypes::Notification:
case yang::NodeTypes::Rpc:
continue;
@@ -172,6 +182,25 @@
}
}
}
+
+ if (attr.m_suffix.type() == typeid(leafListElement_)) {
+ parserContext.m_tmpListKeyLeafPath.m_location = parserContext.currentSchemaPath();
+ ModuleNodePair node{attr.m_prefix.flat_map([](const auto& it) {
+ return boost::optional<std::string>{it.m_name};
+ }),
+ boost::get<leafListElement_>(attr.m_suffix).m_name};
+ parserContext.m_tmpListKeyLeafPath.m_node = node;
+ res = leafListValue.parse(begin, end, ctx, rctx, boost::get<leafListElement_>(attr.m_suffix).m_value);
+
+ if (!res) {
+ if constexpr (PARSER_MODE == NodeParserMode::IncompleteDataNode) {
+ res = true;
+ attr.m_suffix = leafList_{boost::get<leafListElement_>(attr.m_suffix).m_name};
+ } else {
+ begin = saveIter;
+ }
+ }
+ }
}
if (res) {
@@ -189,7 +218,7 @@
using schemaNode = NodeParser<NodeParserMode::SchemaNode>;
using dataNode = NodeParser<NodeParserMode::CompleteDataNode>;
-using dataNodeAllowList = NodeParser<NodeParserMode::IncompleteDataNode>;
+using incompleteDataNode = NodeParser<NodeParserMode::IncompleteDataNode>;
using pathCompletions = NodeParser<NodeParserMode::CompletionsOnly>;
using AnyPath = boost::variant<schemaPath_, dataPath_>;
@@ -245,7 +274,7 @@
if constexpr (PARSER_MODE == PathParserMode::DataPathListEnd || PARSER_MODE == PathParserMode::AnyPath) {
if (!res || !pathEnd.parse(begin, end, ctx, rctx, x3::unused)) {
dataNode_ attrNodeList;
- res = dataNodeAllowList{m_filterFunction}.parse(begin, end, ctx, rctx, attrNodeList);
+ res = incompleteDataNode{m_filterFunction}.parse(begin, end, ctx, rctx, attrNodeList);
if (res) {
attrData.m_nodes.push_back(attrNodeList);
// If the trailing slash matches, no more nodes are parsed.
@@ -295,6 +324,22 @@
#pragma GCC diagnostic ignored "-Woverloaded-shift-op-parentheses"
#endif
+struct SuggestLeafListEnd : x3::parser<SuggestLeafListEnd> {
+ using attribute_type = x3::unused_type;
+ template <typename It, typename Ctx, typename RCtx, typename Attr>
+ bool parse(It& begin, It, Ctx const& ctx, RCtx&, Attr&) const
+ {
+ auto& parserContext = x3::get<parser_context_tag>(ctx);
+ parserContext.m_completionIterator = begin;
+ parserContext.m_suggestions = {Completion{"]"}};
+
+ return true;
+ }
+} const suggestLeafListEnd;
+
+auto const leafListValue_def =
+ '[' >> leaf_data >> suggestLeafListEnd >> ']';
+
auto const rest =
x3::omit[x3::no_skip[+(x3::char_ - '/' - space_separator)]];
@@ -344,6 +389,9 @@
auto const listInstancePath_def =
dataPath;
+auto const leafListElementPath_def =
+ dataPath;
+
// A "nothing" parser, which is used to indicate we tried to parse a path
auto const initializePath_def =
x3::eps;
@@ -360,9 +408,11 @@
BOOST_SPIRIT_DEFINE(writableLeafPath)
BOOST_SPIRIT_DEFINE(presenceContainerPath)
BOOST_SPIRIT_DEFINE(listInstancePath)
+BOOST_SPIRIT_DEFINE(leafListElementPath)
BOOST_SPIRIT_DEFINE(initializePath)
BOOST_SPIRIT_DEFINE(createKeySuggestions)
BOOST_SPIRIT_DEFINE(createValueSuggestions)
BOOST_SPIRIT_DEFINE(suggestKeysEnd)
+BOOST_SPIRIT_DEFINE(leafListValue)
BOOST_SPIRIT_DEFINE(absoluteStart)
BOOST_SPIRIT_DEFINE(trailingSlash)
diff --git a/src/schema.cpp b/src/schema.cpp
index 2cfcbf2..e092569 100644
--- a/src/schema.cpp
+++ b/src/schema.cpp
@@ -46,3 +46,12 @@
return false;
}
}
+
+bool Schema::isLeafList(const std::string& path) const
+{
+ try {
+ return nodeType(path) == yang::NodeTypes::LeafList;
+ } catch (InvalidNodeException&) {
+ return false;
+ }
+}
diff --git a/src/schema.hpp b/src/schema.hpp
index 0a9b2ff..995d921 100644
--- a/src/schema.hpp
+++ b/src/schema.hpp
@@ -58,6 +58,7 @@
bool isLeaf(const schemaPath_& location, const ModuleNodePair& node) const;
bool isList(const schemaPath_& location, const ModuleNodePair& node) const;
bool isPresenceContainer(const schemaPath_& location, const ModuleNodePair& node) const;
+ bool isLeafList(const std::string& path) const;
virtual yang::NodeTypes nodeType(const std::string& path) const = 0;
virtual yang::NodeTypes nodeType(const schemaPath_& location, const ModuleNodePair& node) const = 0;
virtual bool isModule(const std::string& name) const = 0;
diff --git a/src/static_schema.cpp b/src/static_schema.cpp
index 738ce49..a1a5602 100644
--- a/src/static_schema.cpp
+++ b/src/static_schema.cpp
@@ -49,7 +49,7 @@
assert(isList(location, node));
const auto& child = children(locationString).at(fullNodeName(location, node));
- const auto& list = boost::get<yang::list>(child.m_nodeType);
+ const auto& list = std::get<yang::list>(child.m_nodeType);
return list.m_keys.find(key) != list.m_keys.end();
}
@@ -59,7 +59,7 @@
assert(isList(location, node));
const auto& child = children(locationString).at(fullNodeName(location, node));
- const auto& list = boost::get<yang::list>(child.m_nodeType);
+ const auto& list = std::get<yang::list>(child.m_nodeType);
return list.m_keys;
}
@@ -86,6 +86,13 @@
m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
}
+void StaticSchema::addLeafList(const std::string& location, const std::string& name, const yang::LeafDataType& type)
+{
+ m_nodes.at(location).emplace(name, NodeInfo{yang::leaflist{yang::TypeInfo{type, std::nullopt}}, yang::AccessType::Writable});
+ std::string key = joinPaths(location, name);
+ m_nodes.emplace(key, std::unordered_map<std::string, NodeInfo>());
+}
+
void StaticSchema::addModule(const std::string& name)
{
m_modules.emplace(name);
@@ -120,14 +127,23 @@
yang::TypeInfo StaticSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
{
std::string locationString = pathToSchemaString(location, Prefixes::Always);
- return boost::get<yang::leaf>(children(locationString).at(fullNodeName(location, node)).m_nodeType).m_type;
+ auto nodeType = children(locationString).at(fullNodeName(location, node)).m_nodeType;
+ if (std::holds_alternative<yang::leaf>(nodeType)) {
+ return std::get<yang::leaf>(nodeType).m_type;
+ }
+
+ if (std::holds_alternative<yang::leaflist>(nodeType)) {
+ return std::get<yang::leaflist>(nodeType).m_type;
+ }
+
+ throw std::logic_error("StaticSchema::leafType: Path is not a leaf or a leaflist");
}
yang::TypeInfo StaticSchema::leafType(const std::string& path) const
{
auto locationString = stripLastNodeFromPath(path);
auto node = lastNodeOfSchemaPath(path);
- return boost::get<yang::leaf>(children(locationString).at(node).m_nodeType).m_type;
+ return std::get<yang::leaf>(children(locationString).at(node).m_nodeType).m_type;
}
std::set<ModuleNodePair> StaticSchema::availableNodes(const boost::variant<dataPath_, schemaPath_, module_>& path, const Recursion recursion) const
@@ -178,6 +194,30 @@
return res;
}
+struct impl_nodeType {
+
+ yang::NodeTypes operator()(const yang::container& cont)
+ {
+ if (cont.m_presence == yang::ContainerTraits::Presence) {
+ return yang::NodeTypes::PresenceContainer;
+ }
+ return yang::NodeTypes::Container;
+ }
+ yang::NodeTypes operator()(const yang::list&)
+ {
+ return yang::NodeTypes::List;
+ }
+ yang::NodeTypes operator()(const yang::leaf&)
+ {
+ return yang::NodeTypes::Leaf;
+
+ }
+ yang::NodeTypes operator()(const yang::leaflist&)
+ {
+ return yang::NodeTypes::LeafList;
+ }
+};
+
yang::NodeTypes StaticSchema::nodeType(const schemaPath_& location, const ModuleNodePair& node) const
{
std::string locationString = pathToSchemaString(location, Prefixes::Always);
@@ -185,23 +225,7 @@
try {
auto targetNode = children(locationString).at(fullName);
- if (targetNode.m_nodeType.type() == typeid(yang::container)) {
- if (boost::get<yang::container>(targetNode.m_nodeType).m_presence == yang::ContainerTraits::Presence) {
- return yang::NodeTypes::PresenceContainer;
- }
- return yang::NodeTypes::Container;
- }
-
- if (targetNode.m_nodeType.type() == typeid(yang::list)) {
- return yang::NodeTypes::List;
- }
-
- if (targetNode.m_nodeType.type() == typeid(yang::leaf)) {
- return yang::NodeTypes::Leaf;
- }
-
- throw std::runtime_error{"YangSchema::nodeType: unsupported type"};
-
+ return std::visit(impl_nodeType{}, targetNode.m_nodeType);
} catch (std::out_of_range&) {
throw InvalidNodeException();
}
diff --git a/src/static_schema.hpp b/src/static_schema.hpp
index b94dfa3..aab4383 100644
--- a/src/static_schema.hpp
+++ b/src/static_schema.hpp
@@ -30,6 +30,10 @@
yang::TypeInfo m_type;
};
+struct leaflist {
+ yang::TypeInfo m_type;
+};
+
struct module {
};
@@ -39,7 +43,7 @@
};
}
-using NodeType = boost::variant<yang::container, yang::list, yang::leaf, yang::module>;
+using NodeType = std::variant<yang::container, yang::list, yang::leaf, yang::leaflist>;
struct NodeInfo {
NodeType m_nodeType;
@@ -77,6 +81,7 @@
std::set<identityRef_> validIdentities(std::string_view module, std::string_view value);
void addContainer(const std::string& location, const std::string& name, yang::ContainerTraits isPresence = yang::ContainerTraits::None);
void addLeaf(const std::string& location, const std::string& name, const yang::LeafDataType& type, const yang::AccessType accessType = yang::AccessType::Writable);
+ void addLeafList(const std::string& location, const std::string& name, const yang::LeafDataType& type);
void addList(const std::string& location, const std::string& name, const std::set<std::string>& keys);
void addModule(const std::string& name);
void addIdentity(const std::optional<identityRef_>& base, const identityRef_& name);
diff --git a/src/sysrepo_access.cpp b/src/sysrepo_access.cpp
index 5b01d3e..4aaa156 100644
--- a/src/sysrepo_access.cpp
+++ b/src/sysrepo_access.cpp
@@ -191,11 +191,17 @@
using namespace std::string_literals;
Tree res;
- auto fillMap = [&res](auto items) {
+ auto fillMap = [this, &res](auto items) {
if (!items)
return;
for (unsigned int i = 0; i < items->val_cnt(); i++) {
- res.emplace(items->val(i)->xpath(), leafValueFromVal(items->val(i)));
+ auto value = leafValueFromVal(items->val(i));
+ if (m_schema->isLeafList(items->val(i)->xpath())) {
+ res.emplace(items->val(i)->xpath(), special_{SpecialValue::LeafList});
+ res.emplace(items->val(i)->xpath() + "[.="s + escapeListKeyString(leafDataToString(value)) + "]", value);
+ } else {
+ res.emplace(items->val(i)->xpath(), value);
+ }
}
};
@@ -242,6 +248,24 @@
}
}
+void SysrepoAccess::createLeafListInstance(const std::string& path)
+{
+ try {
+ m_session->set_item(path.c_str());
+ } catch (sysrepo::sysrepo_exception& ex) {
+ reportErrors();
+ }
+}
+
+void SysrepoAccess::deleteLeafListInstance(const std::string& path)
+{
+ try {
+ m_session->delete_item(path.c_str());
+ } catch (sysrepo::sysrepo_exception& ex) {
+ reportErrors();
+ }
+}
+
void SysrepoAccess::createListInstance(const std::string& path)
{
try {
diff --git a/src/sysrepo_access.hpp b/src/sysrepo_access.hpp
index 1d72525..26d6c80 100644
--- a/src/sysrepo_access.hpp
+++ b/src/sysrepo_access.hpp
@@ -34,6 +34,8 @@
void deletePresenceContainer(const std::string& path) override;
void createListInstance(const std::string& path) override;
void deleteListInstance(const std::string& path) override;
+ void createLeafListInstance(const std::string& path) override;
+ void deleteLeafListInstance(const std::string& path) override;
Tree executeRpc(const std::string& path, const Tree& input) override;
std::shared_ptr<Schema> schema() override;
diff --git a/src/utils.cpp b/src/utils.cpp
index 3543787..941fa4e 100644
--- a/src/utils.cpp
+++ b/src/utils.cpp
@@ -228,3 +228,10 @@
{
return boost::apply_visitor(getSchemaPathVisitor(), path);
}
+
+std::string stripLeafListValueFromPath(const std::string& path)
+{
+ auto res = path;
+ res.erase(res.find_last_of('['));
+ return res;
+}
diff --git a/src/utils.hpp b/src/utils.hpp
index 781170d..ecbf919 100644
--- a/src/utils.hpp
+++ b/src/utils.hpp
@@ -25,3 +25,4 @@
std::string fullNodeName(const dataPath_& location, const ModuleNodePair& pair);
std::string leafDataToString(const leaf_data_ value);
schemaPath_ anyPathToSchemaPath(const boost::variant<dataPath_, schemaPath_, module_>& path);
+std::string stripLeafListValueFromPath(const std::string& path);
diff --git a/src/yang_schema.cpp b/src/yang_schema.cpp
index 132c25f..48b3724 100644
--- a/src/yang_schema.cpp
+++ b/src/yang_schema.cpp
@@ -201,10 +201,11 @@
}
}
+template <typename NodeType>
yang::TypeInfo YangSchema::impl_leafType(const libyang::S_Schema_Node& node) const
{
using namespace std::string_literals;
- auto leaf = std::make_shared<libyang::Schema_Node_Leaf>(node);
+ auto leaf = std::make_shared<NodeType>(node);
auto leafUnits = leaf->units();
std::function<yang::TypeInfo(std::shared_ptr<libyang::Type>)> resolveType;
resolveType = [this, &resolveType, leaf, leafUnits] (std::shared_ptr<libyang::Type> type) -> yang::TypeInfo {
@@ -293,12 +294,28 @@
yang::TypeInfo YangSchema::leafType(const schemaPath_& location, const ModuleNodePair& node) const
{
- return impl_leafType(getSchemaNode(location, node));
+ auto lyNode = getSchemaNode(location, node);
+ switch (lyNode->nodetype()) {
+ case LYS_LEAF:
+ return impl_leafType<libyang::Schema_Node_Leaf>(lyNode);
+ case LYS_LEAFLIST:
+ return impl_leafType<libyang::Schema_Node_Leaflist>(lyNode);
+ default:
+ throw std::logic_error("YangSchema::leafType: type must be leaf or leaflist");
+ }
}
yang::TypeInfo YangSchema::leafType(const std::string& path) const
{
- return impl_leafType(getSchemaNode(path));
+ auto lyNode = getSchemaNode(path);
+ switch (lyNode->nodetype()) {
+ case LYS_LEAF:
+ return impl_leafType<libyang::Schema_Node_Leaf>(lyNode);
+ case LYS_LEAFLIST:
+ return impl_leafType<libyang::Schema_Node_Leaflist>(lyNode);
+ default:
+ throw std::logic_error("YangSchema::leafType: type must be leaf or leaflist");
+ }
}
std::optional<std::string> YangSchema::leafTypeName(const std::string& path) const
diff --git a/src/yang_schema.hpp b/src/yang_schema.hpp
index a4c0b90..5b2db04 100644
--- a/src/yang_schema.hpp
+++ b/src/yang_schema.hpp
@@ -70,6 +70,7 @@
std::shared_ptr<libyang::Module> getYangModule(const std::string& name);
private:
+ template <typename NodeType>
yang::TypeInfo impl_leafType(const std::shared_ptr<libyang::Schema_Node>& node) const;
std::set<std::string> modules() const;
diff --git a/submodules/dependencies b/submodules/dependencies
index 676f63b..ab68d53 160000
--- a/submodules/dependencies
+++ b/submodules/dependencies
@@ -1 +1 @@
-Subproject commit 676f63bcbf2a14140bf303a51b1fa73d9b8f27d2
+Subproject commit ab68d53de22d1ce6ead243a612538e6244522ff6
diff --git a/tests/datastore_access.cpp b/tests/datastore_access.cpp
index 088fc4b..e11eec5 100644
--- a/tests/datastore_access.cpp
+++ b/tests/datastore_access.cpp
@@ -386,6 +386,36 @@
REQUIRE(datastore.getItems(xpath) == expected);
}
+ SECTION("leaf list")
+ {
+ DatastoreAccess::Tree expected;
+ REQUIRE_CALL(mock, write("/example-schema:addresses", std::nullopt, "0.0.0.0"s));
+ REQUIRE_CALL(mock, write("/example-schema:addresses", std::nullopt, "127.0.0.1"s));
+ datastore.createLeafListInstance("/example-schema:addresses[.='0.0.0.0']");
+ datastore.createLeafListInstance("/example-schema:addresses[.='127.0.0.1']");
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:addresses", special_{SpecialValue::LeafList}},
+ {"/example-schema:addresses[.='0.0.0.0']", "0.0.0.0"s},
+ {"/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
+
+ REQUIRE_CALL(mock, write("/example-schema:addresses", "0.0.0.0"s, std::nullopt));
+ datastore.deleteLeafListInstance("/example-schema:addresses[.='0.0.0.0']");
+ datastore.commitChanges();
+ expected = {
+ {"/example-schema:addresses", special_{SpecialValue::LeafList}},
+ {"/example-schema:addresses[.='127.0.0.1']", "127.0.0.1"s},
+ };
+ REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
+
+ REQUIRE_CALL(mock, write("/example-schema:addresses", "127.0.0.1"s, std::nullopt));
+ datastore.deleteLeafListInstance("/example-schema:addresses[.='127.0.0.1']");
+ datastore.commitChanges();
+ expected = {};
+ REQUIRE(datastore.getItems("/example-schema:addresses") == expected);
+ }
SECTION("copying data from startup refreshes the data")
{
diff --git a/tests/datastoreaccess_mock.hpp b/tests/datastoreaccess_mock.hpp
index 8798973..ac75875 100644
--- a/tests/datastoreaccess_mock.hpp
+++ b/tests/datastoreaccess_mock.hpp
@@ -23,6 +23,8 @@
IMPLEMENT_MOCK2(setLeaf);
IMPLEMENT_MOCK1(createPresenceContainer);
IMPLEMENT_MOCK1(deletePresenceContainer);
+ IMPLEMENT_MOCK1(createLeafListInstance);
+ IMPLEMENT_MOCK1(deleteLeafListInstance);
IMPLEMENT_MOCK1(createListInstance);
IMPLEMENT_MOCK1(deleteListInstance);
IMPLEMENT_MOCK2(executeRpc);
diff --git a/tests/example-schema.yang b/tests/example-schema.yang
index ef9d02d..3f32a87 100644
--- a/tests/example-schema.yang
+++ b/tests/example-schema.yang
@@ -247,4 +247,8 @@
leaf dummy {
type empty;
}
+
+ leaf-list addresses {
+ type string;
+ }
}
diff --git a/tests/list_manipulation.cpp b/tests/list_manipulation.cpp
index 15f2f04..974d806 100644
--- a/tests/list_manipulation.cpp
+++ b/tests/list_manipulation.cpp
@@ -11,11 +11,13 @@
TEST_CASE("list manipulation")
{
+ using namespace std::string_literals;
auto schema = std::make_shared<StaticSchema>();
schema->addModule("mod");
schema->addList("/", "mod:list", {"number"});
schema->addLeaf("/mod:list", "mod:number", yang::Int32{});
schema->addLeaf("/mod:list", "mod:leafInList", yang::String{});
+ schema->addLeafList("/", "mod:addresses", yang::String{});
Parser parser(schema);
std::string input;
std::ostringstream errorStream;
@@ -31,6 +33,13 @@
expectedPath.m_nodes.push_back(dataNode_{module_{"mod"}, listElement_("list", keys)});
}
+ SECTION("create mod:addresses['0.0.0.0']")
+ {
+ input = "mod:addresses['0.0.0.0']";
+ expectedPath.m_nodes.push_back(dataNode_{module_{"mod"}, leafListElement_{"addresses", "0.0.0.0"s}});
+ }
+
+
command_ parsedCreate = parser.parseCommand("create " + input, errorStream);
command_ parsedDelete = parser.parseCommand("delete " + input, errorStream);
create_ expectedCreate;
@@ -42,4 +51,17 @@
REQUIRE(boost::get<create_>(parsedCreate) == expectedCreate);
REQUIRE(boost::get<delete_>(parsedDelete) == expectedDelete);
}
+
+ SECTION("retrieving all leaflist instances")
+ {
+ dataPath_ expected;
+ input = "get mod:addresses";
+ expected.m_nodes.push_back(dataNode_{module_{"mod"}, leafList_{"addresses"}});
+
+ get_ expectedGet;
+ expectedGet.m_path = expected;
+ command_ commandGet = parser.parseCommand(input, errorStream);
+ REQUIRE(commandGet.type() == typeid(get_));
+ REQUIRE(boost::get<get_>(commandGet) == expectedGet);
+ }
}
diff --git a/tests/path_utils.cpp b/tests/path_utils.cpp
index 3a3fb72..6765001 100644
--- a/tests/path_utils.cpp
+++ b/tests/path_utils.cpp
@@ -28,6 +28,21 @@
path.m_nodes.push_back(dataNode_{module_{"example-schema"}, listElement_{"twoKeyList", {{"first", std::string{"a"}}, {"second", std::string{"b"}}}}});
expected += "example-schema:twoKeyList[first='a'][second='b']";
}
+
+ SECTION("example-schema:addresses[.='0.0.0.0']")
+ {
+ SECTION("absolute")
+ {
+ path.m_scope = Scope::Absolute;
+ expected += "/";
+ }
+ SECTION("relative")
+ {
+ path.m_scope = Scope::Relative;
+ }
+ path.m_nodes.push_back(dataNode_{module_{"example-schema"}, leafListElement_{"addresses", std::string{"0.0.0.0"}}});
+ expected += "example-schema:addresses[.='0.0.0.0']";
+ }
REQUIRE(pathToDataString(path, Prefixes::WhenNeeded) == expected);
}
}
diff --git a/tests/yang.cpp b/tests/yang.cpp
index 3d42c5e..71cb69d 100644
--- a/tests/yang.cpp
+++ b/tests/yang.cpp
@@ -419,6 +419,9 @@
status obsolete;
}
+ leaf-list addresses {
+ type string;
+ }
})";
namespace std {
@@ -809,6 +812,14 @@
}};
}
+ SECTION("addresses")
+ {
+ node.first = "example-schema";
+ node.second = "addresses";
+ type.emplace<yang::String>();
+ }
+
+
REQUIRE(ys.leafType(path, node) == type);
}
SECTION("availableNodes")
@@ -851,6 +862,7 @@
{"example-schema"s, "myRpc"},
{"example-schema"s, "systemStats"},
{"example-schema"s, "dummyLeaf"},
+ {"example-schema"s, "addresses"},
{"example-schema"s, "subLeaf"}};
}
@@ -894,6 +906,7 @@
{"example-schema"s, "activeMappedPort"},
{"example-schema"s, "activeNumber"},
{"example-schema"s, "activePort"},
+ {"example-schema"s, "addresses"},
{"example-schema"s, "another-duration"},
{"example-schema"s, "b"},
{"example-schema"s, "carry"},
@@ -943,6 +956,7 @@
{boost::none, "/example-schema:_list/contInList"},
{boost::none, "/example-schema:_list/number"},
{boost::none, "/example-schema:a"},
+ {boost::none, "/example-schema:addresses"},
{boost::none, "/example-schema:a/a2"},
{boost::none, "/example-schema:a/a2/a3"},
{boost::none, "/example-schema:a/leafa"},
@@ -1129,6 +1143,8 @@
expectedType.emplace<yang::Int32>();
expectedUnits = "vt";
}
+ auto nodeType = ys.nodeType(pathToSchemaString(path, Prefixes::WhenNeeded));
+ REQUIRE((nodeType == yang::NodeTypes::Leaf || nodeType == yang::NodeTypes::LeafList));
REQUIRE(ys.leafType(pathToSchemaString(path, Prefixes::WhenNeeded)) == yang::TypeInfo{expectedType, expectedUnits});
}