mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
Bump sysrepo, netopeer2, libnetconf2, libyang, libyang-cpp, sysrepo-cpp, rousette
This commit is contained in:
+1
-1
Submodule buildroot updated: 74fc86b099...33d2ca716f
@@ -3,6 +3,15 @@ Change Log
|
||||
|
||||
All notable changes to the project are documented in this file.
|
||||
|
||||
[v25.05.0][UNRELEASED]
|
||||
-------------------------
|
||||
### Changes
|
||||
- Upgrade libyang to 3.12.2
|
||||
- Upgrade sysrepo to 3.6.11
|
||||
- Upgrade netopeer2 (NETCONF) to 2.4.1
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
[v25.04.0][] - 2025-04-30
|
||||
-------------------------
|
||||
@@ -1547,6 +1556,7 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
|
||||
|
||||
[buildroot]: https://buildroot.org/
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.04.0...HEAD
|
||||
[v25.05.0]: https://github.com/kernelkit/infix/compare/v25.04.0...v25.05.0
|
||||
[v25.04.0]: https://github.com/kernelkit/infix/compare/v25.03.0...v25.04.0
|
||||
[v25.03.0]: https://github.com/kernelkit/infix/compare/v25.02.0...v25.03.0
|
||||
[v25.02.0]: https://github.com/kernelkit/infix/compare/v25.01.0...v25.02.0
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From d0f6422fee7a46fcb7445c88f499f61b3eb0ead0 Mon Sep 17 00:00:00 2001
|
||||
From: Adam Piecek <Adam.Piecek@cesnet.cz>
|
||||
Date: Wed, 23 Oct 2024 14:37:09 +0200
|
||||
Subject: [PATCH 1/8] added support for RpcYang in Context::parseOp
|
||||
Subject: [PATCH 01/18] added support for RpcYang in Context::parseOp
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I25182ea2d042be1e6e4246e18aee260cc032e547
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 7e015f3486bdbb54f1dcc2e2ce51102b1d623081 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 23 Oct 2024 12:52:24 +0200
|
||||
Subject: [PATCH 2/8] throw when lyd_validate_all returns error
|
||||
Subject: [PATCH 02/18] throw when lyd_validate_all returns error
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Bug: https://github.com/CESNET/libyang-cpp/issues/20
|
||||
Change-Id: I005a2f1b057978573a4046e7b4cc31d77e36fde3
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 490d8bb242d33213b948485f5b94c55e22cf86a6 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 21 Nov 2024 11:32:44 +0100
|
||||
Subject: [PATCH 3/8] remove a misleading comment
|
||||
Subject: [PATCH 03/18] remove a misleading comment
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
The whole intention within action's input/output handling here was to
|
||||
put some emphasis on the fact that we aren't tracking the input/output
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From e1b17386cf61048d2fe27fffb3b763981a225f52 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Wed, 27 Nov 2024 09:47:47 +0100
|
||||
Subject: [PATCH 4/8] schema: improve `List::keys()` not to use `std::move`
|
||||
Subject: [PATCH 04/18] schema: improve `List::keys()` not to use `std::move`
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
`List::keys()` used `std::move` while iterating over array of leafs.
|
||||
This was solved without using `std::move`.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 1102ecdcafbc9206f59b383769687e418557838e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Mon, 25 Nov 2024 15:54:02 +0100
|
||||
Subject: [PATCH 5/8] schema: make leaf-list's `default` statement available
|
||||
Subject: [PATCH 05/18] schema: make leaf-list's `default` statement available
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Make leaf-list's `default` statement available so that it can be
|
||||
accessed if end-user requires reading schema nodes.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 01f2633cef60495d5cafc4b4b1f25273b03ab3cd Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Tue, 22 Oct 2024 15:11:30 +0200
|
||||
Subject: [PATCH 6/8] schema: Make choice and case statements available
|
||||
Subject: [PATCH 06/18] schema: Make choice and case statements available
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Make choice and case statements available so that they can be accessed
|
||||
if end-user requires reading schema nodes.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From a1acdc794facf8cbf113f73274ecebd5898c81a1 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 17 Dec 2024 15:08:43 +0100
|
||||
Subject: [PATCH 7/8] Wrap lyd_change_term for changing the value for a
|
||||
Subject: [PATCH 07/18] Wrap lyd_change_term for changing the value for a
|
||||
terminal node
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Previously, the code would require a newPath(...,
|
||||
libyang::CreationOptions::Update), which is quite a mouthful.
|
||||
|
||||
@@ -0,0 +1,652 @@
|
||||
From 32b200ed06e9adb44a8d4ce6771f18812a54d06e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Bed=C5=99ich=20Schindler?= <bedrich.schindler@gmail.com>
|
||||
Date: Wed, 20 Nov 2024 10:20:19 +0100
|
||||
Subject: [PATCH 08/18] Add `Module::child()`, `Module::childrenDfs()` and
|
||||
`Module::immediateChildren()`
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Those functions are implemented in the same manner as in `SchemaNode`
|
||||
and allows to walk through modules children. This is counterpart to
|
||||
already implemented `Module::childInstantiables()` that returns
|
||||
instantiables schema nodes. These return all nodes, including
|
||||
the schema-only nodes such as choice and case if end-user needs
|
||||
to read its schema.
|
||||
|
||||
While the implementation is inspired by functions in `SchemaNode`,
|
||||
imlementation of `Module::parent()` and `Module::siblings()` was omitted
|
||||
as those do no make sense on `Module`.
|
||||
|
||||
Change-Id: I38c8374304f859d65343d04d08302e07deb05f27
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/Collection.hpp | 1 +
|
||||
include/libyang-cpp/Module.hpp | 5 +
|
||||
src/Module.cpp | 40 +++
|
||||
tests/context.cpp | 5 +
|
||||
tests/example_schema.hpp | 21 ++
|
||||
tests/schema_node.cpp | 409 +++++++++++++++++++----------
|
||||
6 files changed, 346 insertions(+), 135 deletions(-)
|
||||
|
||||
diff --git a/include/libyang-cpp/Collection.hpp b/include/libyang-cpp/Collection.hpp
|
||||
index 557a0a2..4324791 100644
|
||||
--- a/include/libyang-cpp/Collection.hpp
|
||||
+++ b/include/libyang-cpp/Collection.hpp
|
||||
@@ -98,6 +98,7 @@ class LIBYANG_CPP_EXPORT Collection {
|
||||
public:
|
||||
friend DataNode;
|
||||
friend Iterator<NodeType, ITER_TYPE>;
|
||||
+ friend Module;
|
||||
friend SchemaNode;
|
||||
~Collection();
|
||||
Collection(const Collection<NodeType, ITER_TYPE>&);
|
||||
diff --git a/include/libyang-cpp/Module.hpp b/include/libyang-cpp/Module.hpp
|
||||
index f10c36f..ab20d36 100644
|
||||
--- a/include/libyang-cpp/Module.hpp
|
||||
+++ b/include/libyang-cpp/Module.hpp
|
||||
@@ -34,6 +34,8 @@ class ChildInstanstiables;
|
||||
class Identity;
|
||||
class SchemaNode;
|
||||
class SubmoduleParsed;
|
||||
+template <typename NodeType, IterationType ITER_TYPE>
|
||||
+class Collection;
|
||||
|
||||
namespace types {
|
||||
class IdentityRef;
|
||||
@@ -86,7 +88,10 @@ public:
|
||||
|
||||
std::vector<Identity> identities() const;
|
||||
|
||||
+ std::optional<SchemaNode> child() const;
|
||||
ChildInstanstiables childInstantiables() const;
|
||||
+ libyang::Collection<SchemaNode, IterationType::Dfs> childrenDfs() const;
|
||||
+ Collection<SchemaNode, IterationType::Sibling> immediateChildren() const;
|
||||
std::vector<SchemaNode> actionRpcs() const;
|
||||
|
||||
std::string printStr(const SchemaOutputFormat format, const std::optional<SchemaPrintFlags> flags = std::nullopt, std::optional<size_t> lineLength = std::nullopt) const;
|
||||
diff --git a/src/Module.cpp b/src/Module.cpp
|
||||
index 4dc9e3b..d6d4023 100644
|
||||
--- a/src/Module.cpp
|
||||
+++ b/src/Module.cpp
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <libyang-cpp/ChildInstantiables.hpp>
|
||||
+#include <libyang-cpp/Collection.hpp>
|
||||
#include <libyang-cpp/Module.hpp>
|
||||
#include <libyang-cpp/Utils.hpp>
|
||||
#include <libyang/libyang.h>
|
||||
@@ -178,6 +179,23 @@ std::vector<Identity> Module::identities() const
|
||||
return res;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Returns the first child node of this module.
|
||||
+ * @return The child, or std::nullopt if there are no children.
|
||||
+ */
|
||||
+std::optional<SchemaNode> Module::child() const
|
||||
+{
|
||||
+ if (!m_module->implemented) {
|
||||
+ throw Error{"Module::child: module is not implemented"};
|
||||
+ }
|
||||
+
|
||||
+ if (!m_module->compiled->data) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
+
|
||||
+ return SchemaNode{m_module->compiled->data, m_ctx};
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Returns a collection of data instantiable top-level nodes of this module.
|
||||
*
|
||||
@@ -191,6 +209,28 @@ ChildInstanstiables Module::childInstantiables() const
|
||||
return ChildInstanstiables{nullptr, m_module->compiled, m_ctx};
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Returns a collection for iterating depth-first over the subtree this module points to.
|
||||
+ */
|
||||
+Collection<SchemaNode, IterationType::Dfs> Module::childrenDfs() const
|
||||
+{
|
||||
+ if (!m_module->implemented) {
|
||||
+ throw Error{"Module::childrenDfs: module is not implemented"};
|
||||
+ }
|
||||
+ return Collection<SchemaNode, IterationType::Dfs>{m_module->compiled->data, m_ctx};
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @brief Returns a collection for iterating over the immediate children of where this module points to.
|
||||
+ *
|
||||
+ * This is a convenience function for iterating over this->child().siblings() which does not throw even when module has no children.
|
||||
+ */
|
||||
+Collection<SchemaNode, IterationType::Sibling> Module::immediateChildren() const
|
||||
+{
|
||||
+ auto c = child();
|
||||
+ return c ? c->siblings() : Collection<SchemaNode, IterationType::Sibling>{nullptr, nullptr};
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Returns a collection of RPC nodes (not action nodes) as SchemaNode
|
||||
*
|
||||
diff --git a/tests/context.cpp b/tests/context.cpp
|
||||
index 5929b75..25343db 100644
|
||||
--- a/tests/context.cpp
|
||||
+++ b/tests/context.cpp
|
||||
@@ -713,6 +713,11 @@ TEST_CASE("context")
|
||||
+--rw anydataWithMandatoryChild anydata
|
||||
+--rw anyxmlBasic? anyxml
|
||||
+--rw anyxmlWithMandatoryChild anyxml
|
||||
+ +--rw (choiceOnModule)?
|
||||
+ | +--:(case1)
|
||||
+ | | +--rw choiceOnModuleLeaf1? string
|
||||
+ | +--:(case2)
|
||||
+ | +--rw choiceOnModuleLeaf2? string
|
||||
+--rw choiceBasicContainer
|
||||
| +--rw (choiceBasic)?
|
||||
| +--:(case1)
|
||||
diff --git a/tests/example_schema.hpp b/tests/example_schema.hpp
|
||||
index ae3b4de..0d8acb9 100644
|
||||
--- a/tests/example_schema.hpp
|
||||
+++ b/tests/example_schema.hpp
|
||||
@@ -390,6 +390,19 @@ module type_module {
|
||||
mandatory true;
|
||||
}
|
||||
|
||||
+ choice choiceOnModule {
|
||||
+ case case1 {
|
||||
+ leaf choiceOnModuleLeaf1 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ case case2 {
|
||||
+ leaf choiceOnModuleLeaf2 {
|
||||
+ type string;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
container choiceBasicContainer {
|
||||
choice choiceBasic {
|
||||
case case1 {
|
||||
@@ -787,6 +800,14 @@ module type_module {
|
||||
}
|
||||
)"s;
|
||||
|
||||
+const auto empty_module = R"(
|
||||
+module empty_module {
|
||||
+ yang-version 1.1;
|
||||
+ namespace "e";
|
||||
+ prefix "e";
|
||||
+}
|
||||
+)"s;
|
||||
+
|
||||
const auto with_inet_types_module = R"(
|
||||
module with-inet-types {
|
||||
yang-version 1.1;
|
||||
diff --git a/tests/schema_node.cpp b/tests/schema_node.cpp
|
||||
index 8d74bd2..0001377 100644
|
||||
--- a/tests/schema_node.cpp
|
||||
+++ b/tests/schema_node.cpp
|
||||
@@ -24,8 +24,10 @@ TEST_CASE("SchemaNode")
|
||||
libyang::ContextOptions::SetPrivParsed | libyang::ContextOptions::NoYangLibrary | libyang::ContextOptions::DisableSearchCwd};
|
||||
ctx->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||
ctx->parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||
+ ctx->parseModule(empty_module, libyang::SchemaFormat::YANG);
|
||||
ctxWithParsed->parseModule(example_schema, libyang::SchemaFormat::YANG);
|
||||
ctxWithParsed->parseModule(type_module, libyang::SchemaFormat::YANG);
|
||||
+ ctxWithParsed->parseModule(empty_module, libyang::SchemaFormat::YANG);
|
||||
|
||||
DOCTEST_SUBCASE("context lifetime")
|
||||
{
|
||||
@@ -74,10 +76,34 @@ TEST_CASE("SchemaNode")
|
||||
REQUIRE(node->schema().path() == "/example-schema:person");
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::child")
|
||||
+ DOCTEST_SUBCASE("child")
|
||||
{
|
||||
- REQUIRE(ctx->findPath("/type_module:listAdvancedWithTwoKey").child()->name() == "first");
|
||||
- REQUIRE(!ctx->findPath("/type_module:leafString").child().has_value());
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("SchemaNode::child")
|
||||
+ {
|
||||
+ REQUIRE(ctx->findPath("/type_module:listAdvancedWithTwoKey").child()->name() == "first");
|
||||
+ REQUIRE(!ctx->findPath("/type_module:leafString").child().has_value());
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Module::child")
|
||||
+ {
|
||||
+ REQUIRE(ctx->getModule("type_module", std::nullopt)->child()->name() == "anydataBasic");
|
||||
+ REQUIRE(!ctx->getModule("empty_module", std::nullopt)->child());
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::child")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->child(), "Module::child: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("SchemaNode::config")
|
||||
@@ -160,162 +186,275 @@ TEST_CASE("SchemaNode")
|
||||
|
||||
DOCTEST_SUBCASE("childInstantiables")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
- std::optional<libyang::ChildInstanstiables> children;
|
||||
-
|
||||
- DOCTEST_SUBCASE("SchemaNode::childInstantiables")
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithOneKey/lol",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey1",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey2",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey3",
|
||||
- "/type_module:listAdvancedWithOneKey/notKey4",
|
||||
- };
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::ChildInstanstiables> children;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("SchemaNode::childInstantiables")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithOneKey/lol",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey1",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey2",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey3",
|
||||
+ "/type_module:listAdvancedWithOneKey/notKey4",
|
||||
+ };
|
||||
+
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithOneKey").childInstantiables();
|
||||
+ }
|
||||
|
||||
- children = ctx->findPath("/type_module:listAdvancedWithOneKey").childInstantiables();
|
||||
- }
|
||||
+ DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ "/type_module:anydataWithMandatoryChild",
|
||||
+ "/type_module:anyxmlBasic",
|
||||
+ "/type_module:anyxmlWithMandatoryChild",
|
||||
+ "/type_module:choiceOnModuleLeaf1",
|
||||
+ "/type_module:choiceOnModuleLeaf2",
|
||||
+ "/type_module:choiceBasicContainer",
|
||||
+ "/type_module:choiceWithMandatoryContainer",
|
||||
+ "/type_module:choiceWithDefaultContainer",
|
||||
+ "/type_module:implicitCaseContainer",
|
||||
+ "/type_module:leafBinary",
|
||||
+ "/type_module:leafBits",
|
||||
+ "/type_module:leafEnum",
|
||||
+ "/type_module:leafEnum2",
|
||||
+ "/type_module:leafNumber",
|
||||
+ "/type_module:leafRef",
|
||||
+ "/type_module:leafRefRelaxed",
|
||||
+ "/type_module:leafString",
|
||||
+ "/type_module:leafUnion",
|
||||
+ "/type_module:meal",
|
||||
+ "/type_module:leafWithConfigFalse",
|
||||
+ "/type_module:leafWithDefaultValue",
|
||||
+ "/type_module:leafWithDescription",
|
||||
+ "/type_module:leafWithMandatoryTrue",
|
||||
+ "/type_module:leafWithStatusDeprecated",
|
||||
+ "/type_module:leafWithStatusObsolete",
|
||||
+ "/type_module:leafWithUnits",
|
||||
+ "/type_module:iid-valid",
|
||||
+ "/type_module:iid-relaxed",
|
||||
+ "/type_module:leafListBasic",
|
||||
+ "/type_module:leafListWithDefault",
|
||||
+ "/type_module:leafListWithMinMaxElements",
|
||||
+ "/type_module:leafListWithUnits",
|
||||
+ "/type_module:listBasic",
|
||||
+ "/type_module:listAdvancedWithOneKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listWithMinMaxElements",
|
||||
+ "/type_module:numeric",
|
||||
+ "/type_module:container",
|
||||
+ "/type_module:containerWithMandatoryChild",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->childInstantiables();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:anydataBasic",
|
||||
- "/type_module:anydataWithMandatoryChild",
|
||||
- "/type_module:anyxmlBasic",
|
||||
- "/type_module:anyxmlWithMandatoryChild",
|
||||
- "/type_module:choiceBasicContainer",
|
||||
- "/type_module:choiceWithMandatoryContainer",
|
||||
- "/type_module:choiceWithDefaultContainer",
|
||||
- "/type_module:implicitCaseContainer",
|
||||
- "/type_module:leafBinary",
|
||||
- "/type_module:leafBits",
|
||||
- "/type_module:leafEnum",
|
||||
- "/type_module:leafEnum2",
|
||||
- "/type_module:leafNumber",
|
||||
- "/type_module:leafRef",
|
||||
- "/type_module:leafRefRelaxed",
|
||||
- "/type_module:leafString",
|
||||
- "/type_module:leafUnion",
|
||||
- "/type_module:meal",
|
||||
- "/type_module:leafWithConfigFalse",
|
||||
- "/type_module:leafWithDefaultValue",
|
||||
- "/type_module:leafWithDescription",
|
||||
- "/type_module:leafWithMandatoryTrue",
|
||||
- "/type_module:leafWithStatusDeprecated",
|
||||
- "/type_module:leafWithStatusObsolete",
|
||||
- "/type_module:leafWithUnits",
|
||||
- "/type_module:iid-valid",
|
||||
- "/type_module:iid-relaxed",
|
||||
- "/type_module:leafListBasic",
|
||||
- "/type_module:leafListWithDefault",
|
||||
- "/type_module:leafListWithMinMaxElements",
|
||||
- "/type_module:leafListWithUnits",
|
||||
- "/type_module:listBasic",
|
||||
- "/type_module:listAdvancedWithOneKey",
|
||||
- "/type_module:listAdvancedWithTwoKey",
|
||||
- "/type_module:listWithMinMaxElements",
|
||||
- "/type_module:numeric",
|
||||
- "/type_module:container",
|
||||
- "/type_module:containerWithMandatoryChild",
|
||||
- };
|
||||
- children = ctx->getModule("type_module", std::nullopt)->childInstantiables();
|
||||
- }
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& child : *children) {
|
||||
+ actualPaths.emplace_back(child.path());
|
||||
+ }
|
||||
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& child : *children) {
|
||||
- actualPaths.emplace_back(child.path());
|
||||
+ REQUIRE(expectedPaths == actualPaths);
|
||||
}
|
||||
|
||||
- REQUIRE(expectedPaths == actualPaths);
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::childInstantiables")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->childInstantiables(), "Module::childInstantiables: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::childrenDfs")
|
||||
+ DOCTEST_SUBCASE("childrenDfs")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
+ {
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::Collection<libyang::SchemaNode, libyang::IterationType::Dfs>> children;
|
||||
|
||||
- const char* path;
|
||||
+ DOCTEST_SUBCASE("SchemaNode::childrenDfs")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey/first",
|
||||
+ "/type_module:listAdvancedWithTwoKey/second",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithTwoKey").childrenDfs();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithTwoKey",
|
||||
- "/type_module:listAdvancedWithTwoKey/first",
|
||||
- "/type_module:listAdvancedWithTwoKey/second",
|
||||
- };
|
||||
+ DOCTEST_SUBCASE("DFS on a leaf")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:leafString",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:leafString").childrenDfs();
|
||||
+ }
|
||||
+ }
|
||||
|
||||
- path = "/type_module:listAdvancedWithTwoKey";
|
||||
- }
|
||||
+ DOCTEST_SUBCASE("Module::childrenDfs")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->childrenDfs();
|
||||
+ }
|
||||
|
||||
- DOCTEST_SUBCASE("DFS on a leaf")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:leafString",
|
||||
- };
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& it : *children) {
|
||||
+ actualPaths.emplace_back(it.path());
|
||||
+ }
|
||||
|
||||
- path = "/type_module:leafString";
|
||||
+ REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& it : ctx->findPath(path).childrenDfs()) {
|
||||
- actualPaths.emplace_back(it.path());
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("Module::childrenDfs")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->childrenDfs(), "Module::childrenDfs: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
}
|
||||
-
|
||||
- REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("SchemaNode::immediateChildren")
|
||||
+ DOCTEST_SUBCASE("immediateChildren")
|
||||
{
|
||||
- std::vector<std::string> expectedPaths;
|
||||
- const char* path;
|
||||
- DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:listAdvancedWithTwoKey/first",
|
||||
- "/type_module:listAdvancedWithTwoKey/second",
|
||||
- };
|
||||
- path = "/type_module:listAdvancedWithTwoKey";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("leaf")
|
||||
+ DOCTEST_SUBCASE("implemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- };
|
||||
- path = "/type_module:leafString";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("no recursion")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/x",
|
||||
- "/type_module:container/y",
|
||||
- "/type_module:container/z",
|
||||
- };
|
||||
- path = "/type_module:container";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("empty container")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- };
|
||||
- path = "/type_module:container/y";
|
||||
- }
|
||||
- DOCTEST_SUBCASE("one item")
|
||||
- {
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/z/z1",
|
||||
- };
|
||||
- path = "/type_module:container/z";
|
||||
+ std::vector<std::string> expectedPaths;
|
||||
+ std::optional<libyang::Collection<libyang::SchemaNode, libyang::IterationType::Sibling>> children;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("SchemaNode::immediateChildren")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("listAdvancedWithTwoKey")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:listAdvancedWithTwoKey/first",
|
||||
+ "/type_module:listAdvancedWithTwoKey/second",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:listAdvancedWithTwoKey").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("leaf")
|
||||
+ {
|
||||
+ expectedPaths = {};
|
||||
+ children = ctx->findPath("/type_module:leafString").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("no recursion")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/x",
|
||||
+ "/type_module:container/y",
|
||||
+ "/type_module:container/z",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("empty container")
|
||||
+ {
|
||||
+ expectedPaths = {};
|
||||
+ children = ctx->findPath("/type_module:container/y").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("one item")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/z/z1",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container/z").immediateChildren();
|
||||
+ }
|
||||
+ DOCTEST_SUBCASE("two items")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:container/x/x1",
|
||||
+ "/type_module:container/x/x2",
|
||||
+ };
|
||||
+ children = ctx->findPath("/type_module:container/x").immediateChildren();
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Module::immediateChildren")
|
||||
+ {
|
||||
+ expectedPaths = {
|
||||
+ "/type_module:anydataBasic",
|
||||
+ "/type_module:anydataWithMandatoryChild",
|
||||
+ "/type_module:anyxmlBasic",
|
||||
+ "/type_module:anyxmlWithMandatoryChild",
|
||||
+ // choiceOnModule is a choice, so it doesn't have path "/type_module:choiceOnModule".
|
||||
+ // This node is tested at the end of the test subcase.
|
||||
+ "/",
|
||||
+ "/type_module:choiceBasicContainer",
|
||||
+ "/type_module:choiceWithMandatoryContainer",
|
||||
+ "/type_module:choiceWithDefaultContainer",
|
||||
+ "/type_module:implicitCaseContainer",
|
||||
+ "/type_module:leafBinary",
|
||||
+ "/type_module:leafBits",
|
||||
+ "/type_module:leafEnum",
|
||||
+ "/type_module:leafEnum2",
|
||||
+ "/type_module:leafNumber",
|
||||
+ "/type_module:leafRef",
|
||||
+ "/type_module:leafRefRelaxed",
|
||||
+ "/type_module:leafString",
|
||||
+ "/type_module:leafUnion",
|
||||
+ "/type_module:meal",
|
||||
+ "/type_module:leafWithConfigFalse",
|
||||
+ "/type_module:leafWithDefaultValue",
|
||||
+ "/type_module:leafWithDescription",
|
||||
+ "/type_module:leafWithMandatoryTrue",
|
||||
+ "/type_module:leafWithStatusDeprecated",
|
||||
+ "/type_module:leafWithStatusObsolete",
|
||||
+ "/type_module:leafWithUnits",
|
||||
+ "/type_module:iid-valid",
|
||||
+ "/type_module:iid-relaxed",
|
||||
+ "/type_module:leafListBasic",
|
||||
+ "/type_module:leafListWithDefault",
|
||||
+ "/type_module:leafListWithMinMaxElements",
|
||||
+ "/type_module:leafListWithUnits",
|
||||
+ "/type_module:listBasic",
|
||||
+ "/type_module:listAdvancedWithOneKey",
|
||||
+ "/type_module:listAdvancedWithTwoKey",
|
||||
+ "/type_module:listWithMinMaxElements",
|
||||
+ "/type_module:numeric",
|
||||
+ "/type_module:container",
|
||||
+ "/type_module:containerWithMandatoryChild",
|
||||
+ };
|
||||
+ children = ctx->getModule("type_module", std::nullopt)->immediateChildren();
|
||||
+
|
||||
+ std::vector<std::string> actualNames;
|
||||
+ for (auto it : children.value()) {
|
||||
+ actualNames.emplace_back(it.name());
|
||||
+ }
|
||||
+ // choiceOnModule is a choice, so it doesn't have path, just name.
|
||||
+ REQUIRE(actualNames[4] == "choiceOnModule");
|
||||
+ }
|
||||
+
|
||||
+ std::vector<std::string> actualPaths;
|
||||
+ for (const auto& it : *children) {
|
||||
+ actualPaths.emplace_back(it.path());
|
||||
+ }
|
||||
+ REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
- DOCTEST_SUBCASE("two items")
|
||||
+
|
||||
+ DOCTEST_SUBCASE("unimplemented module")
|
||||
{
|
||||
- expectedPaths = {
|
||||
- "/type_module:container/x/x1",
|
||||
- "/type_module:container/x/x2",
|
||||
- };
|
||||
- path = "/type_module:container/x";
|
||||
- }
|
||||
- std::vector<std::string> actualPaths;
|
||||
- for (const auto& it : ctx->findPath(path).immediateChildren()) {
|
||||
- actualPaths.emplace_back(it.path());
|
||||
+ DOCTEST_SUBCASE("Module::immediateChildren")
|
||||
+ {
|
||||
+ ctx->setSearchDir(TESTS_DIR / "yang");
|
||||
+ auto modYangPatch = ctx->loadModule("ietf-yang-patch", std::nullopt);
|
||||
+ auto modRestconf = ctx->getModule("ietf-restconf", "2017-01-26");
|
||||
+ REQUIRE(!modRestconf->implemented());
|
||||
+ REQUIRE_THROWS_WITH_AS(modRestconf->immediateChildren(), "Module::child: module is not implemented", libyang::Error);
|
||||
+ }
|
||||
}
|
||||
- REQUIRE(actualPaths == expectedPaths);
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("SchemaNode::siblings")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
From 39c7530caa510144c17521278b721ba1e6d8ff40 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Thu, 9 Jan 2025 15:31:37 +0100
|
||||
Subject: [PATCH 8/8] upstream stopped reporting schema-mounts node
|
||||
Subject: [PATCH 09/18] upstream stopped reporting schema-mounts node
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I940769d38d56fcfda3e1408c92331fdb00c161e9
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
@@ -0,0 +1,39 @@
|
||||
From 82c963766ad4e4a802db7be656acbedb640745e9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 30 Jan 2025 16:34:12 +0100
|
||||
Subject: [PATCH 10/18] CI: temporarily pin version due to anyxml behavior
|
||||
changes upstream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Id977f4d045098c1b93656c0efb871c9a1b650e2d
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index b41c490..19f0def 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -4,13 +4,13 @@
|
||||
- f38-gcc-cover:
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-29
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-29
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
From 50a216e35a555961f94a32a71bb2d45ac611d0aa Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 29 Jan 2025 22:49:08 +0100
|
||||
Subject: [PATCH 11/18] build: a single place to define package version
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I2cd7397895ed4852f852e99b97543dde76eaff8f
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 732f52b..c518ca8 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -21,7 +21,8 @@ add_custom_target(libyang-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-prepare_git_version(LIBYANG_CPP_VERSION "3")
|
||||
+set(LIBYANG_CPP_PKG_VERSION "3")
|
||||
+prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${DOXYGEN_FOUND})
|
||||
@@ -29,7 +30,6 @@ option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a st
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.8 IMPORTED_TARGET)
|
||||
-set(LIBYANG_CPP_PKG_VERSION "3")
|
||||
|
||||
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||
find_package(date)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
From 1533458346b4f395b1187c646b61bbcb1fddc615 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Mon, 4 Nov 2024 14:52:12 +0100
|
||||
Subject: [PATCH 12/18] YANG-flavored regular expressions
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I93b2756d0f470585280c076308df3f384bd7765d
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 3 +++
|
||||
include/libyang-cpp/Regex.hpp | 28 ++++++++++++++++++++++
|
||||
src/Regex.cpp | 45 +++++++++++++++++++++++++++++++++++
|
||||
tests/regex.cpp | 30 +++++++++++++++++++++++
|
||||
4 files changed, 106 insertions(+)
|
||||
create mode 100644 include/libyang-cpp/Regex.hpp
|
||||
create mode 100644 src/Regex.cpp
|
||||
create mode 100644 tests/regex.cpp
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c518ca8..512af8c 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -54,6 +54,7 @@ add_library(yang-cpp
|
||||
src/Enum.cpp
|
||||
src/Collection.cpp
|
||||
src/Module.cpp
|
||||
+ src/Regex.cpp
|
||||
src/SchemaNode.cpp
|
||||
src/Set.cpp
|
||||
src/Type.cpp
|
||||
@@ -83,6 +84,7 @@ if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.24")
|
||||
libyang-cpp/Enum.hpp
|
||||
libyang-cpp/ChildInstantiables.hpp
|
||||
libyang-cpp/Module.hpp
|
||||
+ libyang-cpp/Regex.hpp
|
||||
libyang-cpp/Set.hpp
|
||||
libyang-cpp/SchemaNode.hpp
|
||||
libyang-cpp/Time.hpp
|
||||
@@ -119,6 +121,7 @@ if(BUILD_TESTING)
|
||||
libyang_cpp_test(schema_node)
|
||||
libyang_cpp_test(unsafe)
|
||||
target_link_libraries(test_unsafe PkgConfig::LIBYANG)
|
||||
+ libyang_cpp_test(regex)
|
||||
|
||||
if(date_FOUND)
|
||||
add_executable(test_time-stl-hhdate tests/time.cpp)
|
||||
diff --git a/include/libyang-cpp/Regex.hpp b/include/libyang-cpp/Regex.hpp
|
||||
new file mode 100644
|
||||
index 0000000..31935f2
|
||||
--- /dev/null
|
||||
+++ b/include/libyang-cpp/Regex.hpp
|
||||
@@ -0,0 +1,28 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+#pragma once
|
||||
+#include <libyang-cpp/export.h>
|
||||
+#include <string>
|
||||
+
|
||||
+namespace libyang {
|
||||
+class Context;
|
||||
+
|
||||
+/**
|
||||
+ * @brief A regular expression pattern which uses the YANG-flavored regex engine
|
||||
+ */
|
||||
+class LIBYANG_CPP_EXPORT Regex {
|
||||
+public:
|
||||
+ Regex(const std::string& pattern);
|
||||
+ ~Regex();
|
||||
+ bool matches(const std::string& input);
|
||||
+
|
||||
+private:
|
||||
+ void* code;
|
||||
+};
|
||||
+
|
||||
+}
|
||||
diff --git a/src/Regex.cpp b/src/Regex.cpp
|
||||
new file mode 100644
|
||||
index 0000000..a34fcd5
|
||||
--- /dev/null
|
||||
+++ b/src/Regex.cpp
|
||||
@@ -0,0 +1,45 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+
|
||||
+// The following header MUST be included before anything else which "might" use PCRE2
|
||||
+// because that library uses the preprocessor to prepare the "correct" versions of symbols.
|
||||
+#include <libyang/tree_data.h>
|
||||
+
|
||||
+#include <libyang-cpp/Regex.hpp>
|
||||
+#include <pcre2.h>
|
||||
+#include "utils/exception.hpp"
|
||||
+
|
||||
+#define THE_PCRE2_CODE_P reinterpret_cast<pcre2_code*>(this->code)
|
||||
+#define THE_PCRE2_CODE_P_P reinterpret_cast<pcre2_code**>(&this->code)
|
||||
+
|
||||
+namespace libyang {
|
||||
+
|
||||
+Regex::Regex(const std::string& pattern)
|
||||
+ : code(nullptr)
|
||||
+{
|
||||
+ auto res = ly_pattern_compile(nullptr, pattern.c_str(), THE_PCRE2_CODE_P_P);
|
||||
+ throwIfError(res, ly_last_logmsg());
|
||||
+}
|
||||
+
|
||||
+Regex::~Regex()
|
||||
+{
|
||||
+ pcre2_code_free(THE_PCRE2_CODE_P);
|
||||
+}
|
||||
+
|
||||
+bool Regex::matches(const std::string& input)
|
||||
+{
|
||||
+ auto res = ly_pattern_match(nullptr, nullptr /* we have a precompiled pattern */, input.c_str(), input.size(), THE_PCRE2_CODE_P_P);
|
||||
+ if (res == LY_SUCCESS) {
|
||||
+ return true;
|
||||
+ } else if (res == LY_ENOT) {
|
||||
+ return false;
|
||||
+ } else {
|
||||
+ throwError(res, ly_last_logmsg());
|
||||
+ }
|
||||
+}
|
||||
+}
|
||||
diff --git a/tests/regex.cpp b/tests/regex.cpp
|
||||
new file mode 100644
|
||||
index 0000000..7594f43
|
||||
--- /dev/null
|
||||
+++ b/tests/regex.cpp
|
||||
@@ -0,0 +1,30 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2025 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ *
|
||||
+ * SPDX-License-Identifier: BSD-3-Clause
|
||||
+ */
|
||||
+
|
||||
+#include <doctest/doctest.h>
|
||||
+#include <libyang-cpp/Regex.hpp>
|
||||
+#include <libyang-cpp/Utils.hpp>
|
||||
+
|
||||
+TEST_CASE("regex")
|
||||
+{
|
||||
+ using libyang::Regex;
|
||||
+ using namespace std::string_literals;
|
||||
+
|
||||
+ REQUIRE_THROWS_WITH_AS(Regex{"\\"}, R"(Regular expression "\" is not valid ("": \ at end of pattern).: LY_EVALID)", libyang::ErrorWithCode);
|
||||
+
|
||||
+ Regex re{"ahoj"};
|
||||
+ REQUIRE(re.matches("ahoj"));
|
||||
+ REQUIRE(!re.matches("cau"));
|
||||
+ REQUIRE(re.matches("ahoj")); // test repeated calls as well
|
||||
+ REQUIRE(!re.matches("oj"));
|
||||
+ REQUIRE(!re.matches("aho"));
|
||||
+
|
||||
+ // Testing runtime errors during pattern *matching* is tricky. There's a limit on backtracking,
|
||||
+ // so testing a pattern like x+x+y on an obscenely long string of "x" characters *will* do the trick, eventually,
|
||||
+ // but the PCRE2 library has a default limit of 10M attempts. That's a VERY big number to hit during a test :(.
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
From 8d406728a53c2e77a4fe7393b7e30d42b8f9b9bb Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 30 Jan 2025 15:40:22 +0100
|
||||
Subject: [PATCH 13/18] Adapt to upstream changes in anyxml JSON printing
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5f6de28cebc95a446549017c2768b450f4fd6526
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
CMakeLists.txt | 3 ++-
|
||||
tests/data_node.cpp | 2 +-
|
||||
3 files changed, 5 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index 19f0def..b41c490 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -4,13 +4,13 @@
|
||||
- f38-gcc-cover:
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: cesnet/2025-01-29
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
- override-checkout: cesnet/2025-01-29
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 512af8c..a40fd52 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -29,7 +29,8 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
-pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.8 IMPORTED_TARGET)
|
||||
+# FIXME: it's actually 3.7.12, but that hasn't been released yet
|
||||
+pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.11 IMPORTED_TARGET)
|
||||
|
||||
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||
find_package(date)
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index 45fd6c1..14470dd 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1568,7 +1568,7 @@ TEST_CASE("Data Node manipulation")
|
||||
REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||
== R"|({"example-schema:ax":[1,2,3]})|"s);
|
||||
REQUIRE(*jsonAnyXmlNode.createdNode->printStr(libyang::DataFormat::XML, libyang::PrintFlags::Shrink | libyang::PrintFlags::WithSiblings)
|
||||
- == R"|(<ax xmlns="http://example.com/coze"/>)|"s);
|
||||
+ == R"|(<ax xmlns="http://example.com/coze">)|"s + origJSON + "</ax>");
|
||||
}
|
||||
|
||||
REQUIRE(!!val);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
From f050e7e4a17ef2e221ca000a544042c33c9541fc Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 19:21:26 +0100
|
||||
Subject: [PATCH 14/18] fix DataNode::insertSibling() return value
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
When such an insert happens, the C library returns the node which is now
|
||||
the first sibling among all of the siblings of the node which was used
|
||||
as a reference during the insert. Our API was also documented this way,
|
||||
but we were not doing that.
|
||||
|
||||
Reported-by: Irfan <irfan.haslanded@gmail.com>
|
||||
Bug: https://github.com/CESNET/libyang-cpp/issues/29
|
||||
Change-Id: Id7f84a31e50212d6e2cbce9ab03a351a3721f767
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/DataNode.cpp | 2 +-
|
||||
tests/data_node.cpp | 7 +++++++
|
||||
2 files changed, 8 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index 84591e5..b899b18 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -711,7 +711,7 @@ DataNode DataNode::insertSibling(DataNode toInsert)
|
||||
lyd_insert_sibling(this->m_node, toInsert.m_node, &firstSibling);
|
||||
}, toInsert.parent() ? OperationScope::JustThisNode : OperationScope::AffectsFollowingSiblings, m_refs);
|
||||
|
||||
- return DataNode{m_node, m_refs};
|
||||
+ return DataNode{firstSibling, m_refs};
|
||||
}
|
||||
|
||||
/**
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index 14470dd..b6ee455 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -973,6 +973,13 @@ TEST_CASE("Data Node manipulation")
|
||||
REQUIRE(getNumberOrder() == expected);
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("DataNode::insertSibling")
|
||||
+ {
|
||||
+ auto node = ctx.newPath("/example-schema:leafUInt8", "10");
|
||||
+ REQUIRE(node.insertSibling(ctx.newPath("/example-schema:leafUInt16", "10")).path() == "/example-schema:leafUInt8");
|
||||
+ REQUIRE(node.insertSibling(ctx.newPath("/example-schema:dummy", "10")).path() == "/example-schema:dummy");
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("DataNode::duplicate")
|
||||
{
|
||||
auto root = ctx.parseData(data2, libyang::DataFormat::JSON);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
From f958af42bf5d9fbd901ed59ebc1359ac0ddcc00f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 11:36:50 +0100
|
||||
Subject: [PATCH 15/18] API/ABI change: opaque node naming
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Our C++ API would ignore the "module name or XML prefix", which turns
|
||||
out to be *the* relevant part when it comes to opaque node naming. The
|
||||
prefix is, instead, just that string that might have been inherited from
|
||||
the parent node when parsing the serialized data; it's an optional
|
||||
thingy which, if not set explicitly, is implicitly inherited.
|
||||
|
||||
Adapt the API for this, and since this *will* break the build, let's
|
||||
bump the package version.
|
||||
|
||||
Change-Id: I199afe5fa7a571034b744531c63b93b9c656563a
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 5 ++---
|
||||
include/libyang-cpp/Context.hpp | 4 ++--
|
||||
include/libyang-cpp/DataNode.hpp | 11 ++++++++--
|
||||
src/Context.cpp | 32 ++++++++++++++++++++--------
|
||||
src/DataNode.cpp | 19 ++++++++++++++---
|
||||
tests/data_node.cpp | 36 ++++++++++++++++++++++++++------
|
||||
6 files changed, 82 insertions(+), 25 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index a40fd52..c5fec45 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -21,7 +21,7 @@ add_custom_target(libyang-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-set(LIBYANG_CPP_PKG_VERSION "3")
|
||||
+set(LIBYANG_CPP_PKG_VERSION "4")
|
||||
prepare_git_version(LIBYANG_CPP_VERSION ${LIBYANG_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
@@ -29,8 +29,7 @@ option(WITH_DOCS "Create and install internal documentation (needs Doxygen)" ${D
|
||||
option(BUILD_SHARED_LIBS "By default, shared libs are enabled. Turn off for a static build." ON)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
-# FIXME: it's actually 3.7.12, but that hasn't been released yet
|
||||
-pkg_check_modules(LIBYANG REQUIRED libyang>=3.7.11 IMPORTED_TARGET)
|
||||
+pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET)
|
||||
|
||||
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||
find_package(date)
|
||||
diff --git a/include/libyang-cpp/Context.hpp b/include/libyang-cpp/Context.hpp
|
||||
index baa47b3..ca89063 100644
|
||||
--- a/include/libyang-cpp/Context.hpp
|
||||
+++ b/include/libyang-cpp/Context.hpp
|
||||
@@ -115,8 +115,8 @@ public:
|
||||
CreatedNodes newPath2(const std::string& path, libyang::JSON json, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
CreatedNodes newPath2(const std::string& path, libyang::XML xml, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
std::optional<DataNode> newExtPath(const ExtensionInstance& ext, const std::string& path, const std::optional<std::string>& value, const std::optional<CreationOptions> options = std::nullopt) const;
|
||||
- std::optional<DataNode> newOpaqueJSON(const std::string& moduleName, const std::string& name, const std::optional<libyang::JSON>& value) const;
|
||||
- std::optional<DataNode> newOpaqueXML(const std::string& moduleName, const std::string& name, const std::optional<libyang::XML>& value) const;
|
||||
+ std::optional<DataNode> newOpaqueJSON(const OpaqueName& name, const std::optional<libyang::JSON>& value) const;
|
||||
+ std::optional<DataNode> newOpaqueXML(const OpaqueName& name, const std::optional<libyang::XML>& value) const;
|
||||
SchemaNode findPath(const std::string& dataPath, const InputOutputNodes inputOutputNodes = InputOutputNodes::Input) const;
|
||||
Set<SchemaNode> findXPath(const std::string& path) const;
|
||||
|
||||
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||
index 851681b..310b5e3 100644
|
||||
--- a/include/libyang-cpp/DataNode.hpp
|
||||
+++ b/include/libyang-cpp/DataNode.hpp
|
||||
@@ -225,15 +225,22 @@ private:
|
||||
};
|
||||
|
||||
/**
|
||||
- * @brief Contains a (possibly module-qualified) name of an opaque node.
|
||||
+ * @brief Contains a name of an opaque node.
|
||||
*
|
||||
- * This is generic container of a prefix/module string and a name string.
|
||||
+ * An opaque node always has a name, and a module (or XML namespace) to which this node belongs.
|
||||
+ * Sometimes, it also has a prefix.
|
||||
+ *
|
||||
+ * If the prefix is set *and* the underlying node is an opaque JSON one, then the prefix must be the same as the "module or namespace" name.
|
||||
+ * If the underlying node is an opaque XML one, then the XML prefix might be something completely different, and in that case the real fun begins.
|
||||
+ * Review the libayng C manual, this is something that the C++ wrapper doesn't really have under control.
|
||||
*
|
||||
* Wraps `ly_opaq_name`.
|
||||
*/
|
||||
struct LIBYANG_CPP_EXPORT OpaqueName {
|
||||
+ std::string moduleOrNamespace;
|
||||
std::optional<std::string> prefix;
|
||||
std::string name;
|
||||
+ std::string pretty() const;
|
||||
};
|
||||
|
||||
/**
|
||||
diff --git a/src/Context.cpp b/src/Context.cpp
|
||||
index 287f8c8..fec2f27 100644
|
||||
--- a/src/Context.cpp
|
||||
+++ b/src/Context.cpp
|
||||
@@ -378,17 +378,25 @@ std::optional<DataNode> Context::newExtPath(const ExtensionInstance& ext, const
|
||||
*
|
||||
* Wraps `lyd_new_opaq`.
|
||||
*
|
||||
- * @param moduleName Node module name, used as a prefix as well
|
||||
* @param name Name of the created node
|
||||
* @param value JSON data blob, if any
|
||||
* @return Returns the newly created node (if created)
|
||||
*/
|
||||
-std::optional<DataNode> Context::newOpaqueJSON(const std::string& moduleName, const std::string& name, const std::optional<libyang::JSON>& value) const
|
||||
+std::optional<DataNode> Context::newOpaqueJSON(const OpaqueName& name, const std::optional<libyang::JSON>& value) const
|
||||
{
|
||||
+ if (name.prefix && *name.prefix != name.moduleOrNamespace) {
|
||||
+ throw Error{"invalid opaque JSON node: prefix \"" + *name.prefix + "\" doesn't match module name \"" + name.moduleOrNamespace + "\""};
|
||||
+ }
|
||||
lyd_node* out;
|
||||
- auto err = lyd_new_opaq(nullptr, m_ctx.get(), name.c_str(), value ? value->content.c_str() : nullptr, nullptr, moduleName.c_str(), &out);
|
||||
+ auto err = lyd_new_opaq(nullptr,
|
||||
+ m_ctx.get(),
|
||||
+ name.name.c_str(),
|
||||
+ value ? value->content.c_str() : nullptr,
|
||||
+ name.prefix ? name.prefix->c_str() : nullptr,
|
||||
+ name.moduleOrNamespace.c_str(),
|
||||
+ &out);
|
||||
|
||||
- throwIfError(err, "Couldn't create an opaque JSON node '"s + moduleName + ':' + name + "'");
|
||||
+ throwIfError(err, "Couldn't create an opaque JSON node " + name.pretty());
|
||||
|
||||
if (out) {
|
||||
return DataNode{out, std::make_shared<internal_refcount>(m_ctx)};
|
||||
@@ -403,17 +411,23 @@ std::optional<DataNode> Context::newOpaqueJSON(const std::string& moduleName, co
|
||||
*
|
||||
* Wraps `lyd_new_opaq2`.
|
||||
*
|
||||
- * @param xmlNamespace Node module namespace
|
||||
* @param name Name of the created node
|
||||
* @param value XML data blob, if any
|
||||
* @return Returns the newly created node (if created)
|
||||
*/
|
||||
-std::optional<DataNode> Context::newOpaqueXML(const std::string& xmlNamespace, const std::string& name, const std::optional<libyang::XML>& value) const
|
||||
+std::optional<DataNode> Context::newOpaqueXML(const OpaqueName& name, const std::optional<libyang::XML>& value) const
|
||||
{
|
||||
+ // the XML node naming is "complex", we cannot really check the XML namespace for sanity here
|
||||
lyd_node* out;
|
||||
- auto err = lyd_new_opaq2(nullptr, m_ctx.get(), name.c_str(), value ? value->content.c_str() : nullptr, nullptr, xmlNamespace.c_str(), &out);
|
||||
-
|
||||
- throwIfError(err, "Couldn't create an opaque XML node '"s + name +"' from namespace '" + xmlNamespace + "'");
|
||||
+ auto err = lyd_new_opaq2(nullptr,
|
||||
+ m_ctx.get(),
|
||||
+ name.name.c_str(),
|
||||
+ value ? value->content.c_str() : nullptr,
|
||||
+ name.prefix ? name.prefix->c_str() : nullptr,
|
||||
+ name.moduleOrNamespace.c_str(),
|
||||
+ &out);
|
||||
+
|
||||
+ throwIfError(err, "Couldn't create an opaque XML node " + name.pretty());
|
||||
|
||||
if (out) {
|
||||
return DataNode{out, std::make_shared<internal_refcount>(m_ctx)};
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index b899b18..344f1b6 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -1112,9 +1112,9 @@ OpaqueName DataNodeOpaque::name() const
|
||||
{
|
||||
auto opaq = reinterpret_cast<lyd_node_opaq*>(m_node);
|
||||
return OpaqueName{
|
||||
- .prefix = opaq->name.prefix ? std::optional(opaq->name.prefix) : std::nullopt,
|
||||
- .name = opaq->name.name
|
||||
- };
|
||||
+ .moduleOrNamespace = opaq->name.module_name,
|
||||
+ .prefix = opaq->name.prefix ? std::optional{opaq->name.prefix} : std::nullopt,
|
||||
+ .name = opaq->name.name};
|
||||
}
|
||||
|
||||
std::string DataNodeOpaque::value() const
|
||||
@@ -1122,6 +1122,19 @@ std::string DataNodeOpaque::value() const
|
||||
return reinterpret_cast<lyd_node_opaq*>(m_node)->value;
|
||||
}
|
||||
|
||||
+std::string OpaqueName::pretty() const
|
||||
+{
|
||||
+ if (prefix) {
|
||||
+ if (*prefix == moduleOrNamespace) {
|
||||
+ return *prefix + ':' + name;
|
||||
+ } else {
|
||||
+ return "{" + moduleOrNamespace + "}, " + *prefix + ':' + name;
|
||||
+ }
|
||||
+ } else {
|
||||
+ return "{" + moduleOrNamespace + "}, " + name;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* Wraps a raw non-null lyd_node pointer.
|
||||
* @param node The pointer to be wrapped. Must not be null.
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index b6ee455..a1096a6 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1969,9 +1969,11 @@ TEST_CASE("Data Node manipulation")
|
||||
|
||||
DOCTEST_SUBCASE("opaque nodes for sysrepo ops data discard")
|
||||
{
|
||||
- auto discard1 = ctx.newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{"/example-schema:a"});
|
||||
+ // the "short" form with no prefix
|
||||
+ auto discard1 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", std::nullopt, "discard-items"}, libyang::JSON{"/example-schema:a"});
|
||||
REQUIRE(!!discard1);
|
||||
- auto discard2 = ctx.newOpaqueJSON("sysrepo", "discard-items", libyang::JSON{"/example-schema:b"});
|
||||
+ // let's use a prefix form here
|
||||
+ auto discard2 = ctx.newOpaqueJSON(libyang::OpaqueName{"sysrepo", "sysrepo", "discard-items"}, libyang::JSON{"/example-schema:b"});
|
||||
REQUIRE(!!discard2);
|
||||
discard1->insertSibling(*discard2);
|
||||
REQUIRE(*discard1->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||
@@ -2001,16 +2003,38 @@ TEST_CASE("Data Node manipulation")
|
||||
auto data = ctx.newPath2("/example-schema:myRpc/outputLeaf", "AHOJ", libyang::CreationOptions::Output).createdNode;
|
||||
REQUIRE(data);
|
||||
data->newPath("/example-schema:myRpc/another", "yay", libyang::CreationOptions::Output);
|
||||
+ std::string prettyName;
|
||||
|
||||
- DOCTEST_SUBCASE("JSON") {
|
||||
- out = ctx.newOpaqueJSON(data->schema().module().name(), "output", std::nullopt);
|
||||
+ DOCTEST_SUBCASE("JSON no prefix") {
|
||||
+ out = ctx.newOpaqueJSON({data->schema().module().name(), std::nullopt, "output"}, std::nullopt);
|
||||
+ prettyName = "{example-schema}, output";
|
||||
}
|
||||
|
||||
- DOCTEST_SUBCASE("XML") {
|
||||
- out = ctx.newOpaqueXML(data->schema().module().ns(), "output", std::nullopt);
|
||||
+ DOCTEST_SUBCASE("JSON with prefix") {
|
||||
+ out = ctx.newOpaqueJSON({data->schema().module().name(), data->schema().module().name(), "output"}, std::nullopt);
|
||||
+ prettyName = "example-schema:output";
|
||||
+
|
||||
+ // wrong prefix is detected
|
||||
+ REQUIRE_THROWS_WITH_AS(ctx.newOpaqueJSON({data->schema().module().name(), "xxx", "output"}, std::nullopt),
|
||||
+ R"(invalid opaque JSON node: prefix "xxx" doesn't match module name "example-schema")",
|
||||
+ libyang::Error);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("XML no prefix") {
|
||||
+ out = ctx.newOpaqueXML({data->schema().module().ns(), std::nullopt, "output"}, std::nullopt);
|
||||
+ prettyName = "{http://example.com/coze}, output";
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("XML with prefix") {
|
||||
+ out = ctx.newOpaqueXML({data->schema().module().ns(),
|
||||
+ data->schema().module().name() /* prefix is a module name, not the XML NS*/,
|
||||
+ "output"},
|
||||
+ std::nullopt);
|
||||
+ prettyName = "{http://example.com/coze}, example-schema:output";
|
||||
}
|
||||
|
||||
REQUIRE(out);
|
||||
+ REQUIRE(prettyName == out->asOpaque().name().pretty());
|
||||
data->unlinkWithSiblings();
|
||||
out->insertChild(*data);
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,462 @@
|
||||
From 8c59ecc5c687f8ee2ce62825835a378b422185f2 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 13:41:01 +0100
|
||||
Subject: [PATCH 16/18] Add a helper for finding opaque nodes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is needed for sysrepo where we want to search through the
|
||||
sysrepo:discard-items top-level opaque nodes.
|
||||
|
||||
At first I wanted to simply wrap lyd_find_sibling_opaq_next(), but its
|
||||
semantics is quite different to what is needed in the code which uses
|
||||
sysrepo, so here's my attempt at a nice-ish API.
|
||||
|
||||
Change-Id: I2571961e42f6d7a121e27c881cacdcfec0e87762
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/libyang-cpp/DataNode.hpp | 2 +
|
||||
src/DataNode.cpp | 49 ++++++
|
||||
tests/data_node.cpp | 74 +++++++++
|
||||
tests/yang/sysrepo@2024-10-25.yang | 257 +++++++++++++++++++++++++++++
|
||||
4 files changed, 382 insertions(+)
|
||||
create mode 100644 tests/yang/sysrepo@2024-10-25.yang
|
||||
|
||||
diff --git a/include/libyang-cpp/DataNode.hpp b/include/libyang-cpp/DataNode.hpp
|
||||
index 310b5e3..50d6c0e 100644
|
||||
--- a/include/libyang-cpp/DataNode.hpp
|
||||
+++ b/include/libyang-cpp/DataNode.hpp
|
||||
@@ -102,6 +102,7 @@ public:
|
||||
|
||||
bool isOpaque() const;
|
||||
DataNodeOpaque asOpaque() const;
|
||||
+ std::optional<DataNodeOpaque> firstOpaqueSibling() const;
|
||||
|
||||
// TODO: allow setting the `parent` argument
|
||||
DataNode duplicate(const std::optional<DuplicationOptions> opts = std::nullopt) const;
|
||||
@@ -241,6 +242,7 @@ struct LIBYANG_CPP_EXPORT OpaqueName {
|
||||
std::optional<std::string> prefix;
|
||||
std::string name;
|
||||
std::string pretty() const;
|
||||
+ bool matches(const std::string& prefixIsh, const std::string& name) const;
|
||||
};
|
||||
|
||||
/**
|
||||
diff --git a/src/DataNode.cpp b/src/DataNode.cpp
|
||||
index 344f1b6..7e87917 100644
|
||||
--- a/src/DataNode.cpp
|
||||
+++ b/src/DataNode.cpp
|
||||
@@ -1135,6 +1135,20 @@ std::string OpaqueName::pretty() const
|
||||
}
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @short Fuzzy-match a real-world name against a combination of "something like a prefix" and "unqualified name"
|
||||
+ *
|
||||
+ * Because libyang doesn't propagate inherited prefixes, and because opaque nodes are magic, we seem to require
|
||||
+ * this "fuzzy matching". It won't properly report a match on opaque nodes with a prefix that's inherited when
|
||||
+ * using XML namespaces, though.
|
||||
+ * */
|
||||
+bool OpaqueName::matches(const std::string& prefixIsh, const std::string& name) const
|
||||
+{
|
||||
+ return name == this->name
|
||||
+ && (prefixIsh == moduleOrNamespace
|
||||
+ || (!!prefix && prefixIsh == *prefix));
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* Wraps a raw non-null lyd_node pointer.
|
||||
* @param node The pointer to be wrapped. Must not be null.
|
||||
@@ -1305,4 +1319,39 @@ bool DataNode::siblingsEqual(const libyang::DataNode& other, const DataCompare f
|
||||
}
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @short Find the first opaque node among the siblings
|
||||
+ *
|
||||
+ * This function was inspired by `lyd_find_sibling_opaq_next()`.
|
||||
+ * */
|
||||
+std::optional<DataNodeOpaque> DataNode::firstOpaqueSibling() const
|
||||
+{
|
||||
+ struct lyd_node *candidate = m_node;
|
||||
+
|
||||
+ // Skip all non-opaque nodes; libyang guarantees to have them first, followed by a (possibly empty) set
|
||||
+ // of opaque nodes. This is not documented anywhere, but it was explicitly confirmed by the maintainer:
|
||||
+ //
|
||||
+ // JK: can I rely on all non-opaque nodes being listed first among the siblings, and then all opaque nodes
|
||||
+ // in one continuous sequence (but with an unspecified order among the opaque nodes themselves)?
|
||||
+ //
|
||||
+ // MV: yep
|
||||
+ while (candidate && candidate->schema) {
|
||||
+ candidate = candidate->next;
|
||||
+ }
|
||||
+
|
||||
+ // walk back through the opaque nodes; however, libyang lists are not your regular linked lists
|
||||
+ while (candidate
|
||||
+ && !candidate->prev->schema // don't go from the first opaque node through the non-opaque ones
|
||||
+ && candidate->prev->next // don't wrap from the first node to the last one in case all of them are opaque
|
||||
+ ) {
|
||||
+ candidate = candidate->prev;
|
||||
+ }
|
||||
+
|
||||
+ if (candidate) {
|
||||
+ return DataNode{candidate, m_refs}.asOpaque();
|
||||
+ }
|
||||
+
|
||||
+ return std::nullopt;
|
||||
+}
|
||||
+
|
||||
}
|
||||
diff --git a/tests/data_node.cpp b/tests/data_node.cpp
|
||||
index a1096a6..db5a28e 100644
|
||||
--- a/tests/data_node.cpp
|
||||
+++ b/tests/data_node.cpp
|
||||
@@ -1982,6 +1982,80 @@ TEST_CASE("Data Node manipulation")
|
||||
"sysrepo:discard-items": "/example-schema:b"
|
||||
}
|
||||
)");
|
||||
+
|
||||
+ // check that a list which only consists of opaque nodes doesn't break our iteration
|
||||
+ REQUIRE(!!discard1->firstOpaqueSibling());
|
||||
+ REQUIRE(*discard1->firstOpaqueSibling() == *discard1);
|
||||
+ REQUIRE(!!discard2->firstOpaqueSibling());
|
||||
+ REQUIRE(*discard2->firstOpaqueSibling() == *discard1);
|
||||
+
|
||||
+ auto leafInt16 = ctx.newPath("/example-schema:leafInt16", "666");
|
||||
+ leafInt16.insertSibling(*discard1);
|
||||
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||
+ == R"({
|
||||
+ "example-schema:leafInt16": 666,
|
||||
+ "sysrepo:discard-items": "/example-schema:a",
|
||||
+ "sysrepo:discard-items": "/example-schema:b"
|
||||
+}
|
||||
+)");
|
||||
+ REQUIRE(leafInt16.firstSibling().path() == "/example-schema:leafInt16");
|
||||
+ REQUIRE(discard1->firstSibling().path() == "/example-schema:leafInt16");
|
||||
+ REQUIRE(discard2->firstSibling().path() == "/example-schema:leafInt16");
|
||||
+
|
||||
+ auto dummy = ctx.newPath("/example-schema:dummy", "blah");
|
||||
+ auto opaqueLeaf = ctx.newPath("/example-schema:leafInt32", std::nullopt, libyang::CreationOptions::Opaque);
|
||||
+ opaqueLeaf.newAttrOpaqueJSON("ietf-netconf", "operation", "delete");
|
||||
+ dummy.insertSibling(opaqueLeaf);
|
||||
+
|
||||
+ // FIXME reword this: this one might not be handled by sysrepo, but we want it for our fuzzy matcher testing anyway
|
||||
+ auto discard3 = ctx.newOpaqueXML(libyang::OpaqueName{"http://www.sysrepo.org/yang/sysrepo", "sysrepo", "discard-items"}, libyang::XML{"/example-schema:c"});
|
||||
+ REQUIRE(!!discard3);
|
||||
+ // notice that it's printed without a proper prefix at first...
|
||||
+ REQUIRE(*discard3->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink)
|
||||
+ == R"({"discard-items":"/example-schema:c"})");
|
||||
+ // ...but after loading the module, the proper module is added back
|
||||
+ ctx.parseModule(TESTS_DIR / "yang" / "sysrepo@2024-10-25.yang", libyang::SchemaFormat::YANG);
|
||||
+ REQUIRE(*discard3->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::Shrink)
|
||||
+ == R"({"sysrepo:discard-items":"/example-schema:c"})");
|
||||
+
|
||||
+ dummy.insertSibling(*discard3);
|
||||
+ leafInt16.insertSibling(dummy);
|
||||
+ REQUIRE(*discard1->firstSibling().printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings)
|
||||
+ == R"({
|
||||
+ "example-schema:dummy": "blah",
|
||||
+ "example-schema:leafInt16": 666,
|
||||
+ "sysrepo:discard-items": "/example-schema:a",
|
||||
+ "sysrepo:discard-items": "/example-schema:b",
|
||||
+ "example-schema:leafInt32": "",
|
||||
+ "@example-schema:leafInt32": {
|
||||
+ "ietf-netconf:operation": "delete"
|
||||
+ },
|
||||
+ "sysrepo:discard-items": "/example-schema:c"
|
||||
+}
|
||||
+)");
|
||||
+
|
||||
+ REQUIRE(dummy.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(dummy.firstOpaqueSibling() != discard2);
|
||||
+ REQUIRE(leafInt16.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(opaqueLeaf.firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard1->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard2->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard3->firstOpaqueSibling() == discard1);
|
||||
+ REQUIRE(discard1->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(!discard1->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard2->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(!discard2->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard3->asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+ REQUIRE(discard3->asOpaque().name().matches("http://www.sysrepo.org/yang/sysrepo", "discard-items"));
|
||||
+ REQUIRE(!opaqueLeaf.asOpaque().name().matches("sysrepo", "discard-items"));
|
||||
+
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling() == discard2);
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling()->nextSibling() == opaqueLeaf);
|
||||
+ REQUIRE(!!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling());
|
||||
+ REQUIRE(dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling() == discard3);
|
||||
+ REQUIRE(!dummy.firstOpaqueSibling()->nextSibling()->nextSibling()->nextSibling()->nextSibling());
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("RESTCONF RPC output")
|
||||
diff --git a/tests/yang/sysrepo@2024-10-25.yang b/tests/yang/sysrepo@2024-10-25.yang
|
||||
new file mode 100644
|
||||
index 0000000..f5bc32c
|
||||
--- /dev/null
|
||||
+++ b/tests/yang/sysrepo@2024-10-25.yang
|
||||
@@ -0,0 +1,257 @@
|
||||
+module sysrepo {
|
||||
+ namespace "http://www.sysrepo.org/yang/sysrepo";
|
||||
+ prefix sr;
|
||||
+
|
||||
+ yang-version 1.1;
|
||||
+
|
||||
+ import ietf-yang-types {
|
||||
+ prefix yang;
|
||||
+ }
|
||||
+
|
||||
+ import ietf-datastores {
|
||||
+ prefix ds;
|
||||
+ }
|
||||
+
|
||||
+ import ietf-yang-metadata {
|
||||
+ prefix md;
|
||||
+ revision-date 2016-08-05;
|
||||
+ }
|
||||
+
|
||||
+ organization
|
||||
+ "CESNET";
|
||||
+
|
||||
+ contact
|
||||
+ "Author: Michal Vasko
|
||||
+ <mvasko@cesnet.cz>";
|
||||
+
|
||||
+ description
|
||||
+ "Sysrepo YANG datastore internal attributes and information.";
|
||||
+
|
||||
+ revision "2024-10-25" {
|
||||
+ description
|
||||
+ "Removed redundant metadata used for push operational data.";
|
||||
+ }
|
||||
+
|
||||
+ revision "2019-07-10" {
|
||||
+ description
|
||||
+ "Initial revision.";
|
||||
+ }
|
||||
+
|
||||
+ typedef module-ref {
|
||||
+ description
|
||||
+ "Reference to a module.";
|
||||
+ type leafref {
|
||||
+ path "/sysrepo-modules/module/name";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ md:annotation operation {
|
||||
+ type enumeration {
|
||||
+ enum none {
|
||||
+ description
|
||||
+ "Node with this operation must exist but does not affect the datastore in any way.";
|
||||
+ reference
|
||||
+ "RFC 6241 section 7.2.: default-operation";
|
||||
+ }
|
||||
+ enum ether {
|
||||
+ description
|
||||
+ "Node with this operation does not have to exist and does not affect the datastore in any way.";
|
||||
+ }
|
||||
+ enum purge {
|
||||
+ description
|
||||
+ "Node with this operation represents an arbitrary generic node instance and all
|
||||
+ the instances will be deleted.";
|
||||
+ }
|
||||
+ }
|
||||
+ description
|
||||
+ "Additional proprietary <edit-config> operations used internally.";
|
||||
+ reference
|
||||
+ "RFC 6241 section 7.2.";
|
||||
+ }
|
||||
+
|
||||
+ identity notification {
|
||||
+ base ds:datastore;
|
||||
+ description
|
||||
+ "Special datastore for storing notifications for replay.";
|
||||
+ }
|
||||
+
|
||||
+ grouping module-info-grp {
|
||||
+ leaf name {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "Module name.";
|
||||
+ }
|
||||
+
|
||||
+ leaf revision {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "Module revision.";
|
||||
+ }
|
||||
+
|
||||
+ leaf-list enabled-feature {
|
||||
+ type string;
|
||||
+ description
|
||||
+ "List of all the enabled features.";
|
||||
+ }
|
||||
+
|
||||
+ list plugin {
|
||||
+ key "datastore";
|
||||
+ description
|
||||
+ "Module datastore plugin handling specific datastore data.";
|
||||
+
|
||||
+ leaf datastore {
|
||||
+ type identityref {
|
||||
+ base ds:datastore;
|
||||
+ }
|
||||
+ description
|
||||
+ "Datastore of this plugin.";
|
||||
+ }
|
||||
+
|
||||
+ leaf name {
|
||||
+ type string;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Specific plugin name as present in the plugin structures.";
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ grouping deps-grp {
|
||||
+ list lref {
|
||||
+ description
|
||||
+ "Dependency of a leafref node.";
|
||||
+
|
||||
+ leaf target-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Path identifying the leafref target node.";
|
||||
+ }
|
||||
+
|
||||
+ leaf target-module {
|
||||
+ type module-ref;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Foreign target module of the leafref.";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list inst-id {
|
||||
+ description
|
||||
+ "Dependency of an instance-identifier node.";
|
||||
+
|
||||
+ leaf source-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Path identifying the instance-identifier node.";
|
||||
+ }
|
||||
+
|
||||
+ leaf default-target-path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Default instance-identifier value.";
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list xpath {
|
||||
+ description
|
||||
+ "Dependency of an XPath expression - must or when statement.";
|
||||
+
|
||||
+ leaf expression {
|
||||
+ type yang:xpath1.0;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "XPath expression of the dependency - must or when statement argument.";
|
||||
+ }
|
||||
+
|
||||
+ leaf-list target-module {
|
||||
+ type module-ref;
|
||||
+ description
|
||||
+ "Foreign modules with the data needed for evaluation of the XPath.";
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ container sysrepo-modules {
|
||||
+ config false;
|
||||
+ description
|
||||
+ "All installed Sysrepo modules.";
|
||||
+
|
||||
+ leaf content-id {
|
||||
+ type uint32;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Sysrepo module-set content-id to be used for its generated yang-library data.";
|
||||
+ }
|
||||
+
|
||||
+ list module {
|
||||
+ key "name";
|
||||
+ description
|
||||
+ "Sysrepo module.";
|
||||
+
|
||||
+ uses module-info-grp;
|
||||
+
|
||||
+ leaf replay-support {
|
||||
+ type yang:date-and-time;
|
||||
+ description
|
||||
+ "Present only if the module supports replay. Means the earliest stored notification if any present.
|
||||
+ Otherwise the time the replay support was switched on.";
|
||||
+ }
|
||||
+
|
||||
+ container deps {
|
||||
+ description
|
||||
+ "Module data dependencies on other modules.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+
|
||||
+ leaf-list inverse-deps {
|
||||
+ type module-ref;
|
||||
+ description
|
||||
+ "List of modules that depend on this module.";
|
||||
+ }
|
||||
+
|
||||
+ list rpc {
|
||||
+ key "path";
|
||||
+ description
|
||||
+ "Module RPC/actions.";
|
||||
+
|
||||
+ leaf path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Path identifying the operation.";
|
||||
+ }
|
||||
+
|
||||
+ container in {
|
||||
+ description
|
||||
+ "Operation input dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+
|
||||
+ container out {
|
||||
+ description
|
||||
+ "Operation output dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ list notification {
|
||||
+ key "path";
|
||||
+ description
|
||||
+ "Module notifications.";
|
||||
+
|
||||
+ leaf path {
|
||||
+ type yang:xpath1.0;
|
||||
+ description
|
||||
+ "Path identifying the notification.";
|
||||
+ }
|
||||
+
|
||||
+ container deps {
|
||||
+ description
|
||||
+ "Notification dependencies.";
|
||||
+ uses deps-grp;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
From dab8c9aac96063ccb8541d5d1795ee89b75faeee Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 2 Apr 2025 11:50:24 -0700
|
||||
Subject: [PATCH 17/18] build: link to libpcre2 explicitly
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is apparently a problem on Mac OS X builds where the transitive
|
||||
dependency via libyang doesn't take effect:
|
||||
|
||||
Undefined symbols for architecture arm64:
|
||||
"_pcre2_code_free_8", referenced from:
|
||||
libyang::Regex::~Regex() in Regex.cpp.o
|
||||
libyang::Regex::~Regex() in Regex.cpp.o
|
||||
ld: symbol(s) not found for architecture arm64
|
||||
|
||||
Let's fix this by linking with libpcre2-8 directly.
|
||||
|
||||
We're using a different approach than libyang; they have their own CMake
|
||||
wrapper. I find it easier to work with pkg-config modules, so let's try
|
||||
it that way.
|
||||
|
||||
Change-Id: I1a64407d852161595b7dca654433190002cc3600
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 5 ++++-
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index c5fec45..4e009aa 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -34,6 +34,9 @@ pkg_check_modules(LIBYANG REQUIRED libyang>=3.10.1 IMPORTED_TARGET)
|
||||
# FIXME from gcc 14.1 on we should be able to use the calendar/time from libstdc++ and thus remove the date dependency
|
||||
find_package(date)
|
||||
|
||||
+# libyang::Regex::~Regex() bypasses libyang and calls out to libpcre directly
|
||||
+pkg_check_modules(LIBPCRE2 REQUIRED libpcre2-8 IMPORTED_TARGET)
|
||||
+
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
include(CheckIncludeFileCXX)
|
||||
@@ -64,7 +67,7 @@ add_library(yang-cpp
|
||||
src/utils/newPath.cpp
|
||||
)
|
||||
|
||||
-target_link_libraries(yang-cpp PRIVATE PkgConfig::LIBYANG)
|
||||
+target_link_libraries(yang-cpp PRIVATE PkgConfig::LIBYANG PkgConfig::LIBPCRE2)
|
||||
# We do not offer any long-term API/ABI guarantees. To make stuff easier for downstream consumers,
|
||||
# we will be bumping both API and ABI versions very deliberately.
|
||||
# There will be no attempts at semver tracking, for example.
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
From 7519fcd35216072a6b1eebe2a79e19789be345a9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 15:35:37 +0200
|
||||
Subject: [PATCH 18/18] CI: renamed project upstream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5447f243297fbfde7c364eb3919b00db239bd069
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
README.md | 2 +-
|
||||
ci/build.sh | 2 +-
|
||||
3 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index b41c490..7b92766 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -5,13 +5,13 @@
|
||||
required-projects:
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.3.6
|
||||
- f38-clang-asan-ubsan:
|
||||
required-projects: &projects
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.4.11
|
||||
- f38-clang-tsan:
|
||||
required-projects: *projects
|
||||
diff --git a/README.md b/README.md
|
||||
index d76975b..8291e00 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -11,7 +11,7 @@ Object lifetimes are managed automatically via RAII.
|
||||
- [libyang v3](https://github.com/CESNET/libyang) - the `devel` branch (even for the `master` branch of *libyang-cpp*)
|
||||
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
|
||||
- CMake 3.19+
|
||||
-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework
|
||||
+- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||
- optionally for the docs, Doxygen
|
||||
|
||||
## Building
|
||||
diff --git a/ci/build.sh b/ci/build.sh
|
||||
index d06b646..0867f07 100755
|
||||
--- a/ci/build.sh
|
||||
+++ b/ci/build.sh
|
||||
@@ -73,7 +73,7 @@ build_n_test() {
|
||||
}
|
||||
|
||||
build_n_test github/CESNET/libyang -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF
|
||||
-build_n_test github/onqtam/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
+build_n_test github/doctest/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
build_n_test ${ZUUL_PROJECT_NAME} -DBUILD_TESTING=ON
|
||||
|
||||
pushd ${BUILD_DIR}/${ZUUL_PROJECT_NAME}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From 9622a68eba4aeaa60619b4c33d050ce91b27653d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 8 Oct 2024 12:22:49 +0200
|
||||
Subject: [PATCH 01/20] restconf: report the list key values when they differ
|
||||
Subject: [PATCH 01/44] restconf: report the list key values when they differ
|
||||
between URI and data
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
For creating (leaf-)list instances with PUT and PATCH methods one has
|
||||
to specify the list key values in the URI and in the data as well.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 070cffb48fda789910581930265d4624a7213e1b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 16 Oct 2024 11:02:45 +0200
|
||||
Subject: [PATCH 02/20] uri: rename url-encoding to percent-encoding
|
||||
Subject: [PATCH 02/44] uri: rename url-encoding to percent-encoding
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
RFC 3986 [1] calls it the percent-encoding, let's be consistent.
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 8a233202647f24538f2bbb8fff1e38d52e3599a4 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 16 Oct 2024 11:05:09 +0200
|
||||
Subject: [PATCH 03/20] uri: correct x3 rule class names
|
||||
Subject: [PATCH 03/44] uri: correct x3 rule class names
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Fixes: e06d5bf ("server: parser for data resources in URI paths")
|
||||
Change-Id: Ic953e568d841032113ede1c0e896574361c0ebe2
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From 96cbf730010ee9539d05d0d72697dc960b3a938c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 7 Oct 2024 20:46:24 +0200
|
||||
Subject: [PATCH 04/20] restconf: parser should work on raw percent-encoded
|
||||
Subject: [PATCH 04/44] restconf: parser should work on raw percent-encoded
|
||||
paths
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
The difference between nghttp2's uri_ref::path and raw_path is that the
|
||||
raw_path keeps the percent encoding, while the path converts the percent
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From 8b13c1e4ccaa61a241674c27063439e257fa88de Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 2 Oct 2024 20:21:28 +0200
|
||||
Subject: [PATCH 05/20] restconf: list key values checking must respect
|
||||
Subject: [PATCH 05/44] restconf: list key values checking must respect
|
||||
libyang's canonicalization
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
While dealing with the issue that was fixed in the previous commit we
|
||||
realized that the issue is deeper. Not only that our parser rejected
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From fda47b6a6cfdaecc24e96c4d6138c6de3ef116e0 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 7 Oct 2024 21:21:22 +0200
|
||||
Subject: [PATCH 06/20] tests: test querying lists with union keys
|
||||
Subject: [PATCH 06/44] tests: test querying lists with union keys
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Test that we correctly work with keys that are unions of something
|
||||
that can have a module namespace and that must not have it.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 27ef5bc87fdeb70a77609da6ec18ee5c28656bb6 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 29 Oct 2024 18:54:55 +0100
|
||||
Subject: [PATCH 07/20] error handling in sysrepo has changed
|
||||
Subject: [PATCH 07/44] error handling in sysrepo has changed
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/7969
|
||||
Change-Id: Id028806ed49114cba4c55e2874bcf3fc98308bdc
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 6819561d97e38569c319e36ca2e99768036b4032 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 21 Aug 2024 19:18:08 +0200
|
||||
Subject: [PATCH 08/20] restconf: support fields query parameter
|
||||
Subject: [PATCH 08/44] restconf: support fields query parameter
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
This patch adds support for fields query parameter [1].
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 48d9b6ba3f3f892b9060b76b505d2f9a3aeb9e02 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 25 Nov 2024 09:15:55 +0100
|
||||
Subject: [PATCH 09/20] cmake: adhere to CMP0167
|
||||
Subject: [PATCH 09/44] cmake: adhere to CMP0167
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
After locally updating to cmake 3.30 I have seen a warning that our way
|
||||
of finding boost library is deprecated [1].
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 64997543d48236cd2aae417568bc54d32c54df21 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 14:43:36 +0100
|
||||
Subject: [PATCH 10/20] Fix compatibility with pam_wrapper 1.1.6+
|
||||
Subject: [PATCH 10/44] Fix compatibility with pam_wrapper 1.1.6+
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
An year ago I reported a bug that the pam_wrapper project says that they
|
||||
use a variable called `PAM_WRAPPER_DISABLE_DEEPBIND`, but in fact they
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 849aa35274d5e07726cb849fe724962754b3fa29 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:20:48 +0100
|
||||
Subject: [PATCH 11/20] tests: add missing pragma once
|
||||
Subject: [PATCH 11/44] tests: add missing pragma once
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I269c20e5a914aa8c7bc9431147dd9785ea1aedda
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
From 60eac2b2d60f8f1918a0914272975dd53f527c01 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 19:36:10 +0100
|
||||
Subject: [PATCH 12/20] tests: extend clientRequest wrappers interface and use
|
||||
Subject: [PATCH 12/44] tests: extend clientRequest wrappers interface and use
|
||||
it
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
This is a preparation for refactoring in the next few commits.
|
||||
I will generalize the clientRequest interface to accept server
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 0beaee041fd4fcfbebcbb2912eb6b26ec71f50c7 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:01:54 +0100
|
||||
Subject: [PATCH 13/20] tests: move stuff from the header file into cpp file
|
||||
Subject: [PATCH 13/44] tests: move stuff from the header file into cpp file
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Allright, I have had enough. I am no longer waiting for a minute for a
|
||||
recompilation of one test.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 53aa2e23ee8acc1881487f3969c2c58fb29437be Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:08:53 +0100
|
||||
Subject: [PATCH 14/20] tests: rename datastoreUtils to event_watchers
|
||||
Subject: [PATCH 14/44] tests: rename datastoreUtils to event_watchers
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
The next commit will move RESTCONF notification watchers there, I think
|
||||
the new name is more appropriate.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From f86df829979a26d5192a86c3b43c1393dcba7140 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 3 Dec 2024 11:45:41 +0100
|
||||
Subject: [PATCH 15/20] tests: rename NotificationWatcher
|
||||
Subject: [PATCH 15/44] tests: rename NotificationWatcher
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
This watches notifications going through RESTCONF, so I think
|
||||
RestconfNotificatonWatcher is better name for this.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 30d704588fa7eb9d32f66296ec5f6784f082869e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:11:32 +0100
|
||||
Subject: [PATCH 16/20] tests: make NotificationWatcher reusable
|
||||
Subject: [PATCH 16/44] tests: make NotificationWatcher reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
We will need some parts of this in yang push tests
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 36943051d8563bee0c9cf89eec28acf5d3617272 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 3 Dec 2024 12:02:24 +0100
|
||||
Subject: [PATCH 17/20] tests: helper function to construct server URI
|
||||
Subject: [PATCH 17/44] tests: helper function to construct server URI
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Ic7c03b32a29b07464291f99027530010a7902f77
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 7d15f59d20079ba94224e0bc308682aa5a004483 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 2 Dec 2024 20:15:05 +0100
|
||||
Subject: [PATCH 18/20] tests: make SSEClient reusable
|
||||
Subject: [PATCH 18/44] tests: make SSEClient reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
We will need it in yang push tests
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 3e3e492f4801df56226a5aff5d82bfa86c9d3812 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 3 Dec 2024 13:59:52 +0100
|
||||
Subject: [PATCH 19/20] tests: fix deadlock in tests
|
||||
Subject: [PATCH 19/44] tests: fix deadlock in tests
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
In the case the client never requests (because of misconfiguration or
|
||||
something else) the test would still be waiting for somebody to
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From ed0ff23f7ad341d663484f0b2a617cd3bc4923c8 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 3 Dec 2024 19:27:49 +0100
|
||||
Subject: [PATCH 20/20] restconf: make as_restconf_notification reusable
|
||||
Subject: [PATCH 20/44] restconf: make as_restconf_notification reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
The YANG-PUSH notifications are supposed to be wrapped in this too, so
|
||||
this needs to be accessible from multiple places.
|
||||
|
||||
+18
-7
@@ -1,17 +1,25 @@
|
||||
From f7307481fc4ca167592acf925136e5f8a51e2150 Mon Sep 17 00:00:00 2001
|
||||
From: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 17:26:15 +0100
|
||||
Subject: [PATCH] Update dependencies
|
||||
Subject: [PATCH 21/44] Update dependencies
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8134
|
||||
Change-Id: Ib170aa61600ffb9089460247f762ff5a947dd6c6
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
tests/restconf-writing.cpp | 8 ++++----
|
||||
2 files changed, 5 insertions(+), 5 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 22bce32..5ae8062 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -74,7 +74,7 @@
|
||||
@@ -74,7 +74,7 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2)
|
||||
find_package(Boost REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
@@ -24,7 +32,7 @@ diff --git a/tests/restconf-writing.cpp b/tests/restconf-writing.cpp
|
||||
index 0932984..d37ab03 100644
|
||||
--- a/tests/restconf-writing.cpp
|
||||
+++ b/tests/restconf-writing.cpp
|
||||
@@ -772,7 +772,7 @@
|
||||
@@ -772,7 +772,7 @@ TEST_CASE("writing data")
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -33,7 +41,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -855,7 +855,7 @@
|
||||
@@ -855,7 +855,7 @@ TEST_CASE("writing data")
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -42,7 +50,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1560,7 +1560,7 @@
|
||||
@@ -1560,7 +1560,7 @@ TEST_CASE("writing data")
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -51,7 +59,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1626,7 +1626,7 @@
|
||||
@@ -1626,7 +1626,7 @@ TEST_CASE("writing data")
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -60,3 +68,6 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
From c967e688c53000519ceb6030e8282296e4846d3c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 13 Jan 2025 18:29:29 +0100
|
||||
Subject: [PATCH 22/44] restconf: add RAII datastore switch
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Copied from sysrepo-ietf-alarms[1].
|
||||
|
||||
[1] https://github.com/CESNET/sysrepo-ietf-alarms/blob/4870dbee30a5ec56135d4f4cb5818f191c089c34/src/utils/sysrepo.cpp
|
||||
|
||||
Change-Id: I51738d06187e141ebd662e7b9421ca995380cafc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
src/restconf/utils/sysrepo.cpp | 25 +++++++++++++++++++++++++
|
||||
src/restconf/utils/sysrepo.h | 29 +++++++++++++++++++++++++++++
|
||||
3 files changed, 55 insertions(+)
|
||||
create mode 100644 src/restconf/utils/sysrepo.cpp
|
||||
create mode 100644 src/restconf/utils/sysrepo.h
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 5ae8062..7f3a9c0 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -114,6 +114,7 @@ add_library(rousette-restconf STATIC
|
||||
src/restconf/YangSchemaLocations.cpp
|
||||
src/restconf/uri.cpp
|
||||
src/restconf/utils/dataformat.cpp
|
||||
+ src/restconf/utils/sysrepo.cpp
|
||||
src/restconf/utils/yang.cpp
|
||||
)
|
||||
target_link_libraries(rousette-restconf PUBLIC rousette-http rousette-sysrepo rousette-auth Boost::system Threads::Threads PRIVATE date::date-tz)
|
||||
diff --git a/src/restconf/utils/sysrepo.cpp b/src/restconf/utils/sysrepo.cpp
|
||||
new file mode 100644
|
||||
index 0000000..622ab27
|
||||
--- /dev/null
|
||||
+++ b/src/restconf/utils/sysrepo.cpp
|
||||
@@ -0,0 +1,25 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2020, 2022 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ */
|
||||
+
|
||||
+#include <sysrepo-cpp/Connection.hpp>
|
||||
+#include "sysrepo.h"
|
||||
+
|
||||
+namespace rousette::restconf {
|
||||
+
|
||||
+ScopedDatastoreSwitch::ScopedDatastoreSwitch(sysrepo::Session session, sysrepo::Datastore ds)
|
||||
+ : m_session(std::move(session))
|
||||
+ , m_oldDatastore(m_session.activeDatastore())
|
||||
+{
|
||||
+ m_session.switchDatastore(ds);
|
||||
+}
|
||||
+
|
||||
+ScopedDatastoreSwitch::~ScopedDatastoreSwitch()
|
||||
+{
|
||||
+ m_session.switchDatastore(m_oldDatastore);
|
||||
+}
|
||||
+
|
||||
+}
|
||||
diff --git a/src/restconf/utils/sysrepo.h b/src/restconf/utils/sysrepo.h
|
||||
new file mode 100644
|
||||
index 0000000..da6c359
|
||||
--- /dev/null
|
||||
+++ b/src/restconf/utils/sysrepo.h
|
||||
@@ -0,0 +1,29 @@
|
||||
+/*
|
||||
+ * Copyright (C) 2020, 2022 CESNET, https://photonics.cesnet.cz/
|
||||
+ *
|
||||
+ * Written by Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
+ * Written by Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
+ */
|
||||
+
|
||||
+#pragma once
|
||||
+
|
||||
+#include <libyang-cpp/Context.hpp>
|
||||
+#include <sysrepo-cpp/Session.hpp>
|
||||
+
|
||||
+namespace rousette::restconf {
|
||||
+
|
||||
+/** @brief Ensures that session switches to provided datastore and when the object gets destroyed the session switches back to the original datastore. */
|
||||
+class ScopedDatastoreSwitch {
|
||||
+ sysrepo::Session m_session;
|
||||
+ sysrepo::Datastore m_oldDatastore;
|
||||
+
|
||||
+public:
|
||||
+ ScopedDatastoreSwitch(sysrepo::Session session, sysrepo::Datastore ds);
|
||||
+ ~ScopedDatastoreSwitch();
|
||||
+ ScopedDatastoreSwitch(const ScopedDatastoreSwitch&) = delete;
|
||||
+ ScopedDatastoreSwitch(ScopedDatastoreSwitch&&) = delete;
|
||||
+ ScopedDatastoreSwitch& operator=(const ScopedDatastoreSwitch&) = delete;
|
||||
+ ScopedDatastoreSwitch& operator=(ScopedDatastoreSwitch&&) = delete;
|
||||
+};
|
||||
+
|
||||
+}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
From 05274bd27b886e67accc86be7153ed76465b156e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 15 Jan 2025 17:56:26 +0100
|
||||
Subject: [PATCH 23/44] restconf: add missing pragma once
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I6d337c99351e24226fb322a40ba2e96655104860
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/NotificationStream.h | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/src/restconf/NotificationStream.h b/src/restconf/NotificationStream.h
|
||||
index 6840f11..3029a0d 100644
|
||||
--- a/src/restconf/NotificationStream.h
|
||||
+++ b/src/restconf/NotificationStream.h
|
||||
@@ -5,6 +5,7 @@
|
||||
*
|
||||
*/
|
||||
|
||||
+#pragma once
|
||||
#include <optional>
|
||||
#include <sysrepo-cpp/Session.hpp>
|
||||
#include <sysrepo-cpp/Subscription.hpp>
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
From bd7def1768e360cc491d56805f7c16784c2f4fbe Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 15 Jan 2025 18:27:25 +0100
|
||||
Subject: [PATCH 24/44] restconf: make fetching replay info reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We are querying sysrepo for replay support in our modules for NETCONF
|
||||
stream. This part of code will be reused for dynamic subscriptions to
|
||||
NETCONF stream as well, so let's make it reusable.
|
||||
|
||||
Change-Id: Id5bc656619acb8e56dfafde23b03107139d34e94
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/NotificationStream.cpp | 57 +++++++++++++++++------------
|
||||
1 file changed, 34 insertions(+), 23 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/NotificationStream.cpp b/src/restconf/NotificationStream.cpp
|
||||
index eeddc04..e00c39d 100644
|
||||
--- a/src/restconf/NotificationStream.cpp
|
||||
+++ b/src/restconf/NotificationStream.cpp
|
||||
@@ -50,6 +50,36 @@ bool canBeSubscribed(const libyang::Module& mod)
|
||||
{
|
||||
return mod.implemented() && mod.name() != "sysrepo";
|
||||
}
|
||||
+
|
||||
+struct SysrepoReplayInfo {
|
||||
+ bool enabled;
|
||||
+ std::optional<sysrepo::NotificationTimeStamp> earliestNotification;
|
||||
+};
|
||||
+
|
||||
+SysrepoReplayInfo sysrepoReplayInfo(sysrepo::Session& session)
|
||||
+{
|
||||
+ decltype(sysrepo::ModuleReplaySupport::earliestNotification) globalEarliestNotification;
|
||||
+ bool replayEnabled = false;
|
||||
+
|
||||
+ for (const auto& mod : session.getContext().modules()) {
|
||||
+ if (!canBeSubscribed(mod)) {
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
+ auto replay = session.getConnection().getModuleReplaySupport(mod.name());
|
||||
+ replayEnabled |= replay.enabled;
|
||||
+
|
||||
+ if (replay.earliestNotification) {
|
||||
+ if (!globalEarliestNotification) {
|
||||
+ globalEarliestNotification = replay.earliestNotification;
|
||||
+ } else {
|
||||
+ globalEarliestNotification = std::min(*replay.earliestNotification, *globalEarliestNotification);
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return {replayEnabled, globalEarliestNotification};
|
||||
+}
|
||||
}
|
||||
|
||||
namespace rousette::restconf {
|
||||
@@ -114,28 +144,9 @@ void NotificationStream::activate()
|
||||
/** @brief Creates and fills ietf-restconf-monitoring:restconf-state/stream. To be called in oper callback. */
|
||||
void notificationStreamList(sysrepo::Session& session, std::optional<libyang::DataNode>& parent, const std::string& streamsPrefix)
|
||||
{
|
||||
+ const auto replayInfo = sysrepoReplayInfo(session);
|
||||
static const auto prefix = "/ietf-restconf-monitoring:restconf-state/streams/stream[name='NETCONF']"s;
|
||||
|
||||
- decltype(sysrepo::ModuleReplaySupport::earliestNotification) globalEarliestNotification;
|
||||
- bool replayEnabled = false;
|
||||
-
|
||||
- for (const auto& mod : session.getContext().modules()) {
|
||||
- if (!canBeSubscribed(mod)) {
|
||||
- continue;
|
||||
- }
|
||||
-
|
||||
- auto replay = session.getConnection().getModuleReplaySupport(mod.name());
|
||||
- replayEnabled |= replay.enabled;
|
||||
-
|
||||
- if (replay.earliestNotification) {
|
||||
- if (!globalEarliestNotification) {
|
||||
- globalEarliestNotification = replay.earliestNotification;
|
||||
- } else {
|
||||
- globalEarliestNotification = std::min(*replay.earliestNotification, *globalEarliestNotification);
|
||||
- }
|
||||
- }
|
||||
- }
|
||||
-
|
||||
if (!parent) {
|
||||
parent = session.getContext().newPath(prefix + "/description", "Default NETCONF notification stream");
|
||||
} else {
|
||||
@@ -144,11 +155,11 @@ void notificationStreamList(sysrepo::Session& session, std::optional<libyang::Da
|
||||
parent->newPath(prefix + "/access[encoding='xml']/location", streamsPrefix + "NETCONF/XML");
|
||||
parent->newPath(prefix + "/access[encoding='json']/location", streamsPrefix + "NETCONF/JSON");
|
||||
|
||||
- if (replayEnabled) {
|
||||
+ if (replayInfo.enabled) {
|
||||
parent->newPath(prefix + "/replay-support", "true");
|
||||
|
||||
- if (globalEarliestNotification) {
|
||||
- parent->newPath(prefix + "/replay-log-creation-time", libyang::yangTimeFormat(*globalEarliestNotification, libyang::TimezoneInterpretation::Local));
|
||||
+ if (replayInfo.earliestNotification) {
|
||||
+ parent->newPath(prefix + "/replay-log-creation-time", libyang::yangTimeFormat(*replayInfo.earliestNotification, libyang::TimezoneInterpretation::Local));
|
||||
}
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
From 08458725211e707dec7bfebf7c5188e766136677 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 7 Feb 2025 17:34:04 +0100
|
||||
Subject: [PATCH 25/44] sysrepo upstream API change: NULL output on RPCs
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8289
|
||||
Change-Id: I8db21637246492b0f79d1adf7df1704df5173e39
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
src/restconf/Server.cpp | 4 ++--
|
||||
2 files changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 7f3a9c0..cdd0eb4 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -74,7 +74,7 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2)
|
||||
find_package(Boost REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
-pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=4)
|
||||
+pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5)
|
||||
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||
pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 7c66ea4..daf23ed 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -462,13 +462,13 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
|
||||
auto rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout);
|
||||
|
||||
- if (rpcReply.immediateChildren().empty()) {
|
||||
+ if (!rpcReply || rpcReply->immediateChildren().empty()) {
|
||||
requestCtx->res.write_head(204, {CORS});
|
||||
requestCtx->res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
- auto responseNode = rpcReply.child();
|
||||
+ auto responseNode = rpcReply->child();
|
||||
responseNode->unlinkWithSiblings();
|
||||
|
||||
auto envelope = ctx.newOpaqueJSON(rpcNode->schema().module().name(), "output", std::nullopt);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
From d69697a719781ef06ecb0545a58c2d055ca53c6d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 3 Mar 2025 13:32:51 +0100
|
||||
Subject: [PATCH 26/44] boost 1.87 support
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
In Asio 1.33 (boost 1.87) some of the deprecated functionalities were
|
||||
removed [1]. For rousette there are two changes that needed to be
|
||||
addressed:
|
||||
|
||||
* A removed include in clock.cpp. But actually, we do not need that
|
||||
header at all.
|
||||
* boost::asio::io_service is no longer around, it was actually renamed
|
||||
to boost::asio::io_context a while ago. Its post() method is no
|
||||
longer around as well, it was deprecated in favour of post()
|
||||
function [2].
|
||||
This function is there since boost 1.66, so let's set minimal boost
|
||||
version.
|
||||
|
||||
A bigger problem is that our dependency nghttp2-asio seems to be a dead
|
||||
project. At the time of writing this, there are no patches for more than
|
||||
3 years. However, we are not the only consumers of that project and
|
||||
somebody else already took care of that before us [3,4].
|
||||
|
||||
[1] https://www.boost.org/doc/libs/1_87_0/doc/html/boost_asio/history.html
|
||||
[2] https://live.boost.org/doc/libs/1_86_0/doc/html/boost_asio/reference/io_context/post.html
|
||||
[3] https://github.com/nghttp2/nghttp2-asio/issues/23
|
||||
[3] https://github.com/microsoft/vcpkg/commit/8eecddf7f18792d41c58a404604eed7f87b4b462
|
||||
|
||||
Change-Id: Ia59bb06e5f8ed222aa6e9f1c9b3947b05afeb9ec
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 4 ++--
|
||||
src/clock.cpp | 1 -
|
||||
src/http/EventStream.cpp | 2 +-
|
||||
3 files changed, 3 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index cdd0eb4..ca41ef3 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -71,8 +71,8 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
find_package(spdlog REQUIRED)
|
||||
find_package(date REQUIRED) # FIXME: Remove when we have STL with __cpp_lib_chrono >= 201907 (gcc 14)
|
||||
find_package(PkgConfig)
|
||||
-pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2)
|
||||
-find_package(Boost REQUIRED CONFIG COMPONENTS system thread)
|
||||
+pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) # To compile under boost 1.87 you have to patch nghttp2-asio using https://github.com/nghttp2/nghttp2-asio/issues/23
|
||||
+find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5)
|
||||
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
diff --git a/src/clock.cpp b/src/clock.cpp
|
||||
index 16a49b2..7ac6c3e 100644
|
||||
--- a/src/clock.cpp
|
||||
+++ b/src/clock.cpp
|
||||
@@ -5,7 +5,6 @@
|
||||
*
|
||||
*/
|
||||
|
||||
-#include <boost/asio/io_service.hpp>
|
||||
#include <boost/lexical_cast.hpp>
|
||||
#include <chrono>
|
||||
#include <nghttp2/asio_http2_server.h>
|
||||
diff --git a/src/http/EventStream.cpp b/src/http/EventStream.cpp
|
||||
index 34038f4..6c765c5 100644
|
||||
--- a/src/http/EventStream.cpp
|
||||
+++ b/src/http/EventStream.cpp
|
||||
@@ -117,6 +117,6 @@ void EventStream::enqueue(const std::string& what)
|
||||
spdlog::trace("{}: new event, ∑ queue size = {}", peer, len);
|
||||
queue.push_back(buf);
|
||||
state = HasEvents;
|
||||
- res.io_service().post([&res = this->res]() { res.resume(); });
|
||||
+ boost::asio::post(res.io_service(), [&res = this->res]() { res.resume(); });
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
From 97ceef119c900c37bbaa27860c3b43cfa6d69f95 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 13 Jan 2025 13:01:07 +0100
|
||||
Subject: [PATCH 27/44] restconf: refactor uri parser for stream URIs
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This is a preparation for the upcoming patch that will add support for
|
||||
subscribed streams. I will use boost spirit x3 parser to parse such
|
||||
URIs so I need to refactor the existing parser, which is not really a
|
||||
parser, but we only match for the two known URIs.
|
||||
|
||||
Change-Id: Ib76973cd967fc3e9558ceb6be8f51239ce4c5ef3
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 15 +--------------
|
||||
src/restconf/uri.cpp | 36 +++++++++++++++++++++++++++---------
|
||||
src/restconf/uri.h | 11 ++++++-----
|
||||
src/restconf/uri_impl.h | 2 ++
|
||||
tests/uri-parser.cpp | 4 ++--
|
||||
5 files changed, 38 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index daf23ed..d356f7e 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -874,7 +874,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
|
||||
server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable {
|
||||
auto sess = conn.sessionStart();
|
||||
- libyang::DataFormat dataFormat;
|
||||
std::optional<std::string> xpathFilter;
|
||||
std::optional<sysrepo::NotificationTimeStamp> startTime;
|
||||
std::optional<sysrepo::NotificationTimeStamp> stopTime;
|
||||
@@ -890,18 +889,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
|
||||
auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query);
|
||||
|
||||
- switch(streamRequest.type) {
|
||||
- case RestconfStreamRequest::Type::NetconfNotificationJSON:
|
||||
- dataFormat = libyang::DataFormat::JSON;
|
||||
- break;
|
||||
- case RestconfStreamRequest::Type::NetconfNotificationXML:
|
||||
- dataFormat = libyang::DataFormat::XML;
|
||||
- break;
|
||||
- default:
|
||||
- // GCC 14 complains about uninitialized variable, but asRestconfStreamRequest() would have thrown
|
||||
- __builtin_unreachable();
|
||||
- }
|
||||
-
|
||||
if (auto it = streamRequest.queryParams.find("filter"); it != streamRequest.queryParams.end()) {
|
||||
xpathFilter = std::get<std::string>(it->second);
|
||||
}
|
||||
@@ -916,7 +903,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
// The signal is constructed outside NotificationStream class because it is required to be passed to
|
||||
// NotificationStream's parent (EventStream) constructor where it already must be constructed
|
||||
// Yes, this is a hack.
|
||||
- auto client = std::make_shared<NotificationStream>(req, res, std::make_shared<rousette::http::EventStream::Signal>(), sess, dataFormat, xpathFilter, startTime, stopTime);
|
||||
+ auto client = std::make_shared<NotificationStream>(req, res, std::make_shared<rousette::http::EventStream::Signal>(), sess, streamRequest.type.encoding, xpathFilter, startTime, stopTime);
|
||||
client->activate();
|
||||
} catch (const auth::Error& e) {
|
||||
processAuthError(req, res, e, [&res]() {
|
||||
diff --git a/src/restconf/uri.cpp b/src/restconf/uri.cpp
|
||||
index da1e3a5..e95d70e 100644
|
||||
--- a/src/restconf/uri.cpp
|
||||
+++ b/src/restconf/uri.cpp
|
||||
@@ -57,6 +57,13 @@ const auto resources = x3::rule<class resources, URIPath>{"resources"} =
|
||||
((x3::lit("/") | x3::eps) /* /restconf/ and /restconf */ >> x3::attr(URIPrefix{URIPrefix::Type::RestconfRoot}) >> x3::attr(std::vector<PathSegment>{}));
|
||||
const auto uriGrammar = x3::rule<class grammar, URIPath>{"grammar"} = x3::lit("/restconf") >> resources;
|
||||
|
||||
+
|
||||
+const auto netconfStream = x3::rule<class netconfStream, RestconfStreamRequest::NetconfStream>{"netconfStream"} =
|
||||
+ x3::lit("/NETCONF") >>
|
||||
+ ((x3::lit("/XML") >> x3::attr(libyang::DataFormat::XML)) |
|
||||
+ (x3::lit("/JSON") >> x3::attr(libyang::DataFormat::JSON)));
|
||||
+const auto streamUriGrammar = x3::rule<class grammar, RestconfStreamRequest::NetconfStream>{"streamsGrammar"} = x3::lit("/streams") >> netconfStream;
|
||||
+
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
@@ -197,6 +204,17 @@ std::optional<queryParams::QueryParams> parseQueryParams(const std::string& quer
|
||||
return ret;
|
||||
}
|
||||
|
||||
+std::optional<RestconfStreamRequest::NetconfStream> parseStreamUri(const std::string& uriPath)
|
||||
+{
|
||||
+ std::optional<RestconfStreamRequest::NetconfStream> ret;
|
||||
+
|
||||
+ if (!x3::parse(std::begin(uriPath), std::end(uriPath), streamUriGrammar >> x3::eoi, ret)) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
+
|
||||
+ return ret;
|
||||
+}
|
||||
+
|
||||
URIPrefix::URIPrefix()
|
||||
: resourceType(URIPrefix::Type::BasicRestconfData)
|
||||
{
|
||||
@@ -637,20 +655,20 @@ std::optional<std::variant<libyang::Module, libyang::SubmoduleParsed>> asYangMod
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
-RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString)
|
||||
+RestconfStreamRequest::NetconfStream::NetconfStream() = default;
|
||||
+RestconfStreamRequest::NetconfStream::NetconfStream(const libyang::DataFormat& encoding)
|
||||
+ : encoding(encoding)
|
||||
{
|
||||
- static const auto netconfStreamRoot = "/streams/NETCONF/";
|
||||
- RestconfStreamRequest::Type type;
|
||||
+}
|
||||
|
||||
+RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, const std::string& uriPath, const std::string& uriQueryString)
|
||||
+{
|
||||
if (httpMethod != "GET" && httpMethod != "HEAD") {
|
||||
throw ErrorResponse(405, "application", "operation-not-supported", "Method not allowed.");
|
||||
}
|
||||
|
||||
- if (uriPath == netconfStreamRoot + "XML"s) {
|
||||
- type = RestconfStreamRequest::Type::NetconfNotificationXML;
|
||||
- } else if (uriPath == netconfStreamRoot + "JSON"s) {
|
||||
- type = RestconfStreamRequest::Type::NetconfNotificationJSON;
|
||||
- } else {
|
||||
+ auto type = impl::parseStreamUri(uriPath);
|
||||
+ if (!type) {
|
||||
throw ErrorResponse(404, "application", "invalid-value", "Invalid stream");
|
||||
}
|
||||
|
||||
@@ -661,7 +679,7 @@ RestconfStreamRequest asRestconfStreamRequest(const std::string& httpMethod, con
|
||||
|
||||
validateQueryParametersForStream(*queryParameters);
|
||||
|
||||
- return {type, *queryParameters};
|
||||
+ return {*type, *queryParameters};
|
||||
}
|
||||
|
||||
/** @brief Returns a set of allowed HTTP methods for given URI. Usable for the 'allow' header */
|
||||
diff --git a/src/restconf/uri.h b/src/restconf/uri.h
|
||||
index f6df724..6f1f69f 100644
|
||||
--- a/src/restconf/uri.h
|
||||
+++ b/src/restconf/uri.h
|
||||
@@ -197,11 +197,12 @@ struct RestconfRequest {
|
||||
};
|
||||
|
||||
struct RestconfStreamRequest {
|
||||
- enum class Type {
|
||||
- NetconfNotificationJSON,
|
||||
- NetconfNotificationXML,
|
||||
- };
|
||||
- Type type;
|
||||
+ struct NetconfStream {
|
||||
+ libyang::DataFormat encoding;
|
||||
+
|
||||
+ NetconfStream();
|
||||
+ NetconfStream(const libyang::DataFormat& encoding);
|
||||
+ } type;
|
||||
queryParams::QueryParams queryParams;
|
||||
};
|
||||
|
||||
diff --git a/src/restconf/uri_impl.h b/src/restconf/uri_impl.h
|
||||
index 2bcdb3f..00b22f6 100644
|
||||
--- a/src/restconf/uri_impl.h
|
||||
+++ b/src/restconf/uri_impl.h
|
||||
@@ -66,6 +66,8 @@ BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::impl::YangModule, name, revision);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::PathSegment, apiIdent, keys);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::ApiIdentifier, prefix, identifier);
|
||||
|
||||
+BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::RestconfStreamRequest::NetconfStream, encoding);
|
||||
+
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::ParenExpr, lhs, rhs);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SlashExpr, lhs, rhs);
|
||||
BOOST_FUSION_ADAPT_STRUCT(rousette::restconf::queryParams::fields::SemiExpr, lhs, rhs);
|
||||
diff --git a/tests/uri-parser.cpp b/tests/uri-parser.cpp
|
||||
index fd5dd8b..53d1fbb 100644
|
||||
--- a/tests/uri-parser.cpp
|
||||
+++ b/tests/uri-parser.cpp
|
||||
@@ -1072,13 +1072,13 @@ TEST_CASE("URI path parser")
|
||||
|
||||
{
|
||||
auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/XML", "");
|
||||
- REQUIRE(type == RestconfStreamRequest::Type::NetconfNotificationXML);
|
||||
+ REQUIRE(type.encoding == libyang::DataFormat::XML);
|
||||
REQUIRE(queryParams.empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto [type, queryParams] = asRestconfStreamRequest("GET", "/streams/NETCONF/JSON", "");
|
||||
- REQUIRE(type == RestconfStreamRequest::Type::NetconfNotificationJSON);
|
||||
+ REQUIRE(type.encoding == libyang::DataFormat::JSON);
|
||||
REQUIRE(queryParams.empty());
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
From cc815dac4ea17ccb09a0481ad82745a194efe95f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 27 Jan 2025 19:17:07 +0100
|
||||
Subject: [PATCH 28/44] server: prepare checking if yang features are enabled
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We are not requiring any now, but next commits will need it. I am
|
||||
splitting this into a separate commit for clarity.
|
||||
|
||||
Change-Id: I0a8874b55b21d5ed6c7222f0c36a36c3c5ff52c5
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 22 ++++++++++++++--------
|
||||
1 file changed, 14 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index d356f7e..55e504a 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -817,14 +817,20 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
, server{std::make_unique<nghttp2::asio_http2::server::http2>()}
|
||||
, dwdmEvents{std::make_unique<sr::OpticalEvents>(conn.sessionStart())}
|
||||
{
|
||||
- for (const auto& [module, version] : {
|
||||
- std::pair<std::string, std::string>{"ietf-restconf", "2017-01-26"},
|
||||
- {"ietf-restconf-monitoring", "2017-01-26"},
|
||||
- {"ietf-netconf", ""},
|
||||
- {"ietf-yang-library", "2019-01-04"},
|
||||
- {"ietf-yang-patch", "2017-02-22"},
|
||||
- }) {
|
||||
- if (!conn.sessionStart().getContext().getModuleImplemented(module)) {
|
||||
+ for (const auto& [module, version, features] : {
|
||||
+ std::tuple<std::string, std::string, std::vector<std::string>>{"ietf-restconf", "2017-01-26", {}},
|
||||
+ {"ietf-restconf-monitoring", "2017-01-26", {}},
|
||||
+ {"ietf-netconf", "", {}},
|
||||
+ {"ietf-yang-library", "2019-01-04", {}},
|
||||
+ {"ietf-yang-patch", "2017-02-22", {}},
|
||||
+ }) {
|
||||
+ if (auto mod = conn.sessionStart().getContext().getModuleImplemented(module)) {
|
||||
+ for (const auto& feature : features) {
|
||||
+ if (!mod->featureEnabled(feature)) {
|
||||
+ throw std::runtime_error("Module "s + module + "@" + version + " does not implement feature " + feature);
|
||||
+ }
|
||||
+ }
|
||||
+ } else {
|
||||
throw std::runtime_error("Module "s + module + "@" + version + " is not implemented in sysrepo");
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
From f45d7cab63431c94a4db859b07ea6f503214ebaa Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 17:10:17 +0100
|
||||
Subject: [PATCH 29/44] tests: refactor default handling
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
...so that a followup commit which uses
|
||||
libyang::PrintFlags::KeepEmptyCont introduces less noise.
|
||||
|
||||
Change-Id: I587cf8a5fc0e87b37f4bb91a74d848f58b61e336
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-defaults.cpp | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/tests/restconf-defaults.cpp b/tests/restconf-defaults.cpp
|
||||
index dd8b4da..d5c6004 100644
|
||||
--- a/tests/restconf-defaults.cpp
|
||||
+++ b/tests/restconf-defaults.cpp
|
||||
@@ -32,7 +32,7 @@ TEST_CASE("default handling")
|
||||
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT};
|
||||
|
||||
// default value of /example:a/b/c/enabled is implicitly set so it should not be printed
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
|
||||
}
|
||||
)"});
|
||||
@@ -72,7 +72,7 @@ TEST_CASE("default handling")
|
||||
)"});
|
||||
|
||||
// default value is explicitly set so it should be printed
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
"example:a": {
|
||||
"b": {
|
||||
"c": {
|
||||
@@ -86,8 +86,8 @@ TEST_CASE("default handling")
|
||||
// RFC 6243, sec. 2.3.3: A valid 'delete' operation attribute for a data node that has been set by a client to its schema default value MUST succeed.
|
||||
REQUIRE(httpDelete(RESTCONF_DATA_ROOT "/example:a/b/c/enabled", {AUTH_ROOT}) == Response{204, noContentTypeHeaders, ""});
|
||||
|
||||
- // default value is implicitly set so it should be printed
|
||||
- REQUIRE(get(RESTCONF_DATA_ROOT "/example:a", {}) == Response{200, jsonHeaders, R"({
|
||||
+ // default value is only there implicitly, so it should *not* be printed
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
|
||||
}
|
||||
)"});
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
From 22442d322bda3d83457dd0f1da45d95a10e93081 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 17:32:59 +0100
|
||||
Subject: [PATCH 30/44] Always print empty containers in user-defined content
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
There's some subtle interaction between depth-limited printing, default
|
||||
values, and empty non-presence containers. In order to prepare for an
|
||||
upstream change in sysrepo which fixed depth-limited printing, let's
|
||||
stick to "always" printing the empty non-presence containers.
|
||||
|
||||
(It actually isn't "always" because the data such as error reports,
|
||||
patches, etc., are still printed using the default settings. But the
|
||||
data which is subject to user-defined constraints is now consistent.)
|
||||
|
||||
Change-Id: Ie2d6ad2e851d667e7e7582fcd0ac75dabce2c05e
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 4 +-
|
||||
src/restconf/utils/yang.cpp | 2 +-
|
||||
src/sr/AllEvents.cpp | 2 +-
|
||||
src/sr/OpticalEvents.cpp | 2 +-
|
||||
tests/restconf-defaults.cpp | 12 +++++-
|
||||
tests/restconf-nacm.cpp | 12 +++++-
|
||||
tests/restconf-reading.cpp | 79 ++++++++++++++++++++++++++++++++++---
|
||||
7 files changed, 98 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 55e504a..bae3992 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -478,7 +478,7 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
contentType(requestCtx->dataFormat.response),
|
||||
CORS,
|
||||
});
|
||||
- requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings));
|
||||
+ requestCtx->res.end(*envelope->printStr(requestCtx->dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||
}
|
||||
|
||||
void processPost(std::shared_ptr<RequestContext> requestCtx, const std::chrono::milliseconds timeout)
|
||||
@@ -768,7 +768,7 @@ libyang::PrintFlags libyangPrintFlags(const libyang::DataNode& dataNode, const s
|
||||
} catch(const libyang::Error& e) {
|
||||
}
|
||||
|
||||
- libyang::PrintFlags ret = libyang::PrintFlags::WithSiblings;
|
||||
+ libyang::PrintFlags ret = libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont;
|
||||
|
||||
if (!withDefaults && node && (node->schema().nodeType() == libyang::NodeType::Leaf || node->schema().nodeType() == libyang::NodeType::Leaflist) && node->asTerm().isImplicitDefault()) {
|
||||
return ret | libyang::PrintFlags::WithDefaultsAll;
|
||||
diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp
|
||||
index 30661fc..8acb160 100644
|
||||
--- a/src/restconf/utils/yang.cpp
|
||||
+++ b/src/restconf/utils/yang.cpp
|
||||
@@ -111,7 +111,7 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF
|
||||
envelope->insertChild(*eventTime);
|
||||
envelope->insertChild(notification);
|
||||
|
||||
- auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::WithSiblings);
|
||||
+ auto res = *envelope->printStr(dataFormat, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
|
||||
// notification node comes from sysrepo and sysrepo will free this; if not unlinked then envelope destructor would try to free this as well
|
||||
notification.unlink();
|
||||
diff --git a/src/sr/AllEvents.cpp b/src/sr/AllEvents.cpp
|
||||
index 6e2a21c..2aca4c7 100644
|
||||
--- a/src/sr/AllEvents.cpp
|
||||
+++ b/src/sr/AllEvents.cpp
|
||||
@@ -109,7 +109,7 @@ sysrepo::ErrorCode AllEvents::onChange(sysrepo::Session session, const std::stri
|
||||
break;
|
||||
}
|
||||
};
|
||||
- auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
|
||||
+ auto json = *copy.printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
spdlog::info("JSON: {}", json);
|
||||
spdlog::warn("FULL JSON: {}",
|
||||
*session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings));
|
||||
diff --git a/src/sr/OpticalEvents.cpp b/src/sr/OpticalEvents.cpp
|
||||
index 8c5feff..25906a8 100644
|
||||
--- a/src/sr/OpticalEvents.cpp
|
||||
+++ b/src/sr/OpticalEvents.cpp
|
||||
@@ -15,7 +15,7 @@
|
||||
namespace {
|
||||
std::string dumpDataFrom(sysrepo::Session session, const std::string& module)
|
||||
{
|
||||
- return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings);
|
||||
+ return *session.getData('/' + module + ":*")->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont);
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/tests/restconf-defaults.cpp b/tests/restconf-defaults.cpp
|
||||
index d5c6004..89083f1 100644
|
||||
--- a/tests/restconf-defaults.cpp
|
||||
+++ b/tests/restconf-defaults.cpp
|
||||
@@ -33,7 +33,11 @@ TEST_CASE("default handling")
|
||||
|
||||
// default value of /example:a/b/c/enabled is implicitly set so it should not be printed
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
|
||||
@@ -88,7 +92,11 @@ TEST_CASE("default handling")
|
||||
|
||||
// default value is only there implicitly, so it should *not* be printed
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
}
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 29d7723..51328f3 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -130,6 +130,9 @@ TEST_CASE("NACM")
|
||||
"clock": {
|
||||
"timezone-utc-offset": 2
|
||||
},
|
||||
+ "dns-resolver": {
|
||||
+ "options": {}
|
||||
+ },
|
||||
"radius": {
|
||||
"server": [
|
||||
{
|
||||
@@ -139,7 +142,8 @@ TEST_CASE("NACM")
|
||||
"shared-secret": "shared-secret"
|
||||
}
|
||||
}
|
||||
- ]
|
||||
+ ],
|
||||
+ "options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,6 +211,9 @@ TEST_CASE("NACM")
|
||||
"clock": {
|
||||
"timezone-utc-offset": 2
|
||||
},
|
||||
+ "dns-resolver": {
|
||||
+ "options": {}
|
||||
+ },
|
||||
"radius": {
|
||||
"server": [
|
||||
{
|
||||
@@ -216,7 +223,8 @@ TEST_CASE("NACM")
|
||||
"shared-secret": "shared-secret"
|
||||
}
|
||||
}
|
||||
- ]
|
||||
+ ],
|
||||
+ "options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index e709486..ffdb047 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -62,10 +62,22 @@ TEST_CASE("reading data")
|
||||
// this relies on a NACM rule for anonymous access that filters out "a lot of stuff"
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT, {}) == Response{200, jsonHeaders, R"({
|
||||
"example:top-level-leaf": "moo",
|
||||
+ "example:tlc": {},
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ },
|
||||
+ "example:two-leafs": {},
|
||||
"example:config-nonconfig": {
|
||||
"config-node": "foo-config-true",
|
||||
"nonconfig-node": "foo-config-false"
|
||||
},
|
||||
+ "example:ordered-lists": {},
|
||||
"ietf-restconf-monitoring:restconf-state": {
|
||||
"capabilities": {
|
||||
"capability": [
|
||||
@@ -107,10 +119,22 @@ TEST_CASE("reading data")
|
||||
|
||||
REQUIRE(get(RESTCONF_ROOT_DS("operational"), {}) == Response{200, jsonHeaders, R"({
|
||||
"example:top-level-leaf": "moo",
|
||||
+ "example:tlc": {},
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ },
|
||||
+ "example:two-leafs": {},
|
||||
"example:config-nonconfig": {
|
||||
"config-node": "foo-config-true",
|
||||
"nonconfig-node": "foo-config-false"
|
||||
},
|
||||
+ "example:ordered-lists": {},
|
||||
"ietf-restconf-monitoring:restconf-state": {
|
||||
"capabilities": {
|
||||
"capability": [
|
||||
@@ -152,9 +176,21 @@ TEST_CASE("reading data")
|
||||
|
||||
REQUIRE(get(RESTCONF_ROOT_DS("running"), {}) == Response{200, jsonHeaders, R"({
|
||||
"example:top-level-leaf": "moo",
|
||||
+ "example:tlc": {},
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ },
|
||||
+ "example:two-leafs": {},
|
||||
"example:config-nonconfig": {
|
||||
"config-node": "foo-config-true"
|
||||
- }
|
||||
+ },
|
||||
+ "example:ordered-lists": {}
|
||||
}
|
||||
)"});
|
||||
}
|
||||
@@ -213,7 +249,8 @@ TEST_CASE("reading data")
|
||||
{
|
||||
"name": "a"
|
||||
}
|
||||
- ]
|
||||
+ ],
|
||||
+ "options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,7 +281,8 @@ TEST_CASE("reading data")
|
||||
"shared-secret": "shared-secret"
|
||||
}
|
||||
}
|
||||
- ]
|
||||
+ ],
|
||||
+ "options": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -714,6 +752,7 @@ TEST_CASE("reading data")
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
+ "b1": {},
|
||||
"example-augment:b": {
|
||||
"c": {
|
||||
"enabled": true
|
||||
@@ -723,9 +762,18 @@ TEST_CASE("reading data")
|
||||
}
|
||||
)"});
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=explicit", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
+ // FIXME: libyang is not really consistent in printing of NP-containers when trimming away the defaults...
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({
|
||||
|
||||
}
|
||||
@@ -740,6 +788,7 @@ TEST_CASE("reading data")
|
||||
}
|
||||
}
|
||||
},
|
||||
+ "b1": {},
|
||||
"example-augment:b": {
|
||||
"c": {
|
||||
"enabled": true,
|
||||
@@ -766,6 +815,7 @@ TEST_CASE("reading data")
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
+ "b1": {},
|
||||
"example-augment:b": {
|
||||
"c": {
|
||||
"enabled": true
|
||||
@@ -781,13 +831,24 @@ TEST_CASE("reading data")
|
||||
"c": {
|
||||
"enabled": true
|
||||
}
|
||||
+ },
|
||||
+ "b1": {},
|
||||
+ "example-augment:b": {
|
||||
+ "c": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
)"});
|
||||
|
||||
+ // FIXME: libyang is not consistent here:
|
||||
+ // - /example:a/b/c NP-container *is* printed,
|
||||
+ // - /example:a/example-augment:b/c NP-container is *not* printed
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
|
||||
@@ -801,6 +862,7 @@ TEST_CASE("reading data")
|
||||
}
|
||||
}
|
||||
},
|
||||
+ "b1": {},
|
||||
"example-augment:b": {
|
||||
"c": {
|
||||
"enabled": true,
|
||||
@@ -830,10 +892,15 @@ TEST_CASE("reading data")
|
||||
)"});
|
||||
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled?with-defaults=explicit", {}) == Response{200, jsonHeaders, R"({
|
||||
-
|
||||
+ "example:a": {
|
||||
+ "b": {
|
||||
+ "c": {}
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
)"});
|
||||
|
||||
+ // again, libyang is not 100% consistent in the `trim` mode
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled?with-defaults=trim", {}) == Response{200, jsonHeaders, R"({
|
||||
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
From 818af7ec1dc890774603dbf11ed81cf64d89a628 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 17:56:50 +0100
|
||||
Subject: [PATCH 31/44] fix a typo in RFC number
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I81b68b3920c41129cb98728481580699e44e8c20
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-reading.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index ffdb047..38f5496 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -879,7 +879,7 @@ TEST_CASE("reading data")
|
||||
|
||||
SECTION("Implicit node with default value")
|
||||
{
|
||||
- // RFC 4080, 3.5.4: If target of the query is implicitly created node with default value, ignore basic mode
|
||||
+ // RFC 8040, 3.5.4: If target of the query is implicitly created node with default value, ignore basic mode
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:a/b/c/enabled", {}) == Response{200, jsonHeaders, R"({
|
||||
"example:a": {
|
||||
"b": {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
From 9997d61dc43775444e4d78c37054a30b602c2603 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 13 Mar 2025 18:11:39 +0100
|
||||
Subject: [PATCH 32/44] Fix bug in depth= processing
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
...which got fixed upstream in sysrepo.
|
||||
|
||||
Change-Id: Ida37c48058488be32230165b8b4f45c6bd5a4d3a
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8429
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 1 +
|
||||
tests/restconf-reading.cpp | 45 ++++++++++++++++++++++++++++++++++++++
|
||||
2 files changed, 46 insertions(+)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index ca41ef3..272caad 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -74,6 +74,7 @@ find_package(PkgConfig)
|
||||
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2) # To compile under boost 1.87 you have to patch nghttp2-asio using https://github.com/nghttp2/nghttp2-asio/issues/23
|
||||
find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET)
|
||||
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5)
|
||||
pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||
diff --git a/tests/restconf-reading.cpp b/tests/restconf-reading.cpp
|
||||
index 38f5496..f87c4f5 100644
|
||||
--- a/tests/restconf-reading.cpp
|
||||
+++ b/tests/restconf-reading.cpp
|
||||
@@ -243,6 +243,13 @@ TEST_CASE("reading data")
|
||||
)"});
|
||||
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/ietf-system:system/radius?depth=1", {AUTH_DWDM}) == Response{200, jsonHeaders, R"({
|
||||
+ "ietf-system:system": {
|
||||
+ "radius": {}
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/ietf-system:system/radius?depth=2", {AUTH_DWDM}) == Response{200, jsonHeaders, R"({
|
||||
"ietf-system:system": {
|
||||
"radius": {
|
||||
"server": [
|
||||
@@ -1026,6 +1033,25 @@ TEST_CASE("reading data")
|
||||
}
|
||||
)"});
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=nested/data&depth=1", {}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:tlc": {
|
||||
+ "list": [
|
||||
+ {
|
||||
+ "name": "blabla",
|
||||
+ "nested": [
|
||||
+ {
|
||||
+ "first": "1",
|
||||
+ "second": 2,
|
||||
+ "third": "3",
|
||||
+ "data": {}
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "/example:tlc/list=blabla?fields=nested/data&depth=2", {}) == Response{200, jsonHeaders, R"({
|
||||
"example:tlc": {
|
||||
"list": [
|
||||
{
|
||||
@@ -1049,6 +1075,25 @@ TEST_CASE("reading data")
|
||||
|
||||
// whole datastore with fields filtering
|
||||
REQUIRE(get(RESTCONF_DATA_ROOT "?fields=example:tlc/list/nested/data&depth=1", {}) == Response{200, jsonHeaders, R"({
|
||||
+ "example:tlc": {
|
||||
+ "list": [
|
||||
+ {
|
||||
+ "name": "blabla",
|
||||
+ "nested": [
|
||||
+ {
|
||||
+ "first": "1",
|
||||
+ "second": 2,
|
||||
+ "third": "3",
|
||||
+ "data": {}
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+}
|
||||
+)"});
|
||||
+
|
||||
+ REQUIRE(get(RESTCONF_DATA_ROOT "?fields=example:tlc/list/nested/data&depth=2", {}) == Response{200, jsonHeaders, R"({
|
||||
"example:tlc": {
|
||||
"list": [
|
||||
{
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
From 009b2b01fb4a1ca854402debd66d899a264db8f2 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 13:57:26 +0100
|
||||
Subject: [PATCH 33/44] Adapt to libyang-cpp API break in opaque nodes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
I was wondering for a while whether libyang will do the right thing when
|
||||
printing the RPC's output nodes in XML, but apparently it does (and we
|
||||
have a test for that).
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/dependencies/+/8438
|
||||
Change-Id: I82425743fd2613fcbe2b696848e2feb1c47fc658
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
src/restconf/Server.cpp | 5 +++--
|
||||
src/restconf/utils/yang.cpp | 9 +++++----
|
||||
3 files changed, 9 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 272caad..bcabe84 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -76,7 +76,7 @@ find_package(Boost 1.66 REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET)
|
||||
pkg_check_modules(SYSREPO-CPP REQUIRED IMPORTED_TARGET sysrepo-cpp>=5)
|
||||
-pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=3)
|
||||
+pkg_check_modules(LIBYANG-CPP REQUIRED IMPORTED_TARGET libyang-cpp>=4)
|
||||
pkg_check_modules(SYSTEMD IMPORTED_TARGET libsystemd)
|
||||
pkg_check_modules(PAM REQUIRED IMPORTED_TARGET pam)
|
||||
pkg_check_modules(DOCOPT REQUIRED IMPORTED_TARGET docopt)
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index bae3992..28e2e4c 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -471,7 +471,8 @@ void processActionOrRPC(std::shared_ptr<RequestContext> requestCtx, const std::c
|
||||
auto responseNode = rpcReply->child();
|
||||
responseNode->unlinkWithSiblings();
|
||||
|
||||
- auto envelope = ctx.newOpaqueJSON(rpcNode->schema().module().name(), "output", std::nullopt);
|
||||
+ // libyang auto-resolves the XML namespace when the result is printed into XML
|
||||
+ auto envelope = ctx.newOpaqueJSON({rpcNode->schema().module().name(), rpcNode->schema().module().name(), "output"}, std::nullopt);
|
||||
envelope->insertChild(*responseNode);
|
||||
|
||||
requestCtx->res.write_head(200, {
|
||||
@@ -735,7 +736,7 @@ libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest
|
||||
}
|
||||
|
||||
for (const auto& rpc : mod.actionRpcs()) {
|
||||
- operations.insertChild(*ctx.newOpaqueJSON(rpc.module().name(), rpc.name(), libyang::JSON{"[null]"}));
|
||||
+ operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"}));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
diff --git a/src/restconf/utils/yang.cpp b/src/restconf/utils/yang.cpp
|
||||
index 8acb160..9358840 100644
|
||||
--- a/src/restconf/utils/yang.cpp
|
||||
+++ b/src/restconf/utils/yang.cpp
|
||||
@@ -89,6 +89,7 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF
|
||||
{
|
||||
static const auto jsonNamespace = "ietf-restconf";
|
||||
static const auto xmlNamespace = "urn:ietf:params:xml:ns:netconf:notification:1.0";
|
||||
+ static const auto xmlPrefix = "ietf-netconf-notifications";
|
||||
|
||||
std::optional<libyang::DataNode> envelope;
|
||||
std::optional<libyang::DataNode> eventTime;
|
||||
@@ -96,11 +97,11 @@ std::string as_restconf_notification(const libyang::Context& ctx, libyang::DataF
|
||||
|
||||
/* The namespaces for XML and JSON envelopes are different. See https://datatracker.ietf.org/doc/html/rfc8040#section-6.4 */
|
||||
if (dataFormat == libyang::DataFormat::JSON) {
|
||||
- envelope = ctx.newOpaqueJSON(jsonNamespace, "notification", std::nullopt);
|
||||
- eventTime = ctx.newOpaqueJSON(jsonNamespace, "eventTime", libyang::JSON{timeStr});
|
||||
+ envelope = ctx.newOpaqueJSON({jsonNamespace, jsonNamespace, "notification"}, std::nullopt);
|
||||
+ eventTime = ctx.newOpaqueJSON({jsonNamespace, jsonNamespace, "eventTime"}, libyang::JSON{timeStr});
|
||||
} else {
|
||||
- envelope = ctx.newOpaqueXML(xmlNamespace, "notification", std::nullopt);
|
||||
- eventTime = ctx.newOpaqueXML(xmlNamespace, "eventTime", libyang::XML{timeStr});
|
||||
+ envelope = ctx.newOpaqueXML({xmlNamespace, xmlPrefix, "notification"}, std::nullopt);
|
||||
+ eventTime = ctx.newOpaqueXML({xmlNamespace, xmlPrefix, "eventTime"}, libyang::XML{timeStr});
|
||||
}
|
||||
|
||||
// the notification data node holds only the notification data tree but for nested notification we should print the whole YANG data tree
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
From 061d960d9435018aaba3b2d1b4d857ddb8836d1b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 15:38:44 +0100
|
||||
Subject: [PATCH 34/44] Fix XML serialization when listing RPCs
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The RFC says [1] that each RPC entry that's listed in the reply should
|
||||
look like this when serialized into the XML format:
|
||||
|
||||
<system-restart xmlns='urn:ietf:params:xml:ns:yang:ietf-system'/>
|
||||
|
||||
Our old code would, due to the way how libyang deals with opaque nodes,
|
||||
just try to dump the JSON-ish `[null]` bits in there, which is wrong.
|
||||
|
||||
[1] https://datatracker.ietf.org/doc/html/rfc8040#page-84
|
||||
|
||||
Change-Id: I6ea433dbd3fcf3ca2883d3fa00c9d215c0e1bb4f
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/br2-external/+/8443
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 16 +++++++++++++---
|
||||
tests/restconf-rpc.cpp | 33 ++++++++++++++++++++++++++++++++-
|
||||
2 files changed, 45 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 28e2e4c..05239e9 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -712,7 +712,7 @@ void processPutOrPlainPatch(std::shared_ptr<RequestContext> requestCtx, const st
|
||||
}
|
||||
|
||||
/** @brief Build data trees for endpoints returning ietf-restconf:restconf data */
|
||||
-libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest::Type& type)
|
||||
+libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest::Type& type, libyang::DataFormat dataFormat)
|
||||
{
|
||||
const auto yangLib = *ctx.getModuleLatest("ietf-yang-library");
|
||||
const auto yangApiExt = ctx.getModuleImplemented("ietf-restconf")->extensionInstance("yang-api");
|
||||
@@ -736,7 +736,16 @@ libyang::DataNode apiResource(const libyang::Context& ctx, const RestconfRequest
|
||||
}
|
||||
|
||||
for (const auto& rpc : mod.actionRpcs()) {
|
||||
- operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"}));
|
||||
+ switch (dataFormat) {
|
||||
+ case libyang::DataFormat::JSON:
|
||||
+ operations.insertChild(*ctx.newOpaqueJSON({rpc.module().name(), rpc.module().name(), rpc.name()}, libyang::JSON{"[null]"}));
|
||||
+ break;
|
||||
+ case libyang::DataFormat::XML:
|
||||
+ operations.insertChild(*ctx.newOpaqueXML({rpc.module().ns(), rpc.module().name(), rpc.name()}, std::nullopt));
|
||||
+ break;
|
||||
+ default:
|
||||
+ throw std::logic_error{"Invalid data format for apiResource()"};
|
||||
+ }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -987,7 +996,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
case RestconfRequest::Type::YangLibraryVersion:
|
||||
case RestconfRequest::Type::ListRPC:
|
||||
res.write_head(200, {contentType(dataFormat.response), CORS});
|
||||
- res.end(*apiResource(sess.getContext(), restconfRequest.type).printStr(dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||
+ res.end(*apiResource(sess.getContext(), restconfRequest.type, dataFormat.response)
|
||||
+ .printStr(dataFormat.response, libyang::PrintFlags::WithSiblings | libyang::PrintFlags::KeepEmptyCont));
|
||||
break;
|
||||
|
||||
case RestconfRequest::Type::GetData: {
|
||||
diff --git a/tests/restconf-rpc.cpp b/tests/restconf-rpc.cpp
|
||||
index c4229a0..d49b121 100644
|
||||
--- a/tests/restconf-rpc.cpp
|
||||
+++ b/tests/restconf-rpc.cpp
|
||||
@@ -79,7 +79,9 @@ TEST_CASE("invoking actions and rpcs")
|
||||
|
||||
SECTION("List RPCs")
|
||||
{
|
||||
- REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
+ SECTION("JSON")
|
||||
+ {
|
||||
+ REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT}) == Response{200, jsonHeaders, R"({
|
||||
"ietf-restconf:restconf": {
|
||||
"operations": {
|
||||
"ietf-factory-default:factory-reset": [null],
|
||||
@@ -103,6 +105,35 @@ TEST_CASE("invoking actions and rpcs")
|
||||
}
|
||||
}
|
||||
)"});
|
||||
+ }
|
||||
+
|
||||
+ SECTION("XML")
|
||||
+ {
|
||||
+ REQUIRE(get(RESTCONF_OPER_ROOT, {AUTH_ROOT, CONTENT_TYPE_XML})
|
||||
+ == Response{200, xmlHeaders,
|
||||
+ R"(<restconf xmlns="urn:ietf:params:xml:ns:yang:ietf-restconf">
|
||||
+ <operations>
|
||||
+ <factory-reset xmlns="urn:ietf:params:xml:ns:yang:ietf-factory-default"/>
|
||||
+ <get-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <copy-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <delete-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <lock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <unlock xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <close-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <kill-session xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
|
||||
+ <set-current-datetime xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>
|
||||
+ <system-restart xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>
|
||||
+ <system-shutdown xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>
|
||||
+ <test-rpc xmlns="http://example.tld/example"/>
|
||||
+ <test-rpc-no-output xmlns="http://example.tld/example"/>
|
||||
+ <test-rpc-no-input xmlns="http://example.tld/example"/>
|
||||
+ <test-rpc-no-input-no-output xmlns="http://example.tld/example"/>
|
||||
+ </operations>
|
||||
+</restconf>
|
||||
+)"});
|
||||
+ }
|
||||
}
|
||||
|
||||
SECTION("RPC")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
From 6ad606cb9ad49ca7bfb8056b9695ab271a33e790 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 26 Mar 2025 18:56:06 +0100
|
||||
Subject: [PATCH 35/44] make sure that edit tree is the first sibling
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
After a bug in velia [1], where we passed to Session::editBatch a tree
|
||||
which was not the first sibling (and this caused that all the left
|
||||
siblings of the node were not considered in the edit), I am making sure
|
||||
that we do not lose any data in this project due to this too.
|
||||
|
||||
[1] https://github.com/CESNET/velia/commit/2b7187405740fe61e8b37e0efab8c3173d398a26
|
||||
|
||||
Fixes: 25f3e3b ("restconf: support for YANG patch")
|
||||
Change-Id: I7211168325fbfddd75dc1520c9dd83e31138d3bc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 05239e9..c454541 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -600,7 +600,7 @@ void processYangPatchEdit(const std::shared_ptr<RequestContext>& requestCtx, con
|
||||
if (!mergedEdits) {
|
||||
mergedEdits = *singleEdit;
|
||||
} else {
|
||||
- mergedEdits->insertSibling(*singleEdit);
|
||||
+ mergedEdits = mergedEdits->insertSibling(*singleEdit); // make sure we point to the first sibling, sysrepo::editBatch requires that
|
||||
}
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
||||
From 10c6ae778724e5c15a12b58bc95c814cb17b772e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 8 Apr 2025 16:59:48 +0200
|
||||
Subject: [PATCH 37/44] don't create throwaway sysrepo sessions during startup
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Ic3fc71c08c589a8e87bbdc1aa1b1089c88c7cb1e
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index 9fd3fa9..a840e6e 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -847,7 +847,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
{"ietf-subscribed-notifications", "2019-09-09", {}},
|
||||
{"ietf-restconf-subscribed-notifications", "2019-11-17", {}},
|
||||
}) {
|
||||
- if (auto mod = conn.sessionStart().getContext().getModuleImplemented(module)) {
|
||||
+ if (auto mod = m_monitoringSession.getContext().getModuleImplemented(module)) {
|
||||
for (const auto& feature : features) {
|
||||
if (!mod->featureEnabled(feature)) {
|
||||
throw std::runtime_error("Module "s + module + "@" + version + " does not implement feature " + feature);
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
From 4e287e1e2af95c1ec077689e4f353f657469e5e5 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 8 Apr 2025 17:00:44 +0200
|
||||
Subject: [PATCH 38/44] requests: create sysrepo session later
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
I was looking at all places where a sysrepo session is used, and tried
|
||||
to defer their creations until the last moment possible, just to
|
||||
(potentially) save some resources when it isn't needed. Some of these
|
||||
changes are cosmetic, but I was touching that code anyway.
|
||||
|
||||
This cannot be done for the generic `restconfRoot` handler because of
|
||||
its associated error handling which requires a libyang context. Well, it
|
||||
*could* be done, but I don't feel like doing some m_monitoringSession
|
||||
magic for little to no purpose.
|
||||
|
||||
Change-Id: Ia84c90abc1464e4a0f9682779d7471aefad9e2de
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 5 ++---
|
||||
1 file changed, 2 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index a840e6e..f3c515b 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -902,7 +902,6 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
});
|
||||
|
||||
server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable {
|
||||
- auto sess = conn.sessionStart();
|
||||
std::optional<std::string> xpathFilter;
|
||||
std::optional<sysrepo::NotificationTimeStamp> startTime;
|
||||
std::optional<sysrepo::NotificationTimeStamp> stopTime;
|
||||
@@ -914,6 +913,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
}
|
||||
|
||||
try {
|
||||
+ auto sess = conn.sessionStart();
|
||||
authorizeRequest(nacm, sess, req);
|
||||
|
||||
auto streamRequest = asRestconfStreamRequest(req.method(), req.uri().path, req.uri().raw_query);
|
||||
@@ -962,9 +962,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
return;
|
||||
}
|
||||
|
||||
- auto sess = conn.sessionStart(sysrepo::Datastore::Operational);
|
||||
-
|
||||
try {
|
||||
+ auto sess = conn.sessionStart(sysrepo::Datastore::Operational);
|
||||
authorizeRequest(nacm, sess, req);
|
||||
|
||||
if (auto mod = asYangModule(sess.getContext(), req.uri().path); mod && hasAccessToYangSchema(sess, *mod)) {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
From 422eae2ae879872bd618e3ba19418d20c197650d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 15:52:34 +0200
|
||||
Subject: [PATCH 39/44] docs: doctest has moved
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I45d605f080434a0726d729c7b186ab1bbeb8d8e1
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
README.md | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
index 3fbfd21..646976d 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -94,7 +94,7 @@ In practical terms, this means that the NACM access rules for the following XPat
|
||||
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
|
||||
- CMake 3.19+
|
||||
- optionally systemd - the shared library for logging to `sd-journal`
|
||||
-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework
|
||||
+- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||
- optionally for built-in tests, [trompeloeil](https://github.com/rollbear/trompeloeil) for mock objects in C++
|
||||
- optionally for built-in tests, [`pam_matrix` and `pam_wrapper`](https://cwrap.org/pam_wrapper.html) for PAM mocking
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
From b60c30e4eb482ce7b72ba95c8e6edc94f232ca37 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 8 Apr 2025 18:12:18 +0200
|
||||
Subject: [PATCH 40/44] tests: capture wrongly-formatted SSE messages
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Sometimes during development I do things wrong(TM) and the Server Sent
|
||||
Events data stream is not prefixed.
|
||||
The log failure is unhelpful, so this patch should make the failures
|
||||
more clear in such cases.
|
||||
|
||||
Change-Id: I0d4643a6d7fe56ea5ce694c734a76b6dd471ff4c
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf_utils.cpp | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index c3ff1de..72c2c5a 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -224,6 +224,7 @@ std::vector<std::string> SSEClient::parseEvents(const std::string& msg)
|
||||
res.emplace_back(std::move(event));
|
||||
event.clear();
|
||||
} else {
|
||||
+ CAPTURE(msg);
|
||||
FAIL("Unprefixed response");
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
From 2e866f4076df5cf68e7f8015e088baf45b45ddb3 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 14 Apr 2025 13:15:29 +0200
|
||||
Subject: [PATCH 41/44] tests: perform factory-reset between individual
|
||||
sections of NACM test
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I3b60439a8235e2a0efed58fcdb2ad8eb95f21714
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
tests/restconf-nacm.cpp | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 51328f3..546d694 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -20,6 +20,8 @@ TEST_CASE("NACM")
|
||||
auto nacmGuard = manageNacm(srSess);
|
||||
auto server = rousette::restconf::Server{srConn, SERVER_ADDRESS, SERVER_PORT};
|
||||
|
||||
+ srSess.sendRPC(srSess.getContext().newPath("/ietf-factory-default:factory-reset"));
|
||||
+
|
||||
// something we can read
|
||||
srSess.switchDatastore(sysrepo::Datastore::Operational);
|
||||
srSess.setItem("/ietf-system:system/contact", "contact");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
From 0ef0307d6664affc0547c2e65c8c33900bd7cfc9 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 14 Apr 2025 15:12:00 +0200
|
||||
Subject: [PATCH 42/44] restconf: simplify condition in NACM anonymous access
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The condition required that all access-operations values are "read". But
|
||||
access-operations is a union, so it is enough to check that we have only
|
||||
one value in the union and that it is "read".
|
||||
|
||||
Also test what happens if anonymous rule access-operation is a union of
|
||||
read and something else.
|
||||
|
||||
Change-Id: I291243d9e2167601b24d49684d53c361407f66be
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/auth/Nacm.cpp | 4 +---
|
||||
tests/restconf-nacm.cpp | 18 ++++++++++++++++++
|
||||
2 files changed, 19 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/auth/Nacm.cpp b/src/auth/Nacm.cpp
|
||||
index 61a7dcc..d0dc4a2 100644
|
||||
--- a/src/auth/Nacm.cpp
|
||||
+++ b/src/auth/Nacm.cpp
|
||||
@@ -12,9 +12,7 @@ namespace {
|
||||
bool isRuleReadOnly(const libyang::DataNode& rule)
|
||||
{
|
||||
auto accessOperations = rule.findXPath("access-operations");
|
||||
- return !accessOperations.empty() && std::all_of(accessOperations.begin(), accessOperations.end(), [](const auto& e) {
|
||||
- return e.asTerm().valueStr() == "read";
|
||||
- });
|
||||
+ return accessOperations.size() == 1 && accessOperations.begin()->asTerm().valueStr() == "read";
|
||||
}
|
||||
|
||||
bool isRuleWildcardDeny(const libyang::DataNode& rule)
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 546d694..30f4210 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -95,6 +95,24 @@ TEST_CASE("NACM")
|
||||
srSess.applyChanges();
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("Anonymous has one rule with both read and update access operations")
|
||||
+ {
|
||||
+ srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list");
|
||||
+ srSess.applyChanges();
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/group[.='yangnobody']", "");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/module-name", "ietf-system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/action", "permit");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/access-operations", "read update");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/path", "/ietf-system:system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/module-name", "*");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/action", "deny");
|
||||
+
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", "");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/module-name", "ietf-system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/action", "permit");
|
||||
+ srSess.applyChanges();
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("Anonymous rulelist OK, but not at first place")
|
||||
{
|
||||
srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
From f97dcabf8bf74c463d3291d31e9b36fabec0654f Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 21:17:19 +0200
|
||||
Subject: [PATCH 43/44] restconf: allow specifying exec permissions for
|
||||
anonymous user access
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
We want to be able to allow anonymous users to call the RPC
|
||||
ietf-subscribed-notifications:establish-subscription. Our current NACM
|
||||
anonymous setup did not allow specifying exec permissions.
|
||||
After this patch admins are allowed to setup a rule in the anonymous
|
||||
user rule-list that allows anonymous users to call the
|
||||
establish-subscriptions RPC. We allow only this one RPC, specifying any
|
||||
other will result in disabling the anonymous user access.
|
||||
|
||||
Change-Id: I5aa6fee2bbdabc0b7deac7fb9afecac6c411aca0
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
README.md | 2 +-
|
||||
src/auth/Nacm.cpp | 23 ++++++++++++++++++-----
|
||||
tests/restconf-nacm.cpp | 21 +++++++++++++++++++++
|
||||
tests/restconf_utils.cpp | 4 ++++
|
||||
4 files changed, 44 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/README.md b/README.md
|
||||
index 646976d..0796dce 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -66,7 +66,7 @@ When certain conditions are met, the anonymous access will be mapped to a NACM a
|
||||
There must be some specific access rights set up in `ietf-netconf-acm` model (these are currently very opinionated for our use-case):
|
||||
|
||||
1. The first entry of `rule-list` list must be configured for `ANONYMOUS_USER_GROUP`.
|
||||
-2. All the rules except the last one in this rule-list entry must enable only "read" access operation.
|
||||
+2. All the rules except the last one in this rule-list entry must enable either only "read" access operation or only "exec" operation on `ietf-subscribed-notifications:establish-subscription` RPC.
|
||||
3. The last rule in the first rule-set must be a wildcard rule that disables all operations over all modules.
|
||||
|
||||
The anonymous user access is disabled whenever these rules are not met.
|
||||
diff --git a/src/auth/Nacm.cpp b/src/auth/Nacm.cpp
|
||||
index d0dc4a2..284fa85 100644
|
||||
--- a/src/auth/Nacm.cpp
|
||||
+++ b/src/auth/Nacm.cpp
|
||||
@@ -9,10 +9,23 @@
|
||||
#include "NacmIdentities.h"
|
||||
|
||||
namespace {
|
||||
-bool isRuleReadOnly(const libyang::DataNode& rule)
|
||||
+bool isWhitelistedRPC(const std::optional<libyang::DataNode>& moduleNameNode, const std::optional<libyang::DataNode>& rpcNameNode)
|
||||
+{
|
||||
+ if (!moduleNameNode || !rpcNameNode) {
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ return moduleNameNode->asTerm().valueStr() == "ietf-subscribed-notifications" && rpcNameNode->asTerm().valueStr() == "establish-subscription";
|
||||
+}
|
||||
+
|
||||
+bool isRuleForAnonymousAccess(const libyang::DataNode& rule)
|
||||
{
|
||||
auto accessOperations = rule.findXPath("access-operations");
|
||||
- return accessOperations.size() == 1 && accessOperations.begin()->asTerm().valueStr() == "read";
|
||||
+ auto rpcName = rule.findPath("rpc-name");
|
||||
+ return accessOperations.size() == 1 && // Combining access operations is not allowed in anonymous user rules
|
||||
+ (accessOperations.begin()->asTerm().valueStr() == "read" || // Either read...
|
||||
+ (accessOperations.begin()->asTerm().valueStr() == "exec" && isWhitelistedRPC(rule.findPath("module-name"), rule.findPath("rpc-name"))) // ...or exec on whitelisted RPC.
|
||||
+ );
|
||||
}
|
||||
|
||||
bool isRuleWildcardDeny(const libyang::DataNode& rule)
|
||||
@@ -25,7 +38,7 @@ bool isRuleWildcardDeny(const libyang::DataNode& rule)
|
||||
*
|
||||
* The first rule-list element contains rules for anonymous user access, i.e.:
|
||||
* - The group is set to @p anonGroup (this one should contain the anonymous user)
|
||||
- * - In rules (except the last one) the access-operation allowed is "read"
|
||||
+ * - In rules (except the last one) the access-operation allowed is either "read" or "exec" on a whitelisted RPC.
|
||||
* - The last rule has module-name="*" and action "deny".
|
||||
*
|
||||
* @return boolean indicating whether the rules are configured properly for anonymous user access
|
||||
@@ -60,8 +73,8 @@ bool validAnonymousNacmRules(sysrepo::Session session, const std::string& anonGr
|
||||
return false;
|
||||
}
|
||||
|
||||
- if (!std::all_of(rules.begin(), rules.end() - 1, isRuleReadOnly)) {
|
||||
- spdlog::debug("NACM config validation: First n-1 rules in the anonymous rule-list must be configured for read-access only");
|
||||
+ if (!std::all_of(rules.begin(), rules.end() - 1, isRuleForAnonymousAccess)) {
|
||||
+ spdlog::debug("NACM config validation: First n-1 rules in the anonymous rule-list must be configured either for read-access only or exec on listed RPC paths");
|
||||
return false;
|
||||
}
|
||||
|
||||
diff --git a/tests/restconf-nacm.cpp b/tests/restconf-nacm.cpp
|
||||
index 30f4210..7799fe8 100644
|
||||
--- a/tests/restconf-nacm.cpp
|
||||
+++ b/tests/restconf-nacm.cpp
|
||||
@@ -113,6 +113,27 @@ TEST_CASE("NACM")
|
||||
srSess.applyChanges();
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("This RPC is not allowed")
|
||||
+ {
|
||||
+ srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list");
|
||||
+ srSess.applyChanges();
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/group[.='yangnobody']", "");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/module-name", "ietf-system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/action", "permit");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/access-operations", "read");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='1']/path", "/ietf-system:system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/module-name", "ietf-subscribed-notifications");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/action", "permit");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/access-operations", "read update");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='2']/rpc-name", "/ietf-subscribed-notifications:modify-subscription");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='3']/module-name", "*");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='3']/action", "deny");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", "");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/module-name", "ietf-system");
|
||||
+ srSess.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/rule[name='1']/action", "permit");
|
||||
+ srSess.applyChanges();
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("Anonymous rulelist OK, but not at first place")
|
||||
{
|
||||
srSess.deleteItem("/ietf-netconf-acm:nacm/rule-list");
|
||||
diff --git a/tests/restconf_utils.cpp b/tests/restconf_utils.cpp
|
||||
index 72c2c5a..a137463 100644
|
||||
--- a/tests/restconf_utils.cpp
|
||||
+++ b/tests/restconf_utils.cpp
|
||||
@@ -152,6 +152,10 @@ void setupRealNacm(sysrepo::Session session)
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/action", "permit");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/access-operations", "read");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='15']/path", "/example-delete:immutable");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/module-name", "ietf-subscribed-notifications");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/action", "permit");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/access-operations", "exec");
|
||||
+ session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='16']/rpc-name", "establish-subscription");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/module-name", "*");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='anon rule']/rule[name='99']/action", "deny");
|
||||
session.setItem("/ietf-netconf-acm:nacm/rule-list[name='dwdm rule']/group[.='optics']", "");
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
From 438a18f5884ace3063f00a2dfc53dd2b4034f53e Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 16 Apr 2025 10:56:50 +0200
|
||||
Subject: [PATCH 44/44] restconf: add access log to stream root
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5e1db3f40bc6a7f294e6a5fef9402e0855055258
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/restconf/Server.cpp | 3 +++
|
||||
1 file changed, 3 insertions(+)
|
||||
|
||||
diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp
|
||||
index f3c515b..5b1b2f9 100644
|
||||
--- a/src/restconf/Server.cpp
|
||||
+++ b/src/restconf/Server.cpp
|
||||
@@ -902,6 +902,9 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std::
|
||||
});
|
||||
|
||||
server->handle(netconfStreamRoot, [this, conn](const auto& req, const auto& res) mutable {
|
||||
+ const auto& peer = http::peer_from_request(req);
|
||||
+ spdlog::info("{}: {} {}", peer, req.method(), req.uri().raw_path);
|
||||
+
|
||||
std::optional<std::string> xpathFilter;
|
||||
std::optional<sysrepo::NotificationTimeStamp> startTime;
|
||||
std::optional<sysrepo::NotificationTimeStamp> stopTime;
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 23fa270a747c35d65aeeba691599ed6b21b501f0 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 29 Oct 2024 15:15:52 +0100
|
||||
Subject: [PATCH 1/6] error handling changes in upstream sysrepo
|
||||
Subject: [PATCH 01/20] error handling changes in upstream sysrepo
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Upstream commit 396e331e4634a417420a71ad723567b42d75c443 removed these
|
||||
extra error entries.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From a73ffe1a5bbab51a67cf56dd2864c71a29c6685b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Thu, 21 Nov 2024 11:18:25 +0100
|
||||
Subject: [PATCH 2/6] Fix error message when sr_session_start fails
|
||||
Subject: [PATCH 02/20] Fix error message when sr_session_start fails
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Thanks to Jason Patterson for sending a bugreport; its investigation led
|
||||
to discovering this bug.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 552a366990fe1fe2cdd8d155fa5cecb3ff3fbc13 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 4 Dec 2024 15:51:41 +0100
|
||||
Subject: [PATCH 3/6] We don't support sysrepo v3 yet
|
||||
Subject: [PATCH 03/20] We don't support sysrepo v3 yet
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
That version introduced some backward-incompatible changes to the way
|
||||
how operational data are treated, especially in presence of "discards"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From f2db30721d909d127645721f4149de0cd36d2b1c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 17 Dec 2024 11:41:10 +0100
|
||||
Subject: [PATCH 4/6] refactor: don't use `module` as a variable name
|
||||
Subject: [PATCH 04/20] refactor: don't use `module` as a variable name
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
...to unify stuff with all other occurrences, and to not use that magic
|
||||
identifier.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From a923f490350dc61b9f3e6000c06c1d7950a78a61 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 16:12:14 +0100
|
||||
Subject: [PATCH 5/6] DRY: a dummy leaf XPath
|
||||
Subject: [PATCH 05/20] DRY: a dummy leaf XPath
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I47fca61d27380af07b2327f3ee5b984a8a8afb66
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
From 80e9659b41ea1de2e20d62f94319fe5af26adcee Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 17:57:36 +0100
|
||||
Subject: [PATCH 6/6] build: a single place to define package version
|
||||
Subject: [PATCH 06/20] build: a single place to define package version
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
Organization: Wires
|
||||
|
||||
Don't ask me how I found out that I need this :).
|
||||
|
||||
|
||||
+36
-21
@@ -1,7 +1,11 @@
|
||||
From b34fb200812f9e6bc72e80bf46bd006ebc06a9da Mon Sep 17 00:00:00 2001
|
||||
From: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
From 32bdd8bcad04f890fe724fb6cc2fc74f9b982576 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 17:16:55 +0100
|
||||
Subject: [PATCH] API/ABI break: Change how pushed ops data work
|
||||
Subject: [PATCH 07/20] API/ABI break: Change how pushed ops data work
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Upstream has changed the way how pushed operational data are stored and
|
||||
managed. From now on, each session has a separate edit, these edits are
|
||||
@@ -33,10 +37,18 @@ avoiding the possible confusion at the C++ layer:
|
||||
by `Session::applyChanges()`.
|
||||
|
||||
Change-Id: Iba05a88411fd4c47c03d8c2b48cb7aadfd5dcd2a
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 +-
|
||||
CMakeLists.txt | 4 +-
|
||||
README.md | 1 -
|
||||
include/sysrepo-cpp/Session.hpp | 6 ++-
|
||||
src/Session.cpp | 96 ++++++++++++++++++++++++---------
|
||||
tests/session.cpp | 59 ++++++++++++++++----
|
||||
6 files changed, 128 insertions(+), 42 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index c3aea22..dc87c6f 100644
|
||||
index c3aea22..70c5e49 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -6,7 +6,7 @@
|
||||
@@ -44,7 +56,7 @@ index c3aea22..dc87c6f 100644
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2024-10-before-oper-changes
|
||||
+ override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-30--00
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.8
|
||||
- name: github/rollbear/trompeloeil
|
||||
@@ -53,15 +65,15 @@ index c3aea22..dc87c6f 100644
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2024-10-before-oper-changes
|
||||
+ override-checkout: devel
|
||||
+ override-checkout: cesnet/2025-01-30--00
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- name: github/rollbear/trompeloeil
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 68ade53..cc4dba9 100644
|
||||
index 68ade53..9e09d3d 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -20,7 +20,7 @@
|
||||
@@ -20,7 +20,7 @@ add_custom_target(sysrepo-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
@@ -70,12 +82,12 @@ index 68ade53..cc4dba9 100644
|
||||
prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
@@ -29,7 +29,7 @@
|
||||
@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=2.12.0 sysrepo<3 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.0 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.7 IMPORTED_TARGET)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
@@ -83,7 +95,7 @@ diff --git a/README.md b/README.md
|
||||
index e14b772..27dc748 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -9,7 +9,6 @@
|
||||
@@ -9,7 +9,6 @@ It uses RAII for automatic memory management.
|
||||
|
||||
## Dependencies
|
||||
- [sysrepo](https://github.com/sysrepo/sysrepo) - the `devel` branch (even for the `master` branch of *sysrepo-cpp*)
|
||||
@@ -95,7 +107,7 @@ diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 477e604..238f480 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -85,13 +85,15 @@
|
||||
@@ -85,13 +85,15 @@ public:
|
||||
void setItem(const std::string& path, const std::optional<std::string>& value, const EditOptions opts = sysrepo::EditOptions::Default);
|
||||
void editBatch(libyang::DataNode edit, const DefaultOperation op);
|
||||
void deleteItem(const std::string& path, const EditOptions opts = sysrepo::EditOptions::Default);
|
||||
@@ -117,7 +129,7 @@ diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 94a0fad..634e450 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -23,6 +23,26 @@
|
||||
@@ -23,6 +23,26 @@ extern "C" {
|
||||
|
||||
using namespace std::string_literals;
|
||||
namespace sysrepo {
|
||||
@@ -144,7 +156,7 @@ index 94a0fad..634e450 100644
|
||||
/**
|
||||
* Wraps a pointer to sr_session_ctx_s and manages the lifetime of it. Also extends the lifetime of the connection
|
||||
* specified by the `conn` argument.
|
||||
@@ -119,20 +139,61 @@
|
||||
@@ -119,20 +139,61 @@ void Session::deleteItem(const std::string& path, const EditOptions opts)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,7 +221,7 @@ index 94a0fad..634e450 100644
|
||||
/**
|
||||
* Moves item (a list or a leaf-list) specified by `path`.
|
||||
* @param path Node to move.
|
||||
@@ -155,25 +216,6 @@
|
||||
@@ -155,25 +216,6 @@ void Session::moveItem(const std::string& path, const MovePosition move, const s
|
||||
throwIfError(res, "Session::moveItem: Can't move '"s + path + "'", m_sess.get());
|
||||
}
|
||||
|
||||
@@ -235,16 +247,16 @@ index 94a0fad..634e450 100644
|
||||
/**
|
||||
* @brief Returns a tree which contains all nodes that match the provided XPath.
|
||||
*
|
||||
@@ -263,13 +305,15 @@
|
||||
@@ -263,13 +305,15 @@ void Session::applyChanges(std::chrono::milliseconds timeout)
|
||||
}
|
||||
|
||||
/**
|
||||
- * Discards changes made in this Session.
|
||||
+ * Discards changes made earlier in this Session, optionally only below a given XPath
|
||||
+ *
|
||||
+ * The changes are applied only after calling Session::applyChanges.
|
||||
*
|
||||
- * Wraps `sr_discard_changes`.
|
||||
+ * The changes are applied only after calling Session::applyChanges.
|
||||
+ *
|
||||
+ * Wraps `sr_discard_changes_xpath`.
|
||||
*/
|
||||
-void Session::discardChanges()
|
||||
@@ -259,7 +271,7 @@ diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index f762f10..2637f67 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -211,7 +211,7 @@
|
||||
@@ -211,7 +211,7 @@ TEST_CASE("session")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,7 +280,7 @@ index f762f10..2637f67 100644
|
||||
{
|
||||
// Set some arbitrary leaf.
|
||||
sess.setItem(leaf, "123");
|
||||
@@ -230,15 +230,51 @@
|
||||
@@ -230,15 +230,51 @@ TEST_CASE("session")
|
||||
sess.switchDatastore(sysrepo::Datastore::Operational);
|
||||
REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123");
|
||||
|
||||
@@ -328,7 +340,7 @@ index f762f10..2637f67 100644
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("edit batch")
|
||||
@@ -321,6 +357,11 @@
|
||||
@@ -321,6 +357,11 @@ TEST_CASE("session")
|
||||
sess.discardChanges();
|
||||
}
|
||||
|
||||
@@ -340,3 +352,6 @@ index f762f10..2637f67 100644
|
||||
REQUIRE(sess.getPendingChanges() == std::nullopt);
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
From 27dc076cbdff3c9edf00abb05bdce59d1944988b Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 31 Jan 2025 17:54:06 +0100
|
||||
Subject: [PATCH 08/20] upstream break: sr_rpc_send_tree's `output` might be
|
||||
NULL now
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
As of upstream commit 927fd5221bd28ff50cc1ee7c2cb5c5f30b34c2c9, sysrepo
|
||||
can now return a nullptr when the resulting tree is empty. This appears
|
||||
to have been done in response to our bugreport where we pointed out that
|
||||
`netopeer2-server` started crashing on empty RPCs. However, it's unclear
|
||||
why this necessitated a change in sysrepo, given that this feature has
|
||||
worked well for years.
|
||||
|
||||
Anyway, let's propagate this through the C++ layer; what else can I do,
|
||||
after all.
|
||||
|
||||
Bug: https://github.com/CESNET/netopeer2/issues/1695
|
||||
Change-Id: I646d19f7448d98f0d21e9eec006f28fbb5e479fc
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
CMakeLists.txt | 4 ++--
|
||||
include/sysrepo-cpp/Session.hpp | 2 +-
|
||||
src/Session.cpp | 6 ++++--
|
||||
tests/subscriptions.cpp | 7 ++++---
|
||||
5 files changed, 13 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index 70c5e49..dc87c6f 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -6,7 +6,7 @@
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2025-01-30--00
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.8
|
||||
- name: github/rollbear/trompeloeil
|
||||
@@ -17,7 +17,7 @@
|
||||
- name: github/CESNET/libyang
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
- override-checkout: cesnet/2025-01-30--00
|
||||
+ override-checkout: devel
|
||||
- name: github/onqtam/doctest
|
||||
override-checkout: v2.4.11
|
||||
- name: github/rollbear/trompeloeil
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 9e09d3d..85ff84c 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -20,7 +20,7 @@ add_custom_target(sysrepo-cpp-version-cmake
|
||||
cmake/ProjectGitVersionRunner.cmake
|
||||
)
|
||||
include(cmake/ProjectGitVersion.cmake)
|
||||
-set(SYSREPO_CPP_PKG_VERSION "4")
|
||||
+set(SYSREPO_CPP_PKG_VERSION "5")
|
||||
prepare_git_version(SYSREPO_CPP_VERSION ${SYSREPO_CPP_PKG_VERSION})
|
||||
|
||||
find_package(Doxygen)
|
||||
@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.7 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.8 IMPORTED_TARGET)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 238f480..6e31fa2 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
void discardOperationalChanges(const std::optional<std::string>& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
void dropForeignOperationalContent(const std::optional<std::string>& xpath);
|
||||
void copyConfig(const Datastore source, const std::optional<std::string>& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
- libyang::DataNode sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
+ std::optional<libyang::DataNode> sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
void sendNotification(libyang::DataNode notification, const Wait wait, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
void replaceConfig(std::optional<libyang::DataNode> config, const std::optional<std::string>& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 634e450..5df04bc 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -343,13 +343,15 @@ void Session::copyConfig(const Datastore source, const std::optional<std::string
|
||||
* @param input Libyang tree representing the RPC/action.
|
||||
* @param timeout Optional timeout.
|
||||
*/
|
||||
-libyang::DataNode Session::sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout)
|
||||
+std::optional<libyang::DataNode> Session::sendRPC(libyang::DataNode input, std::chrono::milliseconds timeout)
|
||||
{
|
||||
sr_data_t* output;
|
||||
auto res = sr_rpc_send_tree(m_sess.get(), libyang::getRawNode(input), timeout.count(), &output);
|
||||
throwIfError(res, "Couldn't send RPC", m_sess.get());
|
||||
|
||||
- assert(output); // TODO: sysrepo always gives the RPC node? (even when it has not output or output nodes?)
|
||||
+ if (!output) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
return wrapSrData(m_sess, output);
|
||||
}
|
||||
|
||||
diff --git a/tests/subscriptions.cpp b/tests/subscriptions.cpp
|
||||
index 36fd61c..6fc9cbb 100644
|
||||
--- a/tests/subscriptions.cpp
|
||||
+++ b/tests/subscriptions.cpp
|
||||
@@ -420,9 +420,10 @@ TEST_CASE("subscriptions")
|
||||
if (ret == sysrepo::ErrorCode::Ok) {
|
||||
auto output = sess.sendRPC(sess.getContext().newPath(rpcPath));
|
||||
if (setFunction) {
|
||||
- REQUIRE(output.findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output));
|
||||
- } else {
|
||||
- REQUIRE(!output.findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output).has_value());
|
||||
+ REQUIRE(!!output);
|
||||
+ REQUIRE(output->findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output));
|
||||
+ } else if (output) {
|
||||
+ REQUIRE(!output->findPath("/test_module:shutdown/success", libyang::InputOutputNodes::Output).has_value());
|
||||
}
|
||||
} else {
|
||||
REQUIRE_THROWS(sess.sendRPC(sess.getContext().newPath(rpcPath)));
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
From dc8facb5befbd17f2848098bbc9267c9d9f58e2d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Mon, 10 Mar 2025 12:08:47 +0100
|
||||
Subject: [PATCH 09/20] tests: adapt to upstream breaking change in sr_get_data
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
It seems that since upstream commit ba8897ce3 [1] the max_depth
|
||||
parameter started behaving differently. It now behaves as the
|
||||
documentation [2] states, i.e:
|
||||
|
||||
Maximum depth of the selected subtrees. 0 is unlimited, 1 will not
|
||||
return any descendant nodes. If a list should be returned, its keys
|
||||
are always returned as well.
|
||||
|
||||
The previous behaviour was probably wrong. When reported via slack,
|
||||
another fix was issued [3], so let's depend on that as well.
|
||||
|
||||
[1] https://github.com/sysrepo/sysrepo/commit/ba8897ce3da0bc8203d5b964851fd60fb164760d
|
||||
[2] https://netopeer.liberouter.org/doc/sysrepo/devel/html/group__get__data__api.html
|
||||
[3] https://github.com/sysrepo/sysrepo/commit/309b34d28cc09771cf488d04478161f684f4b8a7
|
||||
|
||||
Change-Id: I337dc1f1f77b9501dda29931846116050123bcad
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
tests/session.cpp | 11 +++++++++--
|
||||
2 files changed, 10 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 85ff84c..fb132a2 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -29,7 +29,7 @@ option(WITH_EXAMPLES "Build examples" ON)
|
||||
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(LIBYANG_CPP REQUIRED libyang-cpp>=3 IMPORTED_TARGET)
|
||||
-pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.4.8 IMPORTED_TARGET)
|
||||
+pkg_check_modules(SYSREPO REQUIRED sysrepo>=3.6.5 IMPORTED_TARGET)
|
||||
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 2637f67..5be0d79 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -127,6 +127,13 @@ TEST_CASE("session")
|
||||
data = sess.getData("/test_module:popelnice", 1);
|
||||
REQUIRE(data);
|
||||
REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
+ "test_module:popelnice": {}
|
||||
+}
|
||||
+)");
|
||||
+
|
||||
+ data = sess.getData("/test_module:popelnice", 2);
|
||||
+ REQUIRE(data);
|
||||
+ REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
"test_module:popelnice": {
|
||||
"content": {}
|
||||
}
|
||||
@@ -134,7 +141,7 @@ TEST_CASE("session")
|
||||
)");
|
||||
|
||||
// If a list should be returned, its keys are always returned as well.
|
||||
- data = sess.getData("/test_module:popelnice", 2);
|
||||
+ data = sess.getData("/test_module:popelnice", 3);
|
||||
REQUIRE(data);
|
||||
REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
"test_module:popelnice": {
|
||||
@@ -152,7 +159,7 @@ TEST_CASE("session")
|
||||
}
|
||||
)");
|
||||
|
||||
- data = sess.getData("/test_module:popelnice", 3);
|
||||
+ data = sess.getData("/test_module:popelnice", 4);
|
||||
REQUIRE(data);
|
||||
REQUIRE(*data->printStr(libyang::DataFormat::JSON, libyang::PrintFlags::KeepEmptyCont) == R"({
|
||||
"test_module:popelnice": {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
From 6ed5442db482a9a37128add02778aebd096f9207 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Fri, 14 Mar 2025 16:20:09 +0100
|
||||
Subject: [PATCH 10/20] Utilities for working with existing
|
||||
sysrepo:discard-items nodes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
findMatchingDiscard() and findMatchingDiscardPrefixes() are useful for
|
||||
figuring out which nodes need to be prune when adding/modifying a
|
||||
discarding node.
|
||||
|
||||
unlinkFromForest() is useful for removing "a node" from a forest of data
|
||||
node trees. Sometimes the node to be deleted *could* be a root node
|
||||
which is also the first sibling among its collection of siblings, and in
|
||||
that case some extra care is needed to ensure that we still have a
|
||||
reference to something after we've done with the unlinking.
|
||||
|
||||
In the real world this doesn't happen that often because there usually
|
||||
is something like a top-level container node below which we're
|
||||
processing these discards. Still, let's put this utility function into a
|
||||
single place.
|
||||
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8436
|
||||
Change-Id: Ib346be6887f75220a8a6435864c74aef8d5925bb
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/utils/utils.hpp | 3 ++
|
||||
src/Session.cpp | 3 ++
|
||||
src/utils/utils.cpp | 57 ++++++++++++++++++++++
|
||||
tests/session.cpp | 74 +++++++++++++++++++++++++++--
|
||||
4 files changed, 134 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/utils/utils.hpp b/include/sysrepo-cpp/utils/utils.hpp
|
||||
index 22d7d66..64b1c2e 100644
|
||||
--- a/include/sysrepo-cpp/utils/utils.hpp
|
||||
+++ b/include/sysrepo-cpp/utils/utils.hpp
|
||||
@@ -12,4 +12,7 @@
|
||||
namespace sysrepo {
|
||||
Session wrapUnmanagedSession(sr_session_ctx_s* session);
|
||||
void setLogLevelStderr(const LogLevel);
|
||||
+std::optional<libyang::DataNodeOpaque> findMatchingDiscard(libyang::DataNode root, const std::string& xpath);
|
||||
+std::vector<libyang::DataNodeOpaque> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix);
|
||||
+void unlinkFromForest(std::optional<libyang::DataNode>& root, libyang::DataNode node);
|
||||
}
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 5df04bc..3c21e84 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -153,6 +153,7 @@ void Session::deleteItem(const std::string& path, const EditOptions opts)
|
||||
* Wraps `sr_discard_items`.
|
||||
*
|
||||
* @param xpath Expression filtering the nodes to discard, nullopt for all nodes.
|
||||
+ * @see findMatchingDiscard()
|
||||
*/
|
||||
void Session::dropForeignOperationalContent(const std::optional<std::string>& xpath)
|
||||
{
|
||||
@@ -167,6 +168,8 @@ void Session::dropForeignOperationalContent(const std::optional<std::string>& xp
|
||||
* with the "replace" operation.
|
||||
*
|
||||
* Wraps `sr_get_oper_changes`.
|
||||
+ *
|
||||
+ * @see findMatchingDiscard()
|
||||
*/
|
||||
std::optional<libyang::DataNode> Session::operationalChanges(const std::optional<std::string>& moduleName) const
|
||||
{
|
||||
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||
index 972a4fa..e339e88 100644
|
||||
--- a/src/utils/utils.cpp
|
||||
+++ b/src/utils/utils.cpp
|
||||
@@ -72,4 +72,61 @@ void checkNoThreadFlag(const SubscribeOptions opts, const std::optional<FDHandli
|
||||
}
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @short If there's a sysrepo:discard-items node which matches the given XPath, return it
|
||||
+ *
|
||||
+ * @see Session::operationalChanges()
|
||||
+ * @see Session::dropForeignOperationalContent()
|
||||
+ */
|
||||
+std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, const std::string& xpath)
|
||||
+{
|
||||
+ auto discard = root.firstOpaqueSibling();
|
||||
+ while (discard) {
|
||||
+ if (discard->name().matches("sysrepo", "discard-items") && discard->value() == xpath) {
|
||||
+ return discard;
|
||||
+ }
|
||||
+ if (auto next = discard->nextSibling()) {
|
||||
+ discard = next->asOpaque();
|
||||
+ } else {
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ return std::nullopt;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendats of this XPath
|
||||
+ */
|
||||
+std::vector<libyang::DataNode> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
|
||||
+{
|
||||
+ auto withSlash = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '/') ? xpathPrefix : xpathPrefix + '/';
|
||||
+ auto withBracket = (xpathPrefix.empty() || xpathPrefix[xpathPrefix.size() - 1] == '[') ? xpathPrefix : xpathPrefix + '[';
|
||||
+ std::vector<libyang::DataNode> res;
|
||||
+ auto discard = root.firstOpaqueSibling();
|
||||
+ while (discard) {
|
||||
+ if (discard->name().matches("sysrepo", "discard-items")) {
|
||||
+ if (auto text = discard->value(); text == xpathPrefix || text.starts_with(withSlash) || text.starts_with(withBracket)) {
|
||||
+ res.emplace_back(*discard);
|
||||
+ }
|
||||
+ }
|
||||
+ if (auto next = discard->nextSibling()) {
|
||||
+ discard = next->asOpaque();
|
||||
+ } else {
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ return res;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @short Remove a node from a forest of tree nodes while modifying the root in-place
|
||||
+ */
|
||||
+void unlinkFromForest(std::optional<libyang::DataNode>& root, libyang::DataNode node)
|
||||
+{
|
||||
+ if (node == root) {
|
||||
+ root = node.nextSibling();
|
||||
+ }
|
||||
+ node.unlink();
|
||||
+}
|
||||
+
|
||||
}
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 5be0d79..9a419a1 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -245,9 +245,77 @@ TEST_CASE("session")
|
||||
sess.applyChanges();
|
||||
REQUIRE(!sess.getData(leaf));
|
||||
|
||||
- // Using discardOperationalChanges makes the leaf visible again (in the operational datastore).
|
||||
- // Also, no need to applyChanges().
|
||||
- sess.discardOperationalChanges("test_module");
|
||||
+ // check that a magic sysrepo:discard-items node is in place
|
||||
+ REQUIRE(!!sess.operationalChanges());
|
||||
+ auto matchingDiscard = sysrepo::findMatchingDiscard(*sess.operationalChanges(), leaf);
|
||||
+ REQUIRE(!!matchingDiscard);
|
||||
+ REQUIRE(matchingDiscard->value() == leaf);
|
||||
+ REQUIRE(matchingDiscard->name().moduleOrNamespace == "sysrepo");
|
||||
+ REQUIRE(matchingDiscard->name().name == "discard-items");
|
||||
+ REQUIRE(!sysrepo::findMatchingDiscard(*sess.operationalChanges(), "something else"));
|
||||
+
|
||||
+ DOCTEST_SUBCASE("forget changes via discardOperationalChanges(module)")
|
||||
+ {
|
||||
+ // Using discardOperationalChanges makes the leaf visible again (in the operational datastore).
|
||||
+ // Also, no need to applyChanges().
|
||||
+ sess.discardOperationalChanges("test_module");
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("forget changes via a selective edit")
|
||||
+ {
|
||||
+ // this edit only has a single node, which means that we cannot really call unlink() and hope for a sane result
|
||||
+ REQUIRE(matchingDiscard->firstSibling() == *matchingDiscard);
|
||||
+
|
||||
+ // so, we add a dummy node instead...
|
||||
+ auto root = matchingDiscard->newPath("/test_module:popelnice/s", "foo");
|
||||
+ // ...and only then we nuke the eixtsing discard-items node
|
||||
+ matchingDiscard->unlink();
|
||||
+ sess.editBatch(*root, sysrepo::DefaultOperation::Replace);
|
||||
+ sess.applyChanges();
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("multiple sysrepo:discard-items nodes")
|
||||
+ {
|
||||
+ sess.dropForeignOperationalContent("/test_module:popelnice");
|
||||
+ sess.dropForeignOperationalContent("/test_module:popelnice/s");
|
||||
+ sess.dropForeignOperationalContent("/test_module:values");
|
||||
+ sess.dropForeignOperationalContent("/test_module:popelnice/content");
|
||||
+ sess.dropForeignOperationalContent("/test_module:denyAllLeaf");
|
||||
+ sess.dropForeignOperationalContent(leaf); // yup, once more, in addition to the one at the very beginning
|
||||
+ sess.applyChanges();
|
||||
+
|
||||
+ auto forPopelnice = sysrepo::findMatchingDiscard(*sess.operationalChanges(), "/test_module:popelnice");
|
||||
+ REQUIRE(!!forPopelnice);
|
||||
+ REQUIRE(forPopelnice->value() == "/test_module:popelnice");
|
||||
+ auto oneMatch = sysrepo::findMatchingDiscard(*sess.operationalChanges(), "/test_module:values");
|
||||
+ REQUIRE(!!oneMatch);
|
||||
+ REQUIRE(oneMatch->value() == "/test_module:values");
|
||||
+
|
||||
+ auto atOrBelowPopelnice = sysrepo::findMatchingDiscardPrefixes(*sess.operationalChanges(), "/test_module:popelnice");
|
||||
+ REQUIRE(atOrBelowPopelnice.size() == 3);
|
||||
+ // yup, these are apparently backwards compared to how I put them in. Never mind.
|
||||
+ REQUIRE(atOrBelowPopelnice[2].value() == "/test_module:popelnice");
|
||||
+ REQUIRE(atOrBelowPopelnice[1].value() == "/test_module:popelnice/s");
|
||||
+ REQUIRE(atOrBelowPopelnice[0].value() == "/test_module:popelnice/content");
|
||||
+
|
||||
+ auto belowPopelnice = sysrepo::findMatchingDiscardPrefixes(*sess.operationalChanges(), "/test_module:popelnice/");
|
||||
+ REQUIRE(belowPopelnice.size() == 2);
|
||||
+ // again, the order is reversed
|
||||
+ REQUIRE(belowPopelnice[1].value() == "/test_module:popelnice/s");
|
||||
+ REQUIRE(belowPopelnice[0].value() == "/test_module:popelnice/content");
|
||||
+
|
||||
+ auto newEdit = sess.operationalChanges();
|
||||
+ auto forLeaf = sysrepo::findMatchingDiscardPrefixes(*newEdit, leaf);
|
||||
+ REQUIRE(forLeaf.size() == 2);
|
||||
+ REQUIRE(forLeaf[0].value() == leaf);
|
||||
+ REQUIRE(forLeaf[1].value() == leaf);
|
||||
+ for (auto node : forLeaf) {
|
||||
+ sysrepo::unlinkFromForest(newEdit, node);
|
||||
+ }
|
||||
+ sess.editBatch(*newEdit, sysrepo::DefaultOperation::Replace);
|
||||
+ sess.applyChanges();
|
||||
+ }
|
||||
+
|
||||
REQUIRE(sess.getData(leaf)->asTerm().valueStr() == "123");
|
||||
}
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
From aa78815b05090df63bef9ac1b6473f32aac5363d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Tue, 18 Mar 2025 11:25:05 +0100
|
||||
Subject: [PATCH 11/20] Fix a typo
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
...because I don't want to go through the CI again, hence a separate
|
||||
commit.
|
||||
|
||||
Fixes: 6ed5442 Utilities for working with existing sysrepo:discard-items nodes
|
||||
Reported-by: Tomáš Pecka <tomas.pecka@cesnet.cz>
|
||||
Change-Id: I31f3efc1006fe222b7853b24a116b3d8cc04dc8d
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/utils/utils.cpp | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
|
||||
index e339e88..ff4a32c 100644
|
||||
--- a/src/utils/utils.cpp
|
||||
+++ b/src/utils/utils.cpp
|
||||
@@ -95,7 +95,7 @@ std::optional<libyang::DataNode> findMatchingDiscard(libyang::DataNode root, con
|
||||
}
|
||||
|
||||
/**
|
||||
- * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendats of this XPath
|
||||
+ * @short Find all sysrepo:discard-items nodes which match the given XPath or the descendants of this XPath
|
||||
*/
|
||||
std::vector<libyang::DataNode> findMatchingDiscardPrefixes(libyang::DataNode root, const std::string& xpathPrefix)
|
||||
{
|
||||
--
|
||||
2.43.0
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,414 @@
|
||||
From 9d812203b3c0550995be8d60e7b11761bdbac04d Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 1 Apr 2025 21:22:04 +0200
|
||||
Subject: [PATCH 13/20] add subtree filtering to notifications subscription
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Ie547b5f478cc8e3b09ea2f324b62854576787e1b
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
include/sysrepo-cpp/Session.hpp | 6 +-
|
||||
src/Session.cpp | 58 +++++++++--
|
||||
tests/subscriptions-dynamic.cpp | 173 ++++++++++++++++++++++++++++++--
|
||||
4 files changed, 213 insertions(+), 26 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 01ec967..47472d2 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -82,7 +82,7 @@ if(BUILD_TESTING)
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-interfaces@2018-02-20.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-ip@2018-02-22.yang
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-network-instance@2019-01-21.yang
|
||||
- --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-subscribed-notifications@2019-09-09.yang -e replay
|
||||
+ --install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-subscribed-notifications@2019-09-09.yang -e replay -e subtree
|
||||
--install ${CMAKE_CURRENT_SOURCE_DIR}/tests/yang/ietf-yang-push@2019-09-09.yang -e on-change
|
||||
)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 255f861..0781890 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -137,17 +137,17 @@ public:
|
||||
const std::optional<FDHandling>& callbacks = std::nullopt);
|
||||
|
||||
[[nodiscard]] DynamicSubscription yangPushPeriodic(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
std::chrono::milliseconds periodTime,
|
||||
const std::optional<NotificationTimeStamp>& anchorTime = std::nullopt,
|
||||
const std::optional<NotificationTimeStamp>& stopTime = std::nullopt);
|
||||
[[nodiscard]] DynamicSubscription yangPushOnChange(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::chrono::milliseconds>& dampeningPeriod = std::nullopt,
|
||||
SyncOnStart syncOnStart = SyncOnStart::No,
|
||||
const std::optional<NotificationTimeStamp>& stopTime = std::nullopt);
|
||||
[[nodiscard]] DynamicSubscription subscribeNotifications(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::string>& stream = std::nullopt,
|
||||
const std::optional<NotificationTimeStamp>& stopTime = std::nullopt,
|
||||
const std::optional<NotificationTimeStamp>& startTime = std::nullopt);
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 2c27c92..1d65d70 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -42,6 +42,37 @@ libyang::DataNode wrapSrData(std::shared_ptr<sr_session_ctx_s> sess, sr_data_t*
|
||||
sr_release_data(data);
|
||||
}));
|
||||
}
|
||||
+
|
||||
+std::optional<std::string> constructXPathFilter(const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter)
|
||||
+{
|
||||
+ if (!filter) {
|
||||
+ return std::nullopt;
|
||||
+ }
|
||||
+
|
||||
+ if (std::holds_alternative<std::string>(*filter)) {
|
||||
+ return std::get<std::string>(*filter);
|
||||
+ }
|
||||
+
|
||||
+ auto node = std::get<libyang::DataNodeAny>(*filter);
|
||||
+ auto value = node.releaseValue();
|
||||
+
|
||||
+ if (!value) {
|
||||
+ return "/"; // select nothing, RFC 6241, 6.4.2
|
||||
+ }
|
||||
+
|
||||
+ if (std::holds_alternative<libyang::DataNode>(*value)) {
|
||||
+ char* str;
|
||||
+
|
||||
+ auto filterTree = std::get<libyang::DataNode>(*value);
|
||||
+ auto res = srsn_filter_subtree2xpath(libyang::getRawNode(filterTree), nullptr, &str);
|
||||
+ std::unique_ptr<char, decltype([](auto* p) constexpr { std::free(p); })> strDeleter(str); // pass ownership of c-string to the deleter
|
||||
+
|
||||
+ throwIfError(res, "Unable to convert subtree filter to xpath");
|
||||
+ return str;
|
||||
+ }
|
||||
+
|
||||
+ throw Error("Subtree filter anydata node must contain (possibly empty) libyang tree");
|
||||
+}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -526,9 +557,9 @@ Subscription Session::onNotification(
|
||||
/**
|
||||
* Subscribe for receiving notifications according to 'ietf-yang-push' YANG periodic subscriptions.
|
||||
*
|
||||
- * Wraps `srsn_yang_push_periodic`.
|
||||
+ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters.
|
||||
*
|
||||
- * @param xpathFilter Optional XPath that filters received notification.
|
||||
+ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny
|
||||
* @param periodTime Notification period.
|
||||
* @param anchorTime Optional anchor time for the period. Anchor time acts as a reference point for the period.
|
||||
* @param stopTime Optional stop time ending the notification subscription.
|
||||
@@ -536,7 +567,7 @@ Subscription Session::onNotification(
|
||||
* @return A YangPushSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::yangPushPeriodic(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
std::chrono::milliseconds periodTime,
|
||||
const std::optional<NotificationTimeStamp>& anchorTime,
|
||||
const std::optional<NotificationTimeStamp>& stopTime)
|
||||
@@ -545,6 +576,8 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
uint32_t subId;
|
||||
auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
|
||||
auto anchorSpec = anchorTime ? std::optional{toTimespec(*anchorTime)} : std::nullopt;
|
||||
+ auto xpathFilter = constructXPathFilter(filter);
|
||||
+
|
||||
auto res = srsn_yang_push_periodic(m_sess.get(),
|
||||
toDatastore(activeDatastore()),
|
||||
xpathFilter ? xpathFilter->c_str() : nullptr,
|
||||
@@ -561,9 +594,9 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
/**
|
||||
* Subscribe for receiving notifications according to 'ietf-yang-push' YANG on-change subscriptions.
|
||||
*
|
||||
- * Wraps `srsn_yang_push_on_change`.
|
||||
+ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters.
|
||||
*
|
||||
- * @param xpathFilter Optional XPath that filters received notification.
|
||||
+ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny
|
||||
* @param dampeningPeriod Optional dampening period.
|
||||
* @param syncOnStart Whether to start with a notification of the current state.
|
||||
* @param stopTime Optional stop time ending the notification subscription.
|
||||
@@ -571,7 +604,7 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
* @return A YangPushSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::yangPushOnChange(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::chrono::milliseconds>& dampeningPeriod,
|
||||
SyncOnStart syncOnStart,
|
||||
const std::optional<NotificationTimeStamp>& stopTime)
|
||||
@@ -579,6 +612,8 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
int fd;
|
||||
uint32_t subId;
|
||||
auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
|
||||
+ auto xpathFilter = constructXPathFilter(filter);
|
||||
+
|
||||
auto res = srsn_yang_push_on_change(m_sess.get(),
|
||||
toDatastore(activeDatastore()),
|
||||
xpathFilter ? xpathFilter->c_str() : nullptr,
|
||||
@@ -596,11 +631,11 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
}
|
||||
|
||||
/**
|
||||
- * Subscribe for receiving notifications according to 'ietf-subscribed-notifications'
|
||||
+ * Subscribe for receiving notifications according to 'ietf-subscribed-notifications'.
|
||||
*
|
||||
- * Wraps `srsn_subscribe.
|
||||
+ * Wraps `srsn_subscribe` and `srsn_filter_subtree2xpath` for subtree filters.
|
||||
*
|
||||
- * @param xpathFilter Optional XPath that filters received notification.
|
||||
+ * @param filter Optional filter for received notification, xpath filter for string type, subtree filter for libyang::DataNodeAny
|
||||
* @param stream Optional stream to subscribe to.
|
||||
* @param stopTime Optional stop time ending the subscription.
|
||||
* @param startTime Optional start time of the subscription, used for replaying stored notifications.
|
||||
@@ -608,7 +643,7 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
* @return A YangPushSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::subscribeNotifications(
|
||||
- const std::optional<std::string>& xpathFilter,
|
||||
+ const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::string>& stream,
|
||||
const std::optional<NotificationTimeStamp>& stopTime,
|
||||
const std::optional<NotificationTimeStamp>& startTime)
|
||||
@@ -618,10 +653,11 @@ DynamicSubscription Session::subscribeNotifications(
|
||||
auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
|
||||
auto startSpec = startTime ? std::optional{toTimespec(*startTime)} : std::nullopt;
|
||||
struct timespec replayStartSpec;
|
||||
+ auto xpathFilter = constructXPathFilter(filter);
|
||||
|
||||
auto res = srsn_subscribe(m_sess.get(),
|
||||
stream ? stream->c_str() : nullptr,
|
||||
- xpathFilter ? xpathFilter->c_str() : nullptr,
|
||||
+ xpathFilter ? xpathFilter->data() : nullptr,
|
||||
stopSpec ? &stopSpec.value() : nullptr,
|
||||
startSpec ? &startSpec.value() : nullptr,
|
||||
false,
|
||||
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
|
||||
index a44bfba..84a0880 100644
|
||||
--- a/tests/subscriptions-dynamic.cpp
|
||||
+++ b/tests/subscriptions-dynamic.cpp
|
||||
@@ -28,6 +28,9 @@
|
||||
#define REQUIRE_NOTIFICATION(SUBSCRIPTION, NOTIFICATION) \
|
||||
TROMPELOEIL_REQUIRE_CALL(rec, recordNotification(NOTIFICATION)).IN_SEQUENCE(seq);
|
||||
|
||||
+#define REQUIRE_NAMED_NOTIFICATION(SUBSCRIPTION, NOTIFICATION) \
|
||||
+ expectations.emplace_back(TROMPELOEIL_NAMED_REQUIRE_CALL(rec, recordNotification(NOTIFICATION)).IN_SEQUENCE(seq));
|
||||
+
|
||||
#define READ_NOTIFICATION(SUBSCRIPTION) \
|
||||
REQUIRE(pipeStatus((SUBSCRIPTION).fd()) == PipeStatus::DataReady); \
|
||||
(SUBSCRIPTION).processEvent(cbNotif);
|
||||
@@ -276,6 +279,89 @@ TEST_CASE("Dynamic subscriptions")
|
||||
const auto excMessage = "Couldn't terminate yang-push subscription with id " + std::to_string(sub->subscriptionId()) + ": SR_ERR_NOT_FOUND";
|
||||
REQUIRE_THROWS_WITH_AS(sub->terminate(), excMessage.c_str(), sysrepo::ErrorWithCode);
|
||||
}
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Filtering")
|
||||
+ {
|
||||
+ std::optional<sysrepo::DynamicSubscription> sub;
|
||||
+ std::vector<std::unique_ptr<trompeloeil::expectation>> expectations;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("xpath filter")
|
||||
+ {
|
||||
+ sub = sess.subscribeNotifications("/test_module:ping");
|
||||
+
|
||||
+ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("subtree filter")
|
||||
+ {
|
||||
+ libyang::CreatedNodes createdNodes;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("filter a node")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("XML")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ libyang::XML{"<ping xmlns='urn:ietf:params:xml:ns:yang:test_module' />"});
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("JSON")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ libyang::JSON{R"({"test_module:ping": {}})"});
|
||||
+ }
|
||||
+
|
||||
+ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("filter more top level nodes")
|
||||
+ {
|
||||
+ DOCTEST_SUBCASE("XML")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ libyang::XML{"<ping xmlns='urn:ietf:params:xml:ns:yang:test_module' />"
|
||||
+ "<silent-ping xmlns='urn:ietf:params:xml:ns:yang:test_module' />"});
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("JSON")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ libyang::JSON{R"({
|
||||
+ "test_module:ping": {},
|
||||
+ "test_module:silent-ping": {}
|
||||
+ })"});
|
||||
+ }
|
||||
+
|
||||
+ REQUIRE_NAMED_NOTIFICATION(sub, notifications[0]);
|
||||
+ REQUIRE_NAMED_NOTIFICATION(sub, notifications[1]);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("empty filter selects nothing")
|
||||
+ {
|
||||
+ createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/stream-subtree-filter",
|
||||
+ std::nullopt);
|
||||
+ }
|
||||
+
|
||||
+ sub = sess.subscribeNotifications(createdNodes.createdNode->asAny());
|
||||
+ }
|
||||
+
|
||||
+ CLIENT_SEND_NOTIFICATION(notifications[0]);
|
||||
+ CLIENT_SEND_NOTIFICATION(notifications[1]);
|
||||
+
|
||||
+ // read as many notifications as we expect
|
||||
+ for (size_t i = 0; i < expectations.size(); ++i) {
|
||||
+ READ_NOTIFICATION_BLOCKING(*sub);
|
||||
+ }
|
||||
+
|
||||
+ sub->terminate();
|
||||
+
|
||||
+ // ensure no more notifications were sent
|
||||
+ REQUIRE_PIPE_HANGUP(*sub);
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("YANG Push on change")
|
||||
@@ -285,9 +371,73 @@ TEST_CASE("Dynamic subscriptions")
|
||||
* between writing to sysrepo and reading the notifications.
|
||||
*/
|
||||
|
||||
- auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::Yes);
|
||||
+ DOCTEST_SUBCASE("Filters")
|
||||
+ {
|
||||
+ std::optional<sysrepo::DynamicSubscription> sub;
|
||||
+
|
||||
+ DOCTEST_SUBCASE("XPath filter")
|
||||
+ {
|
||||
+ sub = sess.yangPushOnChange("/test_module:leafInt32 | /test_module:popelnice/content/trash[name='asd']");
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Subtree filter")
|
||||
+ {
|
||||
+ auto createdNodes = sess.getContext().newPath2(
|
||||
+ "/ietf-subscribed-notifications:establish-subscription/ietf-yang-push:datastore-subtree-filter",
|
||||
+ libyang::XML{"<leafInt32 xmlns='http://example.com/' />"
|
||||
+ "<popelnice xmlns='http://example.com/'><content><trash><name>asd</name></trash></content></popelnice>"});
|
||||
+ sub = sess.yangPushOnChange(createdNodes.createdNode->asAny());
|
||||
+ }
|
||||
+
|
||||
+ client.setItem("/test_module:leafInt32", "42");
|
||||
+ client.setItem("/test_module:popelnice/s", "asd");
|
||||
+ client.setItem("/test_module:popelnice/content/trash[name='asd']", std::nullopt);
|
||||
+ client.applyChanges();
|
||||
|
||||
- REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
+ client.deleteItem("/test_module:popelnice/s");
|
||||
+ client.applyChanges();
|
||||
+
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(*sub, R"({
|
||||
+ "ietf-yang-push:push-change-update": {
|
||||
+ "datastore-changes": {
|
||||
+ "yang-patch": {
|
||||
+ "patch-id": "patch-1",
|
||||
+ "edit": [
|
||||
+ {
|
||||
+ "edit-id": "edit-1",
|
||||
+ "operation": "create",
|
||||
+ "target": "/test_module:leafInt32",
|
||||
+ "value": {
|
||||
+ "test_module:leafInt32": 42
|
||||
+ }
|
||||
+ },
|
||||
+ {
|
||||
+ "edit-id": "edit-2",
|
||||
+ "operation": "create",
|
||||
+ "target": "/test_module:popelnice/content/trash[name='asd']",
|
||||
+ "value": {
|
||||
+ "test_module:trash": {
|
||||
+ "name": "asd"
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+)");
|
||||
+ READ_YANG_PUSH_UPDATE(*sub);
|
||||
+
|
||||
+ sub->terminate();
|
||||
+ REQUIRE_PIPE_HANGUP(*sub);
|
||||
+ }
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Sync on start")
|
||||
+ {
|
||||
+ auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::Yes);
|
||||
+
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
"ietf-yang-push:push-update": {
|
||||
"datastore-contents": {
|
||||
"test_module:values": [
|
||||
@@ -298,14 +448,14 @@ TEST_CASE("Dynamic subscriptions")
|
||||
}
|
||||
}
|
||||
)");
|
||||
- READ_YANG_PUSH_UPDATE(sub);
|
||||
+ READ_YANG_PUSH_UPDATE(sub);
|
||||
|
||||
- client.setItem("/test_module:leafInt32", "123");
|
||||
- client.setItem("/test_module:values[.='5']", std::nullopt);
|
||||
- client.deleteItem("/test_module:values[.='3']");
|
||||
- client.applyChanges();
|
||||
+ client.setItem("/test_module:leafInt32", "123");
|
||||
+ client.setItem("/test_module:values[.='5']", std::nullopt);
|
||||
+ client.deleteItem("/test_module:values[.='3']");
|
||||
+ client.applyChanges();
|
||||
|
||||
- REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
"ietf-yang-push:push-change-update": {
|
||||
"datastore-changes": {
|
||||
"yang-patch": {
|
||||
@@ -342,10 +492,11 @@ TEST_CASE("Dynamic subscriptions")
|
||||
}
|
||||
}
|
||||
)");
|
||||
- READ_YANG_PUSH_UPDATE(sub);
|
||||
+ READ_YANG_PUSH_UPDATE(sub);
|
||||
|
||||
- sub.terminate();
|
||||
- REQUIRE_PIPE_HANGUP(sub);
|
||||
+ sub.terminate();
|
||||
+ REQUIRE_PIPE_HANGUP(sub);
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("YANG Push periodic")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
From b960058e37c471084538bb5db8b3b5e9e992c46c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 2 Apr 2025 21:47:21 +0200
|
||||
Subject: [PATCH 14/20] add excluded changes to YANG push on-change wrapper
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: Idb7bb48a3c60ff70286441cd4d0a5c1c28acee15
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Enum.hpp | 12 +++++++
|
||||
include/sysrepo-cpp/Session.hpp | 1 +
|
||||
src/Session.cpp | 12 ++++++-
|
||||
src/utils/enum.hpp | 8 +++++
|
||||
tests/subscriptions-dynamic.cpp | 60 +++++++++++++++++++++++++++++++++
|
||||
5 files changed, 92 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Enum.hpp b/include/sysrepo-cpp/Enum.hpp
|
||||
index cac1f2e..48467c6 100644
|
||||
--- a/include/sysrepo-cpp/Enum.hpp
|
||||
+++ b/include/sysrepo-cpp/Enum.hpp
|
||||
@@ -209,6 +209,18 @@ constexpr GetOptions operator|(const GetOptions a, const GetOptions b)
|
||||
return implEnumBitOr(a, b);
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Wraps `srsn_yang_push_change_t`
|
||||
+ */
|
||||
+enum class YangPushChange : uint32_t {
|
||||
+ Create = 0, /**< SRSN_YP_CHANGE_CREATE */
|
||||
+ Delete = 1, /**< SRSN_YP_CHANGE_DELETE */
|
||||
+ Insert = 2, /**< SRSN_YP_CHANGE_INSERT */
|
||||
+ Move = 3, /**< SRSN_YP_CHANGE_MOVE */
|
||||
+ Replace = 4, /**< SRSN_YP_CHANGE_REPLACE */
|
||||
+ EnumCount = 5, /**< SRSN_COUNT_YP_CHANGE */
|
||||
+};
|
||||
+
|
||||
std::ostream& operator<<(std::ostream& os, const NotificationType& type);
|
||||
std::ostream& operator<<(std::ostream& os, const Event& event);
|
||||
std::ostream& operator<<(std::ostream& os, const ChangeOperation& changeOp);
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 0781890..b7cad72 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -145,6 +145,7 @@ public:
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::chrono::milliseconds>& dampeningPeriod = std::nullopt,
|
||||
SyncOnStart syncOnStart = SyncOnStart::No,
|
||||
+ const std::set<YangPushChange>& excludedChanges = {},
|
||||
const std::optional<NotificationTimeStamp>& stopTime = std::nullopt);
|
||||
[[nodiscard]] DynamicSubscription subscribeNotifications(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 1d65d70..40c3df3 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -607,6 +607,7 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
const std::optional<std::chrono::milliseconds>& dampeningPeriod,
|
||||
SyncOnStart syncOnStart,
|
||||
+ const std::set<YangPushChange>& excludedChanges,
|
||||
const std::optional<NotificationTimeStamp>& stopTime)
|
||||
{
|
||||
int fd;
|
||||
@@ -614,12 +615,21 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
auto stopSpec = stopTime ? std::optional{toTimespec(*stopTime)} : std::nullopt;
|
||||
auto xpathFilter = constructXPathFilter(filter);
|
||||
|
||||
+ /* The enum values are not used as the other enum flags in sysrepo-cpp.
|
||||
+ * srsn_yang_push_on_change expects an integer array of size EnumCount with 0 or 1 values.
|
||||
+ */
|
||||
+ using YangPushChangeUnderlying = std::underlying_type_t<YangPushChange>;
|
||||
+ std::array<int, static_cast<YangPushChangeUnderlying>(YangPushChange::EnumCount)> excludedChangesArray{};
|
||||
+ for (const auto& change: excludedChanges) {
|
||||
+ excludedChangesArray[static_cast<YangPushChangeUnderlying>(change)] = 1;
|
||||
+ }
|
||||
+
|
||||
auto res = srsn_yang_push_on_change(m_sess.get(),
|
||||
toDatastore(activeDatastore()),
|
||||
xpathFilter ? xpathFilter->c_str() : nullptr,
|
||||
dampeningPeriod ? dampeningPeriod->count() : 0,
|
||||
syncOnStart == SyncOnStart::Yes,
|
||||
- nullptr,
|
||||
+ excludedChangesArray.data(),
|
||||
stopSpec ? &stopSpec.value() : nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
diff --git a/src/utils/enum.hpp b/src/utils/enum.hpp
|
||||
index a2dcf7a..2d3a298 100644
|
||||
--- a/src/utils/enum.hpp
|
||||
+++ b/src/utils/enum.hpp
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <type_traits>
|
||||
extern "C" {
|
||||
#include <sysrepo.h>
|
||||
+#include <sysrepo/subscribed_notifications.h>
|
||||
}
|
||||
#include <sysrepo-cpp/Enum.hpp>
|
||||
|
||||
@@ -171,4 +172,11 @@ constexpr sr_get_options_t toGetOptions(const GetOptions opts)
|
||||
{
|
||||
return static_cast<sr_get_options_t>(opts);
|
||||
}
|
||||
+
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_CREATE) == YangPushChange::Create);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_DELETE) == YangPushChange::Delete);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_INSERT) == YangPushChange::Insert);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_MOVE) == YangPushChange::Move);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_YP_CHANGE_REPLACE) == YangPushChange::Replace);
|
||||
+static_assert(static_cast<YangPushChange>(SRSN_COUNT_YP_CHANGE) == YangPushChange::EnumCount);
|
||||
}
|
||||
diff --git a/tests/subscriptions-dynamic.cpp b/tests/subscriptions-dynamic.cpp
|
||||
index 84a0880..5ed173b 100644
|
||||
--- a/tests/subscriptions-dynamic.cpp
|
||||
+++ b/tests/subscriptions-dynamic.cpp
|
||||
@@ -497,6 +497,66 @@ TEST_CASE("Dynamic subscriptions")
|
||||
sub.terminate();
|
||||
REQUIRE_PIPE_HANGUP(sub);
|
||||
}
|
||||
+
|
||||
+ DOCTEST_SUBCASE("Excluded changes")
|
||||
+ {
|
||||
+ auto sub = sess.yangPushOnChange(std::nullopt, std::nullopt, sysrepo::SyncOnStart::No, {sysrepo::YangPushChange::Create});
|
||||
+
|
||||
+ client.setItem("/test_module:leafInt32", "123");
|
||||
+ client.applyChanges(); // excluded (create)
|
||||
+ client.setItem("/test_module:leafInt32", "124");
|
||||
+ client.applyChanges();
|
||||
+ client.setItem("/test_module:leafInt32", "125");
|
||||
+ client.applyChanges();
|
||||
+
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
+ "ietf-yang-push:push-change-update": {
|
||||
+ "datastore-changes": {
|
||||
+ "yang-patch": {
|
||||
+ "patch-id": "patch-1",
|
||||
+ "edit": [
|
||||
+ {
|
||||
+ "edit-id": "edit-1",
|
||||
+ "operation": "replace",
|
||||
+ "target": "/test_module:leafInt32",
|
||||
+ "value": {
|
||||
+ "test_module:leafInt32": 124
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+)");
|
||||
+
|
||||
+ READ_YANG_PUSH_UPDATE(sub);
|
||||
+
|
||||
+ REQUIRE_YANG_PUSH_UPDATE(sub, R"({
|
||||
+ "ietf-yang-push:push-change-update": {
|
||||
+ "datastore-changes": {
|
||||
+ "yang-patch": {
|
||||
+ "patch-id": "patch-2",
|
||||
+ "edit": [
|
||||
+ {
|
||||
+ "edit-id": "edit-1",
|
||||
+ "operation": "replace",
|
||||
+ "target": "/test_module:leafInt32",
|
||||
+ "value": {
|
||||
+ "test_module:leafInt32": 125
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+)");
|
||||
+ READ_YANG_PUSH_UPDATE(sub);
|
||||
+
|
||||
+ sub.terminate();
|
||||
+ REQUIRE_PIPE_HANGUP(sub);
|
||||
+ }
|
||||
}
|
||||
|
||||
DOCTEST_SUBCASE("YANG Push periodic")
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
From 3407f98d147ffbf031725102181e1fe3cd874363 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 8 Apr 2025 12:52:00 +0200
|
||||
Subject: [PATCH 15/20] wrap sr_nacm_get_user
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
This adds a counterpart to setting NACM user. Sometimes, it might be
|
||||
useful to get it as well.
|
||||
|
||||
Change-Id: I534a229eb536d63091904ab4f6268cb9da6dd3db
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Session.hpp | 1 +
|
||||
src/Session.cpp | 9 +++++++++
|
||||
tests/session.cpp | 7 +++++++
|
||||
3 files changed, 17 insertions(+)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index b7cad72..1c409b8 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -100,6 +100,7 @@ public:
|
||||
void replaceConfig(std::optional<libyang::DataNode> config, const std::optional<std::string>& moduleName = std::nullopt, std::chrono::milliseconds timeout = std::chrono::milliseconds{0});
|
||||
|
||||
void setNacmUser(const std::string& user);
|
||||
+ std::optional<std::string> getNacmUser() const;
|
||||
[[nodiscard]] Subscription initNacm(
|
||||
SubscribeOptions opts = SubscribeOptions::Default,
|
||||
ExceptionHandler handler = nullptr,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 40c3df3..2273c79 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -706,6 +706,15 @@ void Session::setNacmUser(const std::string& user)
|
||||
throwIfError(res, "Couldn't set NACM user", m_sess.get());
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Get the NACM user for this session.
|
||||
+ */
|
||||
+std::optional<std::string> Session::getNacmUser() const
|
||||
+{
|
||||
+ auto* username = sr_nacm_get_user(m_sess.get());
|
||||
+ return username ? std::make_optional<std::string>(username) : std::nullopt;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Initializes NACM callbacks.
|
||||
*
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 9a419a1..548b371 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -390,10 +390,14 @@ TEST_CASE("session")
|
||||
auto data = sess.getData("/test_module:denyAllLeaf");
|
||||
REQUIRE(data.value().findPath("/test_module:denyAllLeaf").value().asTerm().valueStr() == "AHOJ");
|
||||
|
||||
+ REQUIRE(!sess.getNacmUser());
|
||||
+
|
||||
// check that repeated NACM initialization still works
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
auto nacmSub = sess.initNacm();
|
||||
sess.setNacmUser("nobody");
|
||||
+ REQUIRE(sess.getNacmUser() == "nobody");
|
||||
+
|
||||
data = sess.getData("/test_module:denyAllLeaf");
|
||||
// After turning on NACM, we can't access the leaf.
|
||||
REQUIRE(!data);
|
||||
@@ -408,6 +412,9 @@ TEST_CASE("session")
|
||||
sysrepo::ErrorWithCode);
|
||||
}
|
||||
|
||||
+ REQUIRE(!!sess.getNacmUser());
|
||||
+ REQUIRE(sess.getNacmUser() == "nobody");
|
||||
+
|
||||
// duplicate NACM initialization should throw
|
||||
auto nacm = sess.initNacm();
|
||||
REQUIRE_THROWS_WITH_AS(auto x = sess.initNacm(),
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
From afc4b8e04b4bf61d6bf0e0a66c1115b0111eaebf Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 15:36:30 +0200
|
||||
Subject: [PATCH 16/20] CI: renamed project upstream
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Change-Id: I5447f243297fbfde7c364eb3919b00db239bd069
|
||||
Depends-on: https://gerrit.cesnet.cz/c/CzechLight/libyang-cpp/+/8570
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
.zuul.yaml | 4 ++--
|
||||
README.md | 2 +-
|
||||
ci/build.sh | 2 +-
|
||||
3 files changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/.zuul.yaml b/.zuul.yaml
|
||||
index dc87c6f..7ada6f7 100644
|
||||
--- a/.zuul.yaml
|
||||
+++ b/.zuul.yaml
|
||||
@@ -7,7 +7,7 @@
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.4.8
|
||||
- name: github/rollbear/trompeloeil
|
||||
override-checkout: v44
|
||||
@@ -18,7 +18,7 @@
|
||||
override-checkout: devel
|
||||
- name: github/sysrepo/sysrepo
|
||||
override-checkout: devel
|
||||
- - name: github/onqtam/doctest
|
||||
+ - name: github/doctest/doctest
|
||||
override-checkout: v2.4.11
|
||||
- name: github/rollbear/trompeloeil
|
||||
override-checkout: v44
|
||||
diff --git a/README.md b/README.md
|
||||
index 27dc748..dd9570c 100644
|
||||
--- a/README.md
|
||||
+++ b/README.md
|
||||
@@ -12,7 +12,7 @@ It uses RAII for automatic memory management.
|
||||
- [libyang-cpp](https://github.com/CESNET/libyang-cpp) - C++ bindings for *libyang*
|
||||
- C++20 compiler (e.g., GCC 10.x+, clang 10+)
|
||||
- CMake 3.19+
|
||||
-- optionally for built-in tests, [Doctest](https://github.com/onqtam/doctest/) as a C++ unit test framework
|
||||
+- optionally for built-in tests, [Doctest](https://github.com/doctest/doctest/) as a C++ unit test framework
|
||||
- optionally for built-in tests, [trompeloeil](https://github.com/rollbear/trompeloeil) for mock objects in C++
|
||||
- optionally for the docs, Doxygen
|
||||
|
||||
diff --git a/ci/build.sh b/ci/build.sh
|
||||
index d886b79..785a811 100755
|
||||
--- a/ci/build.sh
|
||||
+++ b/ci/build.sh
|
||||
@@ -78,7 +78,7 @@ build_n_test() {
|
||||
|
||||
build_n_test github/CESNET/libyang -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF
|
||||
build_n_test github/sysrepo/sysrepo -DENABLE_BUILD_TESTS=ON -DENABLE_VALGRIND_TESTS=OFF -DREPO_PATH=${PREFIX}/etc-sysrepo
|
||||
-build_n_test github/onqtam/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
+build_n_test github/doctest/doctest -DDOCTEST_WITH_TESTS=OFF
|
||||
# non-release builds download Catch2
|
||||
CMAKE_BUILD_TYPE=Release build_n_test github/rollbear/trompeloeil
|
||||
build_n_test CzechLight/libyang-cpp -DBUILD_TESTING=ON
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
From ad070266f9347f159874e6b2fa57302385f354e3 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Wed, 9 Apr 2025 14:51:31 +0200
|
||||
Subject: [PATCH 17/20] wrap sr_nacm_check_operation
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Sysrepo provides API function for explicit checking if an operation can
|
||||
be authorized on a node. This might come handy if one decides to do NACM
|
||||
authorization himself, like we will do in our RESTCONF server.
|
||||
|
||||
Change-Id: Ida41514a7f03ab120a331363b7f9ed8b69918d88
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Session.hpp | 1 +
|
||||
src/Session.cpp | 13 ++++++++++
|
||||
tests/session.cpp | 46 +++++++++++++++++++++++++++++++++
|
||||
tests/test_module.yang | 4 +++
|
||||
4 files changed, 64 insertions(+)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 1c409b8..2112d21 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -101,6 +101,7 @@ public:
|
||||
|
||||
void setNacmUser(const std::string& user);
|
||||
std::optional<std::string> getNacmUser() const;
|
||||
+ bool checkNacmOperation(const libyang::DataNode& node) const;
|
||||
[[nodiscard]] Subscription initNacm(
|
||||
SubscribeOptions opts = SubscribeOptions::Default,
|
||||
ExceptionHandler handler = nullptr,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 2273c79..ce0dec2 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -715,6 +715,19 @@ std::optional<std::string> Session::getNacmUser() const
|
||||
return username ? std::make_optional<std::string>(username) : std::nullopt;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Checks if operation is allowed for current NACM user. Wraps `sr_nacm_check_operation`.
|
||||
+ * @return true if the current user is authorized to perform operation on given @p node.
|
||||
+ *
|
||||
+ * Details on unsuccessfull authorizations can be retrieved via Session::getErrors.
|
||||
+ * Note that if the NACM user is not set, `sr_nacm_check_operation` and this function both return true.
|
||||
+ */
|
||||
+bool Session::checkNacmOperation(const libyang::DataNode& node) const
|
||||
+{
|
||||
+ auto res = sr_nacm_check_operation(m_sess.get(), libyang::getRawNode(node));
|
||||
+ return res == SR_ERR_OK;
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Initializes NACM callbacks.
|
||||
*
|
||||
diff --git a/tests/session.cpp b/tests/session.cpp
|
||||
index 548b371..35dd545 100644
|
||||
--- a/tests/session.cpp
|
||||
+++ b/tests/session.cpp
|
||||
@@ -423,6 +423,52 @@ TEST_CASE("session")
|
||||
sysrepo::ErrorWithCode);
|
||||
}
|
||||
|
||||
+ DOCTEST_SUBCASE("Session::checkNacmOperation")
|
||||
+ {
|
||||
+ auto nacmSub = sess.initNacm();
|
||||
+
|
||||
+ // check NACM access for RPCs
|
||||
+ auto shutdownRPC = sess.getContext().newPath("/test_module:shutdown", std::nullopt);
|
||||
+ auto denyAllRPC = sess.getContext().newPath("/test_module:deny-all-rpc", std::nullopt);
|
||||
+
|
||||
+ // user not set, everything is permitted
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true);
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true);
|
||||
+ REQUIRE(sess.getErrors().size() == 0);
|
||||
+
|
||||
+ sess.setNacmUser("root");
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true);
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true);
|
||||
+ REQUIRE(sess.getErrors().size() == 0);
|
||||
+
|
||||
+ sess.setNacmUser("nobody");
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true);
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == false);
|
||||
+ REQUIRE(sess.getErrors().size() == 1);
|
||||
+ REQUIRE(sess.getErrors().at(0) == sysrepo::ErrorInfo{
|
||||
+ .code = sysrepo::ErrorCode::Unauthorized,
|
||||
+ .errorMessage = "Executing the operation is denied because \"nobody\" NACM authorization failed.",
|
||||
+ });
|
||||
+
|
||||
+ sess.setNacmUser("root"); // 'nobody' is not authorized to write into this subtree
|
||||
+ sess.switchDatastore(sysrepo::Datastore::Running);
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/enable-external-groups", "false");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/groups/group[name='grp']/user-name[.='nobody']", "");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/group[.='grp']", "");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/module-name", "test_module");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/access-operations", "*");
|
||||
+ sess.setItem("/ietf-netconf-acm:nacm/rule-list[name='rule']/rule[name='1']/action", "deny");
|
||||
+ sess.applyChanges();
|
||||
+
|
||||
+ sess.setNacmUser("root");
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == true);
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == true);
|
||||
+
|
||||
+ sess.setNacmUser("nobody");
|
||||
+ REQUIRE(sess.checkNacmOperation(denyAllRPC) == false);
|
||||
+ REQUIRE(sess.checkNacmOperation(shutdownRPC) == false);
|
||||
+ }
|
||||
+
|
||||
DOCTEST_SUBCASE("Session::getPendingChanges")
|
||||
{
|
||||
REQUIRE(sess.getPendingChanges() == std::nullopt);
|
||||
diff --git a/tests/test_module.yang b/tests/test_module.yang
|
||||
index 02c467b..3d6c26d 100644
|
||||
--- a/tests/test_module.yang
|
||||
+++ b/tests/test_module.yang
|
||||
@@ -55,6 +55,10 @@ module test_module {
|
||||
rpc noop {
|
||||
}
|
||||
|
||||
+ rpc deny-all-rpc {
|
||||
+ nacm:default-deny-all;
|
||||
+ }
|
||||
+
|
||||
rpc shutdown {
|
||||
output {
|
||||
leaf success {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
From cecf636eecea53a597bcbf75eb4dda0f916e9967 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 15 Apr 2025 10:54:11 +0200
|
||||
Subject: [PATCH 18/20] Fix return value type in doxygen
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Fixes: 415c401 ("wrap dynamic subscription functions")
|
||||
Change-Id: I66912530d8aa16bb4dde3d35611be1e6e4247b36
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/Session.cpp | 6 +++---
|
||||
1 file changed, 3 insertions(+), 3 deletions(-)
|
||||
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index ce0dec2..8258295 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -564,7 +564,7 @@ Subscription Session::onNotification(
|
||||
* @param anchorTime Optional anchor time for the period. Anchor time acts as a reference point for the period.
|
||||
* @param stopTime Optional stop time ending the notification subscription.
|
||||
*
|
||||
- * @return A YangPushSubscription handle.
|
||||
+ * @return A DynamicSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::yangPushPeriodic(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
@@ -601,7 +601,7 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
* @param syncOnStart Whether to start with a notification of the current state.
|
||||
* @param stopTime Optional stop time ending the notification subscription.
|
||||
*
|
||||
- * @return A YangPushSubscription handle.
|
||||
+ * @return A DynamicSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::yangPushOnChange(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
@@ -650,7 +650,7 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
* @param stopTime Optional stop time ending the subscription.
|
||||
* @param startTime Optional start time of the subscription, used for replaying stored notifications.
|
||||
*
|
||||
- * @return A YangPushSubscription handle.
|
||||
+ * @return A DynamicSubscription handle.
|
||||
*/
|
||||
DynamicSubscription Session::subscribeNotifications(
|
||||
const std::optional<std::variant<std::string, libyang::DataNodeAny>>& filter,
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
From ee53d86929296c4824e9860f6425850f7d795302 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 15 Apr 2025 11:06:50 +0200
|
||||
Subject: [PATCH 19/20] add a session getter to dynamic subscriptions
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Sometimes it might be useful to get the session which created the
|
||||
subscription (e.g. to do some work in processEvent callback). There was
|
||||
no easy way to do it up until now because the session was not stored as
|
||||
sysrepo::Session.
|
||||
However, it seems quite easy to change that and add the getter.
|
||||
|
||||
Change-Id: Ia943a47debba6f84c3faad5ea48a69da68dac874
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Subscription.hpp | 3 ++-
|
||||
src/Session.cpp | 6 +++---
|
||||
src/Subscription.cpp | 17 +++++++++++------
|
||||
3 files changed, 16 insertions(+), 10 deletions(-)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Subscription.hpp b/include/sysrepo-cpp/Subscription.hpp
|
||||
index 7fdc08b..4c58e78 100644
|
||||
--- a/include/sysrepo-cpp/Subscription.hpp
|
||||
+++ b/include/sysrepo-cpp/Subscription.hpp
|
||||
@@ -303,6 +303,7 @@ public:
|
||||
DynamicSubscription& operator=(DynamicSubscription&&) noexcept;
|
||||
~DynamicSubscription();
|
||||
|
||||
+ sysrepo::Session getSession() const;
|
||||
int fd() const;
|
||||
uint64_t subscriptionId() const;
|
||||
std::optional<NotificationTimeStamp> replayStartTime() const;
|
||||
@@ -310,7 +311,7 @@ public:
|
||||
void terminate(const std::optional<std::string>& reason = std::nullopt);
|
||||
|
||||
private:
|
||||
- DynamicSubscription(std::shared_ptr<sr_session_ctx_s> sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime = std::nullopt);
|
||||
+ DynamicSubscription(sysrepo::Session sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime = std::nullopt);
|
||||
|
||||
struct Data;
|
||||
std::unique_ptr<Data> m_data;
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 8258295..7d44662 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -588,7 +588,7 @@ DynamicSubscription Session::yangPushPeriodic(
|
||||
&subId);
|
||||
throwIfError(res, "Couldn't create yang-push periodic subscription", m_sess.get());
|
||||
|
||||
- return {m_sess, fd, subId};
|
||||
+ return {*this, fd, subId};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -637,7 +637,7 @@ DynamicSubscription Session::yangPushOnChange(
|
||||
&subId);
|
||||
throwIfError(res, "Couldn't create yang-push on-change subscription", m_sess.get());
|
||||
|
||||
- return {m_sess, fd, subId};
|
||||
+ return {*this, fd, subId};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -683,7 +683,7 @@ DynamicSubscription Session::subscribeNotifications(
|
||||
replayStart = toTimePoint(replayStartSpec);
|
||||
}
|
||||
|
||||
- return {m_sess, fd, subId, replayStart};
|
||||
+ return {*this, fd, subId, replayStart};
|
||||
}
|
||||
|
||||
/**
|
||||
diff --git a/src/Subscription.cpp b/src/Subscription.cpp
|
||||
index ab27b16..6dccaee 100644
|
||||
--- a/src/Subscription.cpp
|
||||
+++ b/src/Subscription.cpp
|
||||
@@ -414,18 +414,18 @@ bool ChangeIterator::operator==(const ChangeIterator& other) const
|
||||
|
||||
|
||||
struct DynamicSubscription::Data {
|
||||
- std::shared_ptr<sr_session_ctx_s> sess;
|
||||
+ sysrepo::Session sess;
|
||||
int fd;
|
||||
uint64_t subId;
|
||||
std::optional<NotificationTimeStamp> m_replayStartTime;
|
||||
bool m_terminated;
|
||||
|
||||
- Data(std::shared_ptr<sr_session_ctx_s> sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime, bool terminated);
|
||||
+ Data(sysrepo::Session sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime, bool terminated);
|
||||
~Data();
|
||||
void terminate(const std::optional<std::string>& reason = std::nullopt);
|
||||
};
|
||||
|
||||
-DynamicSubscription::DynamicSubscription(std::shared_ptr<sr_session_ctx_s> sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime)
|
||||
+DynamicSubscription::DynamicSubscription(sysrepo::Session sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime)
|
||||
: m_data(std::make_unique<Data>(std::move(sess), fd, subId, replayStartTime, false))
|
||||
{
|
||||
}
|
||||
@@ -434,6 +434,12 @@ DynamicSubscription::DynamicSubscription(DynamicSubscription&&) noexcept = defau
|
||||
DynamicSubscription& DynamicSubscription::operator=(DynamicSubscription&&) noexcept = default;
|
||||
DynamicSubscription::~DynamicSubscription() = default;
|
||||
|
||||
+/** @brief Returns sysrepo Session associated with this subscription */
|
||||
+sysrepo::Session DynamicSubscription::getSession() const
|
||||
+{
|
||||
+ return m_data->sess;
|
||||
+}
|
||||
+
|
||||
/** @brief Returns the file descriptor associated with this subscription. */
|
||||
int DynamicSubscription::fd() const
|
||||
{
|
||||
@@ -472,9 +478,8 @@ void DynamicSubscription::processEvent(YangPushNotifCb cb) const
|
||||
{
|
||||
struct timespec timestamp;
|
||||
struct lyd_node* tree;
|
||||
- auto ctx = std::unique_ptr<const ly_ctx, std::function<void(const ly_ctx*)>>(sr_session_acquire_context(m_data->sess.get()), [&](const ly_ctx*) { sr_session_release_context(m_data->sess.get()); });
|
||||
|
||||
- auto err = srsn_read_notif(fd(), ctx.get(), ×tamp, &tree);
|
||||
+ auto err = srsn_read_notif(fd(), libyang::retrieveContext(m_data->sess.getContext()), ×tamp, &tree);
|
||||
throwIfError(err, "Couldn't read yang-push notification");
|
||||
|
||||
const auto wrappedNotification = tree ? std::optional{libyang::wrapRawNode(tree)} : std::nullopt;
|
||||
@@ -488,7 +493,7 @@ void DynamicSubscription::processEvent(YangPushNotifCb cb) const
|
||||
cb(wrappedNotification, toTimePoint(timestamp));
|
||||
}
|
||||
|
||||
-DynamicSubscription::Data::Data(std::shared_ptr<sr_session_ctx_s> sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime, bool terminated)
|
||||
+DynamicSubscription::Data::Data(sysrepo::Session sess, int fd, uint64_t subId, const std::optional<NotificationTimeStamp>& replayStartTime, bool terminated)
|
||||
: sess(std::move(sess))
|
||||
, fd(fd)
|
||||
, subId(subId)
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
From 67eed65414cb9ccd031e16ec76149cc09c341186 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Pecka?= <tomas.pecka@cesnet.cz>
|
||||
Date: Tue, 15 Apr 2025 15:26:30 +0200
|
||||
Subject: [PATCH 20/20] wrap sr_nacm_get_recovery_user
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
The function does not actually need a session, I am only adding it to
|
||||
Session so it is in the same place as other NACM user methods.
|
||||
|
||||
Change-Id: I3ee015b2bdc180ab1a9115ffc1a3a53f8e6f677c
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
include/sysrepo-cpp/Session.hpp | 1 +
|
||||
src/Session.cpp | 10 ++++++++++
|
||||
2 files changed, 11 insertions(+)
|
||||
|
||||
diff --git a/include/sysrepo-cpp/Session.hpp b/include/sysrepo-cpp/Session.hpp
|
||||
index 2112d21..2612e86 100644
|
||||
--- a/include/sysrepo-cpp/Session.hpp
|
||||
+++ b/include/sysrepo-cpp/Session.hpp
|
||||
@@ -101,6 +101,7 @@ public:
|
||||
|
||||
void setNacmUser(const std::string& user);
|
||||
std::optional<std::string> getNacmUser() const;
|
||||
+ static std::string getNacmRecoveryUser();
|
||||
bool checkNacmOperation(const libyang::DataNode& node) const;
|
||||
[[nodiscard]] Subscription initNacm(
|
||||
SubscribeOptions opts = SubscribeOptions::Default,
|
||||
diff --git a/src/Session.cpp b/src/Session.cpp
|
||||
index 7d44662..e96e6ec 100644
|
||||
--- a/src/Session.cpp
|
||||
+++ b/src/Session.cpp
|
||||
@@ -715,6 +715,16 @@ std::optional<std::string> Session::getNacmUser() const
|
||||
return username ? std::make_optional<std::string>(username) : std::nullopt;
|
||||
}
|
||||
|
||||
+/**
|
||||
+ * @brief Get the sysrepo NACM recovery user.
|
||||
+ *
|
||||
+ * wraps `sr_nacm_get_recovery_user`.
|
||||
+ */
|
||||
+std::string Session::getNacmRecoveryUser()
|
||||
+{
|
||||
+ return sr_nacm_get_recovery_user();
|
||||
+}
|
||||
+
|
||||
/**
|
||||
* @brief Checks if operation is allowed for current NACM user. Wraps `sr_nacm_check_operation`.
|
||||
* @return true if the current user is authorized to perform operation on given @p node.
|
||||
--
|
||||
2.43.0
|
||||
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
From d2e4e60838761e2bdd02d651b8e0bea47cc2dcb5 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||
Date: Tue, 2 Jul 2024 14:56:15 +0200
|
||||
Subject: [PATCH 2/4] Disable local users (backwards compat with older model)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
|
||||
Drop local-users, because if enabled netopeer require all users that
|
||||
should be allowed to use NETCONF to also be configured in the updated
|
||||
ietf-netconf-server model.
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
|
||||
---
|
||||
scripts/common.sh | 2 +-
|
||||
scripts/merge_config.sh | 3 ---
|
||||
2 files changed, 1 insertion(+), 4 deletions(-)
|
||||
|
||||
diff --git a/scripts/common.sh b/scripts/common.sh
|
||||
index 25e531b..13234e1 100644
|
||||
--- a/scripts/common.sh
|
||||
+++ b/scripts/common.sh
|
||||
@@ -29,7 +29,7 @@ LN2_MODULES=(
|
||||
"ietf-tcp-server@2023-12-28.yang -e tcp-server-keepalives"
|
||||
"ietf-tcp-client@2023-12-28.yang -e local-binding-supported -e tcp-client-keepalives"
|
||||
"ietf-ssh-common@2023-12-28.yang -e transport-params"
|
||||
-"ietf-ssh-server@2023-12-28.yang -e local-users-supported -e local-user-auth-publickey -e local-user-auth-password -e local-user-auth-none"
|
||||
+"ietf-ssh-server@2023-12-28.yang"
|
||||
"ietf-tls-common@2023-12-28.yang -e tls10 -e tls11 -e tls12 -e tls13 -e hello-params"
|
||||
"ietf-tls-server@2023-12-28.yang -e server-ident-x509-cert -e client-auth-supported -e client-auth-x509-cert"
|
||||
"ietf-netconf-server@2023-12-28.yang -e ssh-listen -e tls-listen -e ssh-call-home -e tls-call-home -e central-netconf-server-supported"
|
||||
diff --git a/scripts/merge_config.sh b/scripts/merge_config.sh
|
||||
index eddada7..61af617 100755
|
||||
--- a/scripts/merge_config.sh
|
||||
+++ b/scripts/merge_config.sh
|
||||
@@ -89,9 +89,6 @@ CONFIG="<netconf-server xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-server\
|
||||
</public-key>
|
||||
</host-key>
|
||||
</server-identity>
|
||||
- <client-authentication>
|
||||
- ${USERS_CONFIG}
|
||||
- </client-authentication>
|
||||
</ssh-server-parameters>
|
||||
</ssh>
|
||||
</endpoint>
|
||||
--
|
||||
2.43.0
|
||||
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
From 2950046cd17bf9296a0b70b8f6b38114f6985864 Mon Sep 17 00:00:00 2001
|
||||
From c62a0c72ac2cc7c840f6004c274c37bf8ce6c854 Mon Sep 17 00:00:00 2001
|
||||
From: Joachim Wiberg <troglobit@gmail.com>
|
||||
Date: Thu, 22 Jun 2023 10:24:57 +0200
|
||||
Subject: [PATCH 1/4] Allow 'factory' as copy-from (only) in rpc copy-config
|
||||
Subject: [PATCH 1/2] Allow 'factory' as copy-from (only) in rpc copy-config
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -14,10 +14,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/src/netconf.c b/src/netconf.c
|
||||
index f1cc6b3..fdd7297 100644
|
||||
index 1d9ba79..2a6f1a4 100644
|
||||
--- a/src/netconf.c
|
||||
+++ b/src/netconf.c
|
||||
@@ -369,6 +369,8 @@ np2srv_rpc_copyconfig_cb(sr_session_ctx_t *session, uint32_t UNUSED(sub_id), con
|
||||
@@ -334,6 +334,8 @@ np2srv_rpc_copyconfig_cb(const struct lyd_node *rpc, struct np_user_sess *user_s
|
||||
ds = SR_DS_STARTUP;
|
||||
} else if (!strcmp(nodeset->dnodes[0]->schema->name, "candidate")) {
|
||||
ds = SR_DS_CANDIDATE;
|
||||
+4
-4
@@ -1,7 +1,7 @@
|
||||
From 2f09813c91def7672c036c7f713302cb3a12cf18 Mon Sep 17 00:00:00 2001
|
||||
From 15bff587a9096354f20803c78fbae729f50f8d6c Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Mattias=20Walstr=C3=B6m?= <lazzer@gmail.com>
|
||||
Date: Tue, 4 Feb 2025 20:14:50 +0100
|
||||
Subject: [PATCH 4/4] Do not generate data in sysrepo
|
||||
Subject: [PATCH 2/2] Do not generate data in sysrepo
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
@@ -16,10 +16,10 @@ Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
1 file changed, 40 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 4341be1..f1c4ae8 100644
|
||||
index 94dc315..66d69d5 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -383,47 +383,7 @@ if(SYSREPO_SETUP)
|
||||
@@ -386,47 +386,7 @@ if(SYSREPO_SETUP)
|
||||
message(FATAL_ERROR \" OUTPUT:\\n \${CMD_OUT_F}\\n ERROR:\\n \${CMD_ERR_F}\")
|
||||
endif()
|
||||
")
|
||||
@@ -1,36 +0,0 @@
|
||||
From 2137bc0d614ad0aeabc37fd0041d05bc341a9007 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Fri, 10 Jan 2025 08:55:38 +0100
|
||||
Subject: [PATCH 01/22] modinfo BUGFIX add NP containers to push oper data
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
... so that oper get subscriptions for the
|
||||
containers are called.
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/modinfo.c | 5 +++++
|
||||
1 file changed, 5 insertions(+)
|
||||
|
||||
diff --git a/src/modinfo.c b/src/modinfo.c
|
||||
index a58717f8..14d0b2ae 100644
|
||||
--- a/src/modinfo.c
|
||||
+++ b/src/modinfo.c
|
||||
@@ -1459,6 +1459,11 @@ sr_module_oper_data_update(struct sr_mod_info_mod_s *mod, const char *orig_name,
|
||||
if ((err_info = sr_module_oper_data_load(mod, conn, 0, NULL, data))) {
|
||||
return err_info;
|
||||
}
|
||||
+
|
||||
+ /* add any missing NP containers in the data */
|
||||
+ if ((err_info = sr_lyd_new_implicit_module(data, mod->ly_mod, LYD_IMPLICIT_NO_DEFAULTS, NULL))) {
|
||||
+ return err_info;
|
||||
+ }
|
||||
}
|
||||
|
||||
if (get_oper_opts & SR_OPER_NO_SUBS) {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
From d4cded05509b63083283ba3a9104b27663c7eb39 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Fri, 10 Jan 2025 08:56:29 +0100
|
||||
Subject: [PATCH 02/22] sysrepo BUGFIX wrong param
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/sysrepo.c | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||
index 844e11a5..7c96243c 100644
|
||||
--- a/src/sysrepo.c
|
||||
+++ b/src/sysrepo.c
|
||||
@@ -3578,7 +3578,7 @@ sr_delete_item(sr_session_ctx_t *session, const char *path, const sr_edit_option
|
||||
/* just delete the selected node */
|
||||
node = NULL;
|
||||
if (session->dt[session->ds].edit->tree &&
|
||||
- (err_info = sr_lyd_find_path(session->dt[session->ds].edit->tree, path, 1, &node))) {
|
||||
+ (err_info = sr_lyd_find_path(session->dt[session->ds].edit->tree, path, 0, &node))) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (node) {
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
From 526da167043615ab9a0a8d36d4dd4a92644a0f58 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Fri, 10 Jan 2025 08:56:54 +0100
|
||||
Subject: [PATCH 03/22] SOVERSION bump to version 7.29.11
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 84990130..4ecfe4d9 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -62,7 +62,7 @@ set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_
|
||||
# with backward compatible change and micro version is connected with any internal change of the library.
|
||||
set(SYSREPO_MAJOR_SOVERSION 7)
|
||||
set(SYSREPO_MINOR_SOVERSION 29)
|
||||
-set(SYSREPO_MICRO_SOVERSION 10)
|
||||
+set(SYSREPO_MICRO_SOVERSION 11)
|
||||
set(SYSREPO_SOVERSION_FULL ${SYSREPO_MAJOR_SOVERSION}.${SYSREPO_MINOR_SOVERSION}.${SYSREPO_MICRO_SOVERSION})
|
||||
set(SYSREPO_SOVERSION ${SYSREPO_MAJOR_SOVERSION})
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
From 880e15c69312a471c85a8f125e5c0ade3a93dcb1 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Fri, 10 Jan 2025 08:57:04 +0100
|
||||
Subject: [PATCH 04/22] VERSION bump to version 3.3.11
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 4ecfe4d9..9f3cfde0 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -54,7 +54,7 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
|
||||
# micro version is changed with a set of small changes or bugfixes anywhere in the project.
|
||||
set(SYSREPO_MAJOR_VERSION 3)
|
||||
set(SYSREPO_MINOR_VERSION 3)
|
||||
-set(SYSREPO_MICRO_VERSION 10)
|
||||
+set(SYSREPO_MICRO_VERSION 11)
|
||||
set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_MICRO_VERSION})
|
||||
|
||||
# Version of the library
|
||||
--
|
||||
2.43.0
|
||||
|
||||
-963
@@ -1,963 +0,0 @@
|
||||
From 31af3459bd8ea24228e2b13193176af6c34c289e Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Mon, 13 Jan 2025 11:19:30 +0100
|
||||
Subject: [PATCH 05/22] sysrepo UPDATE generate notifications on module changes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
modules/sysrepo-notifications@2025-01-10.yang | 97 ++++++
|
||||
modules/sysrepo_notifications_yang.h | 208 ++++++++++++
|
||||
src/common.c | 295 +++++++++++++++++-
|
||||
src/common.h | 42 ++-
|
||||
src/lyd_mods.c | 4 +
|
||||
src/modinfo.c | 2 +-
|
||||
src/replay.c | 42 +--
|
||||
src/replay.h | 10 +-
|
||||
src/sysrepo.c | 20 +-
|
||||
tests/test_oper_pull.c | 6 +
|
||||
10 files changed, 693 insertions(+), 33 deletions(-)
|
||||
create mode 100644 modules/sysrepo-notifications@2025-01-10.yang
|
||||
create mode 100644 modules/sysrepo_notifications_yang.h
|
||||
|
||||
diff --git a/modules/sysrepo-notifications@2025-01-10.yang b/modules/sysrepo-notifications@2025-01-10.yang
|
||||
new file mode 100644
|
||||
index 00000000..73df4353
|
||||
--- /dev/null
|
||||
+++ b/modules/sysrepo-notifications@2025-01-10.yang
|
||||
@@ -0,0 +1,97 @@
|
||||
+module sysrepo-notifications {
|
||||
+ namespace "http://www.sysrepo.org/yang/sysrepo-notifications";
|
||||
+ prefix srn;
|
||||
+
|
||||
+ yang-version 1.1;
|
||||
+
|
||||
+ import ietf-yang-library {
|
||||
+ prefix yanglib;
|
||||
+ }
|
||||
+
|
||||
+ organization
|
||||
+ "CESNET";
|
||||
+
|
||||
+ contact
|
||||
+ "Author: Michal Vasko
|
||||
+ <mvasko@cesnet.cz>";
|
||||
+
|
||||
+ description
|
||||
+ "Sysrepo YANG notifications for various events.";
|
||||
+
|
||||
+ revision "2025-01-10" {
|
||||
+ description
|
||||
+ "Initial revision.";
|
||||
+ }
|
||||
+
|
||||
+ typedef module-revision {
|
||||
+ type union {
|
||||
+ type yanglib:revision-identifier;
|
||||
+ type string {
|
||||
+ length "0";
|
||||
+ }
|
||||
+ }
|
||||
+ description
|
||||
+ "The YANG module revision date.
|
||||
+ A zero-length string is used if no revision statement
|
||||
+ is present in the YANG module.";
|
||||
+ }
|
||||
+
|
||||
+ notification module-change {
|
||||
+ description
|
||||
+ "YANG module change occurred.";
|
||||
+
|
||||
+ leaf name {
|
||||
+ type string;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Name of the changed module.";
|
||||
+ }
|
||||
+
|
||||
+ leaf revision {
|
||||
+ type module-revision;
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Revision of the module.";
|
||||
+ }
|
||||
+
|
||||
+ leaf change {
|
||||
+ type enumeration {
|
||||
+ enum installed {
|
||||
+ description
|
||||
+ "New module was installed.";
|
||||
+ }
|
||||
+ enum uninstalled {
|
||||
+ description
|
||||
+ "Module was uninstalled.";
|
||||
+ }
|
||||
+ enum updated {
|
||||
+ description
|
||||
+ "Module was updated to a newer revision.";
|
||||
+ }
|
||||
+ enum feature-enabled {
|
||||
+ description
|
||||
+ "Feature was enabled in a module.";
|
||||
+ }
|
||||
+ enum feature-disabled {
|
||||
+ description
|
||||
+ "Feature was disabled in a module.";
|
||||
+ }
|
||||
+ }
|
||||
+ mandatory true;
|
||||
+ description
|
||||
+ "Type of the module change.";
|
||||
+ }
|
||||
+
|
||||
+ leaf old-revision {
|
||||
+ when "../change = 'updated'";
|
||||
+ type module-revision;
|
||||
+ mandatory true;
|
||||
+ }
|
||||
+
|
||||
+ leaf feature-name {
|
||||
+ when "../change = ('feature-enabled' or 'feature-disabled')";
|
||||
+ type string;
|
||||
+ mandatory true;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
diff --git a/modules/sysrepo_notifications_yang.h b/modules/sysrepo_notifications_yang.h
|
||||
new file mode 100644
|
||||
index 00000000..8f99c518
|
||||
--- /dev/null
|
||||
+++ b/modules/sysrepo_notifications_yang.h
|
||||
@@ -0,0 +1,208 @@
|
||||
+char sysrepo_notifications_yang[] = {
|
||||
+ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x73, 0x79, 0x73, 0x72, 0x65,
|
||||
+ 0x70, 0x6f, 0x2d, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
+ 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x6e,
|
||||
+ 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x20, 0x22, 0x68, 0x74,
|
||||
+ 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x73, 0x79, 0x73,
|
||||
+ 0x72, 0x65, 0x70, 0x6f, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x79, 0x61, 0x6e,
|
||||
+ 0x67, 0x2f, 0x73, 0x79, 0x73, 0x72, 0x65, 0x70, 0x6f, 0x2d, 0x6e, 0x6f,
|
||||
+ 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22,
|
||||
+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78,
|
||||
+ 0x20, 0x73, 0x72, 0x6e, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x79,
|
||||
+ 0x61, 0x6e, 0x67, 0x2d, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20,
|
||||
+ 0x31, 0x2e, 0x31, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x69, 0x6d,
|
||||
+ 0x70, 0x6f, 0x72, 0x74, 0x20, 0x69, 0x65, 0x74, 0x66, 0x2d, 0x79, 0x61,
|
||||
+ 0x6e, 0x67, 0x2d, 0x6c, 0x69, 0x62, 0x72, 0x61, 0x72, 0x79, 0x20, 0x7b,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x70, 0x72, 0x65,
|
||||
+ 0x66, 0x69, 0x78, 0x20, 0x79, 0x61, 0x6e, 0x67, 0x6c, 0x69, 0x62, 0x3b,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x43, 0x45,
|
||||
+ 0x53, 0x4e, 0x45, 0x54, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x22, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x3a,
|
||||
+ 0x20, 0x4d, 0x69, 0x63, 0x68, 0x61, 0x6c, 0x20, 0x56, 0x61, 0x73, 0x6b,
|
||||
+ 0x6f, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x6d, 0x76, 0x61, 0x73,
|
||||
+ 0x6b, 0x6f, 0x40, 0x63, 0x65, 0x73, 0x6e, 0x65, 0x74, 0x2e, 0x63, 0x7a,
|
||||
+ 0x3e, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73,
|
||||
+ 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x53, 0x79, 0x73, 0x72, 0x65, 0x70,
|
||||
+ 0x6f, 0x20, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66,
|
||||
+ 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x20, 0x66, 0x6f, 0x72,
|
||||
+ 0x20, 0x76, 0x61, 0x72, 0x69, 0x6f, 0x75, 0x73, 0x20, 0x65, 0x76, 0x65,
|
||||
+ 0x6e, 0x74, 0x73, 0x2e, 0x22, 0x3b, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x22, 0x32, 0x30,
|
||||
+ 0x32, 0x35, 0x2d, 0x30, 0x31, 0x2d, 0x31, 0x30, 0x22, 0x20, 0x7b, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63,
|
||||
+ 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x49, 0x6e, 0x69,
|
||||
+ 0x74, 0x69, 0x61, 0x6c, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f,
|
||||
+ 0x6e, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x64, 0x65, 0x66, 0x20,
|
||||
+ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d, 0x72, 0x65, 0x76, 0x69, 0x73,
|
||||
+ 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x75, 0x6e, 0x69, 0x6f, 0x6e,
|
||||
+ 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x79, 0x61, 0x6e, 0x67,
|
||||
+ 0x6c, 0x69, 0x62, 0x3a, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e,
|
||||
+ 0x2d, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x3b,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
|
||||
+ 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x6e, 0x67, 0x74,
|
||||
+ 0x68, 0x20, 0x22, 0x30, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69,
|
||||
+ 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x22, 0x54, 0x68, 0x65, 0x20, 0x59, 0x41, 0x4e, 0x47,
|
||||
+ 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x72, 0x65, 0x76, 0x69,
|
||||
+ 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x61, 0x74, 0x65, 0x2e, 0x0a, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x41, 0x20, 0x7a, 0x65, 0x72, 0x6f, 0x2d, 0x6c, 0x65, 0x6e, 0x67, 0x74,
|
||||
+ 0x68, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x69, 0x73, 0x20,
|
||||
+ 0x75, 0x73, 0x65, 0x64, 0x20, 0x69, 0x66, 0x20, 0x6e, 0x6f, 0x20, 0x72,
|
||||
+ 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x73, 0x74, 0x61, 0x74,
|
||||
+ 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x69, 0x73, 0x20, 0x70, 0x72,
|
||||
+ 0x65, 0x73, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x74, 0x68, 0x65,
|
||||
+ 0x20, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
|
||||
+ 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
|
||||
+ 0x69, 0x6f, 0x6e, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d, 0x63,
|
||||
+ 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
|
||||
+ 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x22, 0x59, 0x41, 0x4e, 0x47, 0x20, 0x6d, 0x6f,
|
||||
+ 0x64, 0x75, 0x6c, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20,
|
||||
+ 0x6f, 0x63, 0x63, 0x75, 0x72, 0x72, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61,
|
||||
+ 0x66, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70,
|
||||
+ 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
|
||||
+ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65,
|
||||
+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
+ 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4e, 0x61, 0x6d, 0x65, 0x20,
|
||||
+ 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67,
|
||||
+ 0x65, 0x64, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66,
|
||||
+ 0x20, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x74, 0x79, 0x70, 0x65, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2d,
|
||||
+ 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x3b, 0x0a, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
|
||||
+ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65,
|
||||
+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
|
||||
+ 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x52, 0x65, 0x76, 0x69, 0x73,
|
||||
+ 0x69, 0x6f, 0x6e, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x6d,
|
||||
+ 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x6c, 0x65, 0x61, 0x66, 0x20, 0x63, 0x68, 0x61,
|
||||
+ 0x6e, 0x67, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x65,
|
||||
+ 0x6e, 0x75, 0x6d, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x7b,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x69, 0x6e,
|
||||
+ 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
|
||||
+ 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x4e, 0x65, 0x77, 0x20, 0x6d,
|
||||
+ 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x69, 0x6e,
|
||||
+ 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x2e, 0x22, 0x3b, 0x0a, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75,
|
||||
+ 0x6d, 0x20, 0x75, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65,
|
||||
+ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x22, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20,
|
||||
+ 0x75, 0x6e, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x64, 0x2e,
|
||||
+ 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
|
||||
+ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x22, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20,
|
||||
+ 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x61,
|
||||
+ 0x20, 0x6e, 0x65, 0x77, 0x65, 0x72, 0x20, 0x72, 0x65, 0x76, 0x69, 0x73,
|
||||
+ 0x69, 0x6f, 0x6e, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e, 0x75, 0x6d, 0x20, 0x66, 0x65,
|
||||
+ 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65,
|
||||
+ 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x22, 0x46, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x20, 0x77, 0x61, 0x73,
|
||||
+ 0x20, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20,
|
||||
+ 0x61, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x65, 0x6e,
|
||||
+ 0x75, 0x6d, 0x20, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x64,
|
||||
+ 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x20, 0x7b, 0x0a, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
|
||||
+ 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x46, 0x65, 0x61, 0x74, 0x75,
|
||||
+ 0x72, 0x65, 0x20, 0x77, 0x61, 0x73, 0x20, 0x64, 0x69, 0x73, 0x61, 0x62,
|
||||
+ 0x6c, 0x65, 0x64, 0x20, 0x69, 0x6e, 0x20, 0x61, 0x20, 0x6d, 0x6f, 0x64,
|
||||
+ 0x75, 0x6c, 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d,
|
||||
+ 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x7d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x6d, 0x61, 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79,
|
||||
+ 0x20, 0x74, 0x72, 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x64, 0x65, 0x73, 0x63, 0x72,
|
||||
+ 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x22,
|
||||
+ 0x54, 0x79, 0x70, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20,
|
||||
+ 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x20, 0x63, 0x68, 0x61, 0x6e, 0x67,
|
||||
+ 0x65, 0x2e, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x6c, 0x65, 0x61, 0x66, 0x20, 0x6f, 0x6c, 0x64, 0x2d, 0x72, 0x65, 0x76,
|
||||
+ 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x65, 0x6e,
|
||||
+ 0x20, 0x22, 0x2e, 0x2e, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20,
|
||||
+ 0x3d, 0x20, 0x27, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x27, 0x22,
|
||||
+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x74, 0x79, 0x70, 0x65, 0x20, 0x6d, 0x6f, 0x64, 0x75, 0x6c,
|
||||
+ 0x65, 0x2d, 0x72, 0x65, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x3b, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x6d, 0x61, 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72,
|
||||
+ 0x75, 0x65, 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x7d, 0x0a, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6c,
|
||||
+ 0x65, 0x61, 0x66, 0x20, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d,
|
||||
+ 0x6e, 0x61, 0x6d, 0x65, 0x20, 0x7b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20,
|
||||
+ 0x22, 0x2e, 0x2e, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x20, 0x3d,
|
||||
+ 0x20, 0x28, 0x27, 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x65,
|
||||
+ 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x27, 0x20, 0x6f, 0x72, 0x20, 0x27,
|
||||
+ 0x66, 0x65, 0x61, 0x74, 0x75, 0x72, 0x65, 0x2d, 0x64, 0x69, 0x73, 0x61,
|
||||
+ 0x62, 0x6c, 0x65, 0x64, 0x27, 0x29, 0x22, 0x3b, 0x0a, 0x20, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x74, 0x79, 0x70,
|
||||
+ 0x65, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3b, 0x0a, 0x20, 0x20,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x6d, 0x61,
|
||||
+ 0x6e, 0x64, 0x61, 0x74, 0x6f, 0x72, 0x79, 0x20, 0x74, 0x72, 0x75, 0x65,
|
||||
+ 0x3b, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a,
|
||||
+ 0x20, 0x20, 0x20, 0x20, 0x7d, 0x0a, 0x7d, 0x0a, 0x00
|
||||
+};
|
||||
diff --git a/src/common.c b/src/common.c
|
||||
index 10a379c0..72f3fc93 100644
|
||||
--- a/src/common.c
|
||||
+++ b/src/common.c
|
||||
@@ -55,10 +55,12 @@
|
||||
#include "modinfo.h"
|
||||
#include "plugins_datastore.h"
|
||||
#include "plugins_notification.h"
|
||||
+#include "replay.h"
|
||||
#include "shm_ext.h"
|
||||
#include "shm_main.h"
|
||||
#include "shm_mod.h"
|
||||
#include "shm_sub.h"
|
||||
+#include "subscr.h"
|
||||
#include "sysrepo.h"
|
||||
|
||||
#define SR_IS_YANG_ID_CHAR(c) (isalpha(c) || isdigit(c) || ((c) == '_') || ((c) == '-') || ((c) == '.'))
|
||||
@@ -965,9 +967,9 @@ sr_module_default_mode(const struct lys_module *ly_mod)
|
||||
if (!strcmp(ly_mod->name, "sysrepo")) {
|
||||
return SR_INTMOD_MAIN_FILE_PERM;
|
||||
} else if (sr_is_module_internal(ly_mod)) {
|
||||
- if (!strcmp(ly_mod->name, "sysrepo-plugind") || !strcmp(ly_mod->name, "ietf-yang-schema-mount") ||
|
||||
- !strcmp(ly_mod->name, "ietf-yang-library") || !strcmp(ly_mod->name, "ietf-netconf-notifications") ||
|
||||
- !strcmp(ly_mod->name, "ietf-netconf")) {
|
||||
+ if (!strcmp(ly_mod->name, "sysrepo-plugind") || !strcmp(ly_mod->name, "sysrepo-notifications") ||
|
||||
+ !strcmp(ly_mod->name, "ietf-yang-schema-mount") || !strcmp(ly_mod->name, "ietf-yang-library") ||
|
||||
+ !strcmp(ly_mod->name, "ietf-netconf-notifications") || !strcmp(ly_mod->name, "ietf-netconf")) {
|
||||
return SR_INTMOD_WITHDATA_FILE_PERM;
|
||||
} else if (!strcmp(ly_mod->name, "ietf-netconf-acm") || !strcmp(ly_mod->name, "sysrepo-monitoring")) {
|
||||
return SR_INTMOD_NACM_SRMON_FILE_PERM;
|
||||
@@ -5551,3 +5553,290 @@ sr_is_prod_env(void)
|
||||
}
|
||||
return sr_prod_env;
|
||||
}
|
||||
+
|
||||
+/**
|
||||
+ * @brief Check whether a 'sysrepo-notifications' notification even need to be generated.
|
||||
+ *
|
||||
+ * @param[in] conn Connection to use.
|
||||
+ * @param[in] shm_mod SHM mod.
|
||||
+ * @param[out] subs_or_replay Set if there are any subscribers or replay is enabled for the module.
|
||||
+ * @return err_info, NULL on success.
|
||||
+ */
|
||||
+static sr_error_info_t *
|
||||
+sr_generate_notif_has_subs_or_replay(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, int *subs_or_replay)
|
||||
+{
|
||||
+ sr_error_info_t *err_info = NULL;
|
||||
+ sr_mod_notif_sub_t *notif_subs;
|
||||
+ uint32_t notif_sub_count;
|
||||
+
|
||||
+ /* EXT READ LOCK */
|
||||
+ if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 0, __func__))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* get subscriber count */
|
||||
+ err_info = sr_notif_find_subscriber(conn, "sysrepo-notifications", ¬if_subs, ¬if_sub_count, NULL);
|
||||
+
|
||||
+ /* EXT READ UNLOCK */
|
||||
+ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 0, __func__);
|
||||
+
|
||||
+ if (err_info) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* check replay support and subscribers */
|
||||
+ if (!shm_mod->replay_supp && !notif_sub_count) {
|
||||
+ /* no subscribers and replay off */
|
||||
+ *subs_or_replay = 0;
|
||||
+ } else {
|
||||
+ *subs_or_replay = 1;
|
||||
+ }
|
||||
+
|
||||
+cleanup:
|
||||
+ return err_info;
|
||||
+}
|
||||
+
|
||||
+/**
|
||||
+ * @brief Send a generated notification.
|
||||
+ *
|
||||
+ * @param[in] conn Connection to use.
|
||||
+ * @param[in] shm_mod SHM mod.
|
||||
+ * @param[in] notif Notification to send.
|
||||
+ * @return err_info, NULL on success.
|
||||
+ */
|
||||
+static sr_error_info_t *
|
||||
+sr_generate_notif_send(sr_conn_ctx_t *conn, sr_mod_t *shm_mod, const struct lyd_node *notif)
|
||||
+{
|
||||
+ sr_error_info_t *err_info = NULL;
|
||||
+ struct timespec notif_ts_mono, notif_ts_real;
|
||||
+
|
||||
+ /* NOTIF SUB READ LOCK */
|
||||
+ if ((err_info = sr_rwlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid,
|
||||
+ __func__, NULL, NULL))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* remember when the notification was generated */
|
||||
+ sr_timeouttime_get(¬if_ts_mono, 0);
|
||||
+ sr_realtime_get(¬if_ts_real);
|
||||
+
|
||||
+ /* send the notification (non-validated, must be valid) */
|
||||
+ err_info = sr_shmsub_notif_notify(conn, notif, notif_ts_mono, notif_ts_real, NULL, NULL, 0, 0);
|
||||
+
|
||||
+ /* NOTIF SUB READ UNLOCK */
|
||||
+ sr_rwunlock(&shm_mod->notif_lock, SR_SHMEXT_SUB_LOCK_TIMEOUT, SR_LOCK_READ, conn->cid, __func__);
|
||||
+
|
||||
+ if (err_info) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* store the notification for a replay */
|
||||
+ if ((err_info = sr_replay_store(conn, NULL, notif, notif_ts_real))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+cleanup:
|
||||
+ return err_info;
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+sr_generate_notif_module_change_installed(sr_conn_ctx_t *conn, sr_int_install_mod_t *new_mods, uint32_t new_mod_count)
|
||||
+{
|
||||
+ sr_error_info_t *err_info = NULL;
|
||||
+ struct lyd_node *notif = NULL;
|
||||
+ sr_mod_t *shm_mod;
|
||||
+ uint32_t i;
|
||||
+ int subs_or_replay;
|
||||
+
|
||||
+ /* get this module */
|
||||
+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications");
|
||||
+ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup);
|
||||
+
|
||||
+ /* check whether to even generate any notifications */
|
||||
+ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < new_mod_count; ++i) {
|
||||
+ /* generate the notifcation */
|
||||
+ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL,
|
||||
+ ¬if))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "name", new_mods[i].ly_mod->name))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", new_mods[i].ly_mod->revision))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "installed"))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* send it */
|
||||
+ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ lyd_free_siblings(notif);
|
||||
+ notif = NULL;
|
||||
+ }
|
||||
+
|
||||
+cleanup:
|
||||
+ lyd_free_siblings(notif);
|
||||
+ sr_errinfo_free(&err_info);
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+sr_generate_notif_module_change_uninstalled(sr_conn_ctx_t *conn, struct ly_set *mod_set)
|
||||
+{
|
||||
+ sr_error_info_t *err_info = NULL;
|
||||
+ struct lyd_node *notif = NULL;
|
||||
+ const struct lys_module *ly_mod;
|
||||
+ sr_mod_t *shm_mod;
|
||||
+ uint32_t i;
|
||||
+ int subs_or_replay;
|
||||
+
|
||||
+ /* get this module */
|
||||
+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications");
|
||||
+ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup);
|
||||
+
|
||||
+ /* check whether to even generate any notifications */
|
||||
+ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < mod_set->count; ++i) {
|
||||
+ ly_mod = mod_set->objs[i];
|
||||
+
|
||||
+ /* generate the notifcation */
|
||||
+ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL,
|
||||
+ ¬if))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "uninstalled"))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* send it */
|
||||
+ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ lyd_free_siblings(notif);
|
||||
+ notif = NULL;
|
||||
+ }
|
||||
+
|
||||
+cleanup:
|
||||
+ lyd_free_siblings(notif);
|
||||
+ sr_errinfo_free(&err_info);
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+sr_generate_notif_module_change_updated(sr_conn_ctx_t *conn, struct ly_set *old_mod_set, struct ly_set *upd_mod_set)
|
||||
+{
|
||||
+ sr_error_info_t *err_info = NULL;
|
||||
+ struct lyd_node *notif = NULL;
|
||||
+ const struct lys_module *ly_mod;
|
||||
+ sr_mod_t *shm_mod;
|
||||
+ uint32_t i;
|
||||
+ int subs_or_replay;
|
||||
+
|
||||
+ /* get this module */
|
||||
+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications");
|
||||
+ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup);
|
||||
+
|
||||
+ /* check whether to even generate any notifications */
|
||||
+ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < upd_mod_set->count; ++i) {
|
||||
+ ly_mod = upd_mod_set->objs[i];
|
||||
+
|
||||
+ /* generate the notifcation */
|
||||
+ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL,
|
||||
+ ¬if))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "change", "updated"))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ ly_mod = old_mod_set->objs[i];
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "old-revision", ly_mod->revision))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* send it */
|
||||
+ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ lyd_free_siblings(notif);
|
||||
+ notif = NULL;
|
||||
+ }
|
||||
+
|
||||
+cleanup:
|
||||
+ lyd_free_siblings(notif);
|
||||
+ sr_errinfo_free(&err_info);
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+sr_generate_notif_module_change_feature(sr_conn_ctx_t *conn, const struct lys_module *ly_mod, const char *feature_name,
|
||||
+ int enabled)
|
||||
+{
|
||||
+ sr_error_info_t *err_info = NULL;
|
||||
+ struct lyd_node *notif = NULL;
|
||||
+ sr_mod_t *shm_mod;
|
||||
+ int subs_or_replay;
|
||||
+
|
||||
+ /* get this module */
|
||||
+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), "sysrepo-notifications");
|
||||
+ SR_CHECK_INT_GOTO(!shm_mod, err_info, cleanup);
|
||||
+
|
||||
+ /* check whether to even generate any notifications */
|
||||
+ if ((err_info = sr_generate_notif_has_subs_or_replay(conn, shm_mod, &subs_or_replay)) || !subs_or_replay) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* generate the notifcation */
|
||||
+ if ((err_info = sr_lyd_new_path(NULL, conn->ly_ctx, "/sysrepo-notifications:module-change", NULL, 0, NULL,
|
||||
+ ¬if))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "name", ly_mod->name))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "revision", ly_mod->revision))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "change", enabled ? "feature-enabled" : "feature-disabled"))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ if ((err_info = sr_lyd_new_term(notif, NULL, "feature-name", feature_name))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+ /* send it */
|
||||
+ if ((err_info = sr_generate_notif_send(conn, shm_mod, notif))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
+
|
||||
+cleanup:
|
||||
+ lyd_free_siblings(notif);
|
||||
+ sr_errinfo_free(&err_info);
|
||||
+}
|
||||
diff --git a/src/common.h b/src/common.h
|
||||
index 99304bd7..9fc675d4 100644
|
||||
--- a/src/common.h
|
||||
+++ b/src/common.h
|
||||
@@ -4,8 +4,8 @@
|
||||
* @brief common routines header
|
||||
*
|
||||
* @copyright
|
||||
- * Copyright (c) 2018 - 2023 Deutsche Telekom AG.
|
||||
- * Copyright (c) 2018 - 2023 CESNET, z.s.p.o.
|
||||
+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG.
|
||||
+ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o.
|
||||
*
|
||||
* This source code is licensed under BSD 3-Clause License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
@@ -1371,4 +1371,42 @@ sr_error_info_t *sr_conn_info(sr_cid_t **cids, pid_t **pids, uint32_t *count, sr
|
||||
*/
|
||||
int sr_is_prod_env(void);
|
||||
|
||||
+/**
|
||||
+ * @brief Generate the 'module-change' notification for newly installed modules.
|
||||
+ *
|
||||
+ * @param[in] conn Connection to use.
|
||||
+ * @param[in] new_mods Array of new modules.
|
||||
+ * @param[in] new_mod_count Count of @p new_mods.
|
||||
+ */
|
||||
+void sr_generate_notif_module_change_installed(sr_conn_ctx_t *conn, sr_int_install_mod_t *new_mods,
|
||||
+ uint32_t new_mod_count);
|
||||
+
|
||||
+/**
|
||||
+ * @brief Generate the 'module-change' notification for uninstalled modules.
|
||||
+ *
|
||||
+ * @param[in] conn Connection to use.
|
||||
+ * @param[in] mod_set Set of uninstalled modules.
|
||||
+ */
|
||||
+void sr_generate_notif_module_change_uninstalled(sr_conn_ctx_t *conn, struct ly_set *mod_set);
|
||||
+
|
||||
+/**
|
||||
+ * @brief Generate the 'module-change' notification for updated modules.
|
||||
+ *
|
||||
+ * @param[in] conn Connection to use.
|
||||
+ * @param[in] old_mod_set Set of old modules.
|
||||
+ * @param[in] upd_mod_set Set of updated modules.
|
||||
+ */
|
||||
+void sr_generate_notif_module_change_updated(sr_conn_ctx_t *conn, struct ly_set *old_mod_set, struct ly_set *upd_mod_set);
|
||||
+
|
||||
+/**
|
||||
+ * @brief Generate the 'module-change' notification for a module feature change.
|
||||
+ *
|
||||
+ * @param[in] conn Connection to use.
|
||||
+ * @param[in] ly_mod Changed module.
|
||||
+ * @param[in] feature_name Changed feature name.
|
||||
+ * @param[in] enabled Whether the feature was enabled or disabled.
|
||||
+ */
|
||||
+void sr_generate_notif_module_change_feature(sr_conn_ctx_t *conn, const struct lys_module *ly_mod,
|
||||
+ const char *feature_name, int enabled);
|
||||
+
|
||||
#endif /* _COMMON_H */
|
||||
diff --git a/src/lyd_mods.c b/src/lyd_mods.c
|
||||
index 256f292e..77f8cd7b 100644
|
||||
--- a/src/lyd_mods.c
|
||||
+++ b/src/lyd_mods.c
|
||||
@@ -65,6 +65,7 @@
|
||||
#include "../modules/ietf_netconf_yang.h"
|
||||
#include "../modules/ietf_origin_yang.h"
|
||||
#include "../modules/sysrepo_monitoring_yang.h"
|
||||
+#include "../modules/sysrepo_notifications_yang.h"
|
||||
#include "../modules/sysrepo_plugind_yang.h"
|
||||
|
||||
/**
|
||||
@@ -1000,6 +1001,9 @@ sr_lydmods_create(sr_conn_ctx_t *conn, struct ly_ctx *ly_ctx, struct lyd_node **
|
||||
/* install sysrepo-plugind */
|
||||
SR_INSTALL_INT_MOD(ly_ctx, sysrepo_plugind_yang, 0, new_mods, new_mod_count);
|
||||
|
||||
+ /* install sysrepo-notifications */
|
||||
+ SR_INSTALL_INT_MOD(ly_ctx, sysrepo_notifications_yang, 0, new_mods, new_mod_count);
|
||||
+
|
||||
/* install ietf-netconf (implemented dependency) and ietf-netconf-with-defaults */
|
||||
SR_INSTALL_INT_MOD(ly_ctx, ietf_netconf_yang, 1, new_mods, new_mod_count);
|
||||
SR_INSTALL_INT_MOD(ly_ctx, ietf_netconf_with_defaults_yang, 0, new_mods, new_mod_count);
|
||||
diff --git a/src/modinfo.c b/src/modinfo.c
|
||||
index 14d0b2ae..7414121e 100644
|
||||
--- a/src/modinfo.c
|
||||
+++ b/src/modinfo.c
|
||||
@@ -3824,7 +3824,7 @@ sr_modinfo_generate_config_change_notif(struct sr_mod_info_s *mod_info, sr_sessi
|
||||
}
|
||||
|
||||
/* store the notification for a replay */
|
||||
- if ((err_info = sr_replay_store(session, notif, notif_ts_real))) {
|
||||
+ if ((err_info = sr_replay_store(mod_info->conn, session, notif, notif_ts_real))) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
diff --git a/src/replay.c b/src/replay.c
|
||||
index c31043de..00e63eec 100644
|
||||
--- a/src/replay.c
|
||||
+++ b/src/replay.c
|
||||
@@ -127,7 +127,7 @@ cleanup:
|
||||
}
|
||||
|
||||
sr_error_info_t *
|
||||
-sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts)
|
||||
+sr_replay_store(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts)
|
||||
{
|
||||
sr_error_info_t *err_info = NULL;
|
||||
sr_mod_t *shm_mod;
|
||||
@@ -146,7 +146,7 @@ sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct tim
|
||||
SR_CHECK_INT_RET(notif_op->schema->nodetype != LYS_NOTIF, err_info);
|
||||
|
||||
/* find SHM mod for replay lock and check if replay is even supported */
|
||||
- shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(sess->conn), ly_mod->name);
|
||||
+ shm_mod = sr_shmmod_find_module(SR_CONN_MOD_SHM(conn), ly_mod->name);
|
||||
SR_CHECK_INT_RET(!shm_mod, err_info);
|
||||
|
||||
if (!shm_mod->replay_supp) {
|
||||
@@ -154,32 +154,34 @@ sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct tim
|
||||
return NULL;
|
||||
}
|
||||
|
||||
- /* MUTEX LOCK */
|
||||
- sr_timeouttime_get(&timeout_ts, SR_NOTIF_BUF_LOCK_TIMEOUT);
|
||||
- if ((r = pthread_mutex_clocklock(&sess->notif_buf.lock.mutex, COMPAT_CLOCK_ID, &timeout_ts))) {
|
||||
- SR_ERRINFO_LOCK(&err_info, __func__, r);
|
||||
- return err_info;
|
||||
- }
|
||||
+ if (sess) {
|
||||
+ /* MUTEX LOCK */
|
||||
+ sr_timeouttime_get(&timeout_ts, SR_NOTIF_BUF_LOCK_TIMEOUT);
|
||||
+ if ((r = pthread_mutex_clocklock(&sess->notif_buf.lock.mutex, COMPAT_CLOCK_ID, &timeout_ts))) {
|
||||
+ SR_ERRINFO_LOCK(&err_info, __func__, r);
|
||||
+ return err_info;
|
||||
+ }
|
||||
|
||||
- if (sess->notif_buf.thread_running) {
|
||||
- /* store the notification in the buffer */
|
||||
- has_buf = 1;
|
||||
- err_info = sr_notif_buf_store(sess, notif, notif_ts);
|
||||
+ if (sess->notif_buf.thread_running) {
|
||||
+ /* store the notification in the buffer */
|
||||
+ has_buf = 1;
|
||||
+ err_info = sr_notif_buf_store(sess, notif, notif_ts);
|
||||
|
||||
- /* broadcast condition */
|
||||
- sr_cond_broadcast(&sess->notif_buf.lock.cond);
|
||||
- }
|
||||
+ /* broadcast condition */
|
||||
+ sr_cond_broadcast(&sess->notif_buf.lock.cond);
|
||||
+ }
|
||||
|
||||
- /* MUTEX UNLOCK */
|
||||
- pthread_mutex_unlock(&sess->notif_buf.lock.mutex);
|
||||
+ /* MUTEX UNLOCK */
|
||||
+ pthread_mutex_unlock(&sess->notif_buf.lock.mutex);
|
||||
|
||||
- if (err_info) {
|
||||
- return err_info;
|
||||
+ if (err_info) {
|
||||
+ return err_info;
|
||||
+ }
|
||||
}
|
||||
|
||||
if (!has_buf) {
|
||||
/* write the notification to a replay file */
|
||||
- if ((err_info = sr_notif_write(sess->conn, shm_mod, notif, notif_ts))) {
|
||||
+ if ((err_info = sr_notif_write(conn, shm_mod, notif, notif_ts))) {
|
||||
return err_info;
|
||||
}
|
||||
}
|
||||
diff --git a/src/replay.h b/src/replay.h
|
||||
index ecf0cf25..7f3a851a 100644
|
||||
--- a/src/replay.h
|
||||
+++ b/src/replay.h
|
||||
@@ -4,8 +4,8 @@
|
||||
* @brief header for notification replay routines
|
||||
*
|
||||
* @copyright
|
||||
- * Copyright (c) 2018 - 2021 Deutsche Telekom AG.
|
||||
- * Copyright (c) 2018 - 2021 CESNET, z.s.p.o.
|
||||
+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG.
|
||||
+ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o.
|
||||
*
|
||||
* This source code is licensed under BSD 3-Clause License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
@@ -26,12 +26,14 @@
|
||||
/**
|
||||
* @brief Store a notification for replay.
|
||||
*
|
||||
- * @param[in] sess Session to use.
|
||||
+ * @param[in] conn Connection to use.
|
||||
+ * @param[in] sess Session to use, if available.
|
||||
* @param[in] notif Notification to store.
|
||||
* @param[in] notif_ts Notification timestamp to store.
|
||||
* @return err_info, NULL on success.
|
||||
*/
|
||||
-sr_error_info_t *sr_replay_store(sr_session_ctx_t *sess, const struct lyd_node *notif, struct timespec notif_ts);
|
||||
+sr_error_info_t *sr_replay_store(sr_conn_ctx_t *conn, sr_session_ctx_t *sess, const struct lyd_node *notif,
|
||||
+ struct timespec notif_ts);
|
||||
|
||||
/**
|
||||
* @brief Notification buffer thread.
|
||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||
index 7c96243c..614b5454 100644
|
||||
--- a/src/sysrepo.c
|
||||
+++ b/src/sysrepo.c
|
||||
@@ -4,8 +4,8 @@
|
||||
* @brief sysrepo API routines
|
||||
*
|
||||
* @copyright
|
||||
- * Copyright (c) 2018 - 2024 Deutsche Telekom AG.
|
||||
- * Copyright (c) 2018 - 2024 CESNET, z.s.p.o.
|
||||
+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG.
|
||||
+ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o.
|
||||
*
|
||||
* This source code is licensed under BSD 3-Clause License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
@@ -1541,6 +1541,9 @@ _sr_install_modules(sr_conn_ctx_t *conn, const char *search_dirs, const char *da
|
||||
SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx);
|
||||
sr_conn_ctx_switch(conn, &new_ctx, &old_ctx);
|
||||
|
||||
+ /* send the notification */
|
||||
+ sr_generate_notif_module_change_installed(conn, *new_mods, *new_mod_count);
|
||||
+
|
||||
goto cleanup;
|
||||
|
||||
error:
|
||||
@@ -1859,6 +1862,9 @@ sr_remove_modules(sr_conn_ctx_t *conn, const char **module_names, int force)
|
||||
SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx);
|
||||
sr_conn_ctx_switch(conn, &new_ctx, &old_ctx);
|
||||
|
||||
+ /* send the notification */
|
||||
+ sr_generate_notif_module_change_uninstalled(conn, &mod_set);
|
||||
+
|
||||
cleanup:
|
||||
sr_lycc_update_data_clear(&data_info);
|
||||
lyd_free_siblings(sr_mods);
|
||||
@@ -2104,6 +2110,9 @@ sr_update_modules(sr_conn_ctx_t *conn, const char **schema_paths, const char *se
|
||||
SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx);
|
||||
sr_conn_ctx_switch(conn, &new_ctx, &old_ctx);
|
||||
|
||||
+ /* send the notification */
|
||||
+ sr_generate_notif_module_change_updated(conn, &old_mod_set, &upd_mod_set);
|
||||
+
|
||||
cleanup:
|
||||
sr_lycc_update_data_clear(&data_info);
|
||||
lyd_free_siblings(sr_mods);
|
||||
@@ -2606,6 +2615,9 @@ sr_change_module_feature(sr_conn_ctx_t *conn, const char *module_name, const cha
|
||||
SR_CONN_MAIN_SHM(conn)->content_id = ly_ctx_get_modules_hash(new_ctx);
|
||||
sr_conn_ctx_switch(conn, &new_ctx, &old_ctx);
|
||||
|
||||
+ /* send the notification */
|
||||
+ sr_generate_notif_module_change_feature(conn, ly_mod, feature_name, enable);
|
||||
+
|
||||
cleanup:
|
||||
sr_lycc_update_data_clear(&data_info);
|
||||
lyd_free_siblings(sr_mods);
|
||||
@@ -2735,6 +2747,8 @@ sr_is_module_internal(const struct lys_module *ly_mod)
|
||||
return 1;
|
||||
} else if (!strcmp(ly_mod->name, "sysrepo-plugind")) {
|
||||
return 1;
|
||||
+ } else if (!strcmp(ly_mod->name, "sysrepo-notifications")) {
|
||||
+ return 1;
|
||||
} else if (!strcmp(ly_mod->name, "ietf-netconf-acm")) {
|
||||
return 1;
|
||||
}
|
||||
@@ -7407,7 +7421,7 @@ sr_notif_send_tree(sr_session_ctx_t *session, struct lyd_node *notif, uint32_t t
|
||||
}
|
||||
|
||||
/* store the notification for a replay */
|
||||
- if ((err_info = sr_replay_store(session, notif_top, notif_ts_real))) {
|
||||
+ if ((err_info = sr_replay_store(session->conn, session, notif_top, notif_ts_real))) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
diff --git a/tests/test_oper_pull.c b/tests/test_oper_pull.c
|
||||
index c9654faa..91ba526e 100644
|
||||
--- a/tests/test_oper_pull.c
|
||||
+++ b/tests/test_oper_pull.c
|
||||
@@ -406,6 +406,9 @@ test_sr_mon(void **state)
|
||||
" </datastore>\n"
|
||||
" </module>\n"
|
||||
" <module>\n"
|
||||
+ " <name>sysrepo-notifications</name>\n"
|
||||
+ " </module>\n"
|
||||
+ " <module>\n"
|
||||
" <name>ietf-netconf</name>\n"
|
||||
" </module>\n"
|
||||
" <module>\n"
|
||||
@@ -669,6 +672,9 @@ test_sr_mon(void **state)
|
||||
" </ds-lock>\n"
|
||||
" </module>\n"
|
||||
" <module>\n"
|
||||
+ " <name>sysrepo-notifications</name>\n"
|
||||
+ " </module>\n"
|
||||
+ " <module>\n"
|
||||
" <name>ietf-netconf</name>\n"
|
||||
" </module>\n"
|
||||
" <module>\n"
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
From 05ef34960277dca49e1dda26eb2f4ffd5f1ba354 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Mon, 13 Jan 2025 11:19:51 +0100
|
||||
Subject: [PATCH 06/22] SOVERSION bump to version 7.29.12
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index 9f3cfde0..fcb49a24 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -62,7 +62,7 @@ set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_
|
||||
# with backward compatible change and micro version is connected with any internal change of the library.
|
||||
set(SYSREPO_MAJOR_SOVERSION 7)
|
||||
set(SYSREPO_MINOR_SOVERSION 29)
|
||||
-set(SYSREPO_MICRO_SOVERSION 11)
|
||||
+set(SYSREPO_MICRO_SOVERSION 12)
|
||||
set(SYSREPO_SOVERSION_FULL ${SYSREPO_MAJOR_SOVERSION}.${SYSREPO_MINOR_SOVERSION}.${SYSREPO_MICRO_SOVERSION})
|
||||
set(SYSREPO_SOVERSION ${SYSREPO_MAJOR_SOVERSION})
|
||||
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
From 74f5fa11043e579b6c007a2ff1f5f1d880637b92 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Mon, 13 Jan 2025 11:20:00 +0100
|
||||
Subject: [PATCH 07/22] VERSION bump to version 3.4.0
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
CMakeLists.txt | 4 ++--
|
||||
1 file changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
||||
index fcb49a24..7d49cedb 100644
|
||||
--- a/CMakeLists.txt
|
||||
+++ b/CMakeLists.txt
|
||||
@@ -53,8 +53,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
|
||||
# minor version changes with added functionality (new tool, functionality of the tool or library, ...) and
|
||||
# micro version is changed with a set of small changes or bugfixes anywhere in the project.
|
||||
set(SYSREPO_MAJOR_VERSION 3)
|
||||
-set(SYSREPO_MINOR_VERSION 3)
|
||||
-set(SYSREPO_MICRO_VERSION 11)
|
||||
+set(SYSREPO_MINOR_VERSION 4)
|
||||
+set(SYSREPO_MICRO_VERSION 0)
|
||||
set(SYSREPO_VERSION ${SYSREPO_MAJOR_VERSION}.${SYSREPO_MINOR_VERSION}.${SYSREPO_MICRO_VERSION})
|
||||
|
||||
# Version of the library
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
From a0e02874aaf24a9e94c501ac95c5b9f55c316500 Mon Sep 17 00:00:00 2001
|
||||
From: IrfanMohammad <irfan.haslanded@gmail.com>
|
||||
Date: Mon, 13 Jan 2025 08:19:04 +0000
|
||||
Subject: [PATCH 08/22] shmext REFACTOR conn_remap_unlock remove last hole
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
The logic in sr_shmext_conn_remap_unlock can be cleaner.
|
||||
|
||||
A call to sr_file_get_size() can be avoided since we already know it
|
||||
as `conn->ext_shm.size`.
|
||||
|
||||
Also cleanup the somewhat complex cast + arithmetic in a if condition.
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/shm_ext.c | 16 +++++++---------
|
||||
1 file changed, 7 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/src/shm_ext.c b/src/shm_ext.c
|
||||
index f4103837..78930469 100644
|
||||
--- a/src/shm_ext.c
|
||||
+++ b/src/shm_ext.c
|
||||
@@ -131,8 +131,8 @@ sr_shmext_conn_remap_unlock(sr_conn_ctx_t *conn, sr_lock_mode_t mode, int ext_lo
|
||||
{
|
||||
sr_error_info_t *err_info = NULL;
|
||||
sr_ext_hole_t *iter, *last = NULL;
|
||||
- uint32_t last_size;
|
||||
- size_t shm_file_size = 0;
|
||||
+ uint32_t new_size;
|
||||
+ char *last_hole_end;
|
||||
|
||||
/* make ext SHM smaller if there is a memory hole at its end */
|
||||
if (((mode == SR_LOCK_WRITE) || (mode == SR_LOCK_WRITE_URGE)) && ext_lock) {
|
||||
@@ -140,17 +140,15 @@ sr_shmext_conn_remap_unlock(sr_conn_ctx_t *conn, sr_lock_mode_t mode, int ext_lo
|
||||
last = iter;
|
||||
}
|
||||
|
||||
- if (last && ((uint32_t)((char *)last - conn->ext_shm.addr) + last->size == conn->ext_shm.size)) {
|
||||
- if ((err_info = sr_file_get_size(conn->ext_shm.fd, &shm_file_size))) {
|
||||
- goto cleanup_unlock;
|
||||
- }
|
||||
+ /* cast `last` as a char* for correct pointer arithmetic. */
|
||||
+ last_hole_end = last ? ((char *)last + last->size) : NULL;
|
||||
|
||||
+ if (last_hole_end == conn->ext_shm.addr + conn->ext_shm.size) {
|
||||
/* remove the hole */
|
||||
- last_size = last->size;
|
||||
+ new_size = conn->ext_shm.size - last->size;
|
||||
sr_ext_hole_del(SR_CONN_EXT_SHM(conn), last);
|
||||
-
|
||||
/* remap (and truncate) ext SHM */
|
||||
- if ((err_info = sr_shm_remap(&conn->ext_shm, shm_file_size - last_size))) {
|
||||
+ if ((err_info = sr_shm_remap(&conn->ext_shm, new_size))) {
|
||||
goto cleanup_unlock;
|
||||
}
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,250 +0,0 @@
|
||||
From e0f1e2480a3f69e291deed06a2626ed9b4df1662 Mon Sep 17 00:00:00 2001
|
||||
From: IrfanMohammad <irfan.haslanded@gmail.com>
|
||||
Date: Mon, 13 Jan 2025 13:46:28 +0000
|
||||
Subject: [PATCH 09/22] modinfo UPDATE sess push oper data module cache
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Maintain a list of modules whose push oper data were modified
|
||||
by the session.
|
||||
|
||||
Acquiring the global ext_lock is significantly more expensive.
|
||||
|
||||
Previously this was implemented for connections in
|
||||
3b0165d656709b5f0cd3e8debc2e55c76c73663a
|
||||
|
||||
But in sysrepo v3.x, oper push data is tied to the session and the logic
|
||||
needs to be re-introduced.
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/common_types.h | 7 +++-
|
||||
src/modinfo.c | 93 ++++++++++++++++++++++++++--------------------
|
||||
src/sysrepo.c | 14 ++++++-
|
||||
3 files changed, 71 insertions(+), 43 deletions(-)
|
||||
|
||||
diff --git a/src/common_types.h b/src/common_types.h
|
||||
index 915d9196..3ce27ad4 100644
|
||||
--- a/src/common_types.h
|
||||
+++ b/src/common_types.h
|
||||
@@ -180,7 +180,12 @@ struct sr_session_ctx_s {
|
||||
char *user; /**< Session (system) user. */
|
||||
char *nacm_user; /**< Optional NACM user. If set, NACM is applied. */
|
||||
sr_error_info_t *err_info; /**< Session error information. */
|
||||
- int push_oper_data; /**< Whether the session has stored any push operational data. */
|
||||
+
|
||||
+ struct sr_oper_push_cache_s {
|
||||
+ char *name; /**< Module name whose push oper data were ever modified by this session. */
|
||||
+ int has_data; /**< Flag if there are any actual data currently. */
|
||||
+ } *oper_push_mods;
|
||||
+ uint32_t oper_push_mod_count; /**< Count of modules with modified push oper data by this session. */
|
||||
|
||||
char *orig_name; /**< Originator name used for all events sent on this session. */
|
||||
void *orig_data; /**< Originator data used for all events sent on this session. */
|
||||
diff --git a/src/modinfo.c b/src/modinfo.c
|
||||
index 7414121e..d5c27672 100644
|
||||
--- a/src/modinfo.c
|
||||
+++ b/src/modinfo.c
|
||||
@@ -322,42 +322,26 @@ sr_error_info_t *
|
||||
sr_modinfo_collect_oper_sess(sr_session_ctx_t *sess, const struct lys_module *ly_mod, struct sr_mod_info_s *mod_info)
|
||||
{
|
||||
sr_error_info_t *err_info = NULL;
|
||||
- sr_mod_shm_t *mod_shm;
|
||||
- sr_mod_t *shm_mod;
|
||||
- sr_mod_oper_push_t *oper_push;
|
||||
const struct lys_module *ly_mod2;
|
||||
- uint32_t i, j;
|
||||
-
|
||||
- mod_shm = SR_CONN_MOD_SHM(sess->conn);
|
||||
-
|
||||
- /* EXT READ LOCK */
|
||||
- if ((err_info = sr_shmext_conn_remap_lock(sess->conn, SR_LOCK_READ, 1, __func__))) {
|
||||
- return err_info;
|
||||
- }
|
||||
-
|
||||
- /* go through all the SHM modules */
|
||||
- for (i = 0; i < mod_shm->mod_count; ++i) {
|
||||
- shm_mod = SR_SHM_MOD_IDX(mod_shm, i);
|
||||
+ uint32_t i;
|
||||
|
||||
- if (ly_mod && strcmp(ly_mod->name, ((char *)mod_shm) + shm_mod->name)) {
|
||||
+ /* add only the cached modules */
|
||||
+ for (i = 0; i < sess->oper_push_mod_count; ++i) {
|
||||
+ if (ly_mod && strcmp(ly_mod->name, sess->oper_push_mods[i].name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
- /* go through the sessions with oper data */
|
||||
- for (j = 0; j < shm_mod->oper_push_data_count; ++j) {
|
||||
- oper_push = &((sr_mod_oper_push_t *)(sess->conn->ext_shm.addr + shm_mod->oper_push_data))[j];
|
||||
- if ((oper_push->sid == sess->sid) && oper_push->has_data) {
|
||||
- break;
|
||||
- }
|
||||
+ if (!sess->oper_push_mods[i].has_data) {
|
||||
+ continue;
|
||||
}
|
||||
|
||||
- if (j < shm_mod->oper_push_data_count) {
|
||||
- /* remember the module */
|
||||
- ly_mod2 = ly_ctx_get_module_implemented(sess->conn->ly_ctx, ((char *)mod_shm) + shm_mod->name);
|
||||
- SR_CHECK_INT_GOTO(!ly_mod2, err_info, cleanup_unlock);
|
||||
- if ((err_info = sr_modinfo_add(ly_mod2, NULL, 0, 0, mod_info))) {
|
||||
- goto cleanup_unlock;
|
||||
- }
|
||||
+ ly_mod2 = ly_ctx_get_module_implemented(sess->conn->ly_ctx, sess->oper_push_mods[i].name);
|
||||
+ if (!ly_mod2) {
|
||||
+ /* could have been removed */
|
||||
+ continue;
|
||||
+ }
|
||||
+ if ((err_info = sr_modinfo_add(ly_mod2, NULL, 0, 0, mod_info))) {
|
||||
+ return err_info;
|
||||
}
|
||||
|
||||
if (ly_mod) {
|
||||
@@ -365,11 +349,7 @@ sr_modinfo_collect_oper_sess(sr_session_ctx_t *sess, const struct lys_module *ly
|
||||
}
|
||||
}
|
||||
|
||||
-cleanup_unlock:
|
||||
- /* EXT READ UNLOCK */
|
||||
- sr_shmext_conn_remap_unlock(sess->conn, SR_LOCK_READ, 1, __func__);
|
||||
-
|
||||
- return err_info;
|
||||
+ return NULL;
|
||||
}
|
||||
|
||||
sr_error_info_t *
|
||||
@@ -2664,7 +2644,7 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m
|
||||
struct lyd_node *mod_data = NULL;
|
||||
const char **xpaths;
|
||||
uint32_t xpath_count;
|
||||
- int modified, has_data;
|
||||
+ int modified, has_data = 0;
|
||||
char *orig_name = NULL;
|
||||
void *orig_data = NULL;
|
||||
|
||||
@@ -2674,8 +2654,8 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m
|
||||
if ((mod_info->ds == SR_DS_OPERATIONAL) && (mod_info->ds2 == SR_DS_OPERATIONAL)) {
|
||||
assert(sess);
|
||||
|
||||
- /* check whether this session even has any push oper data for this module */
|
||||
- if ((err_info = sr_shmext_oper_push_get(conn, mod->shm_mod, mod->ly_mod->name, sess->sid, NULL, &has_data,
|
||||
+ /* check whether this session even has any push oper data and next push data for this module */
|
||||
+ if (sess->oper_push_mod_count && (err_info = sr_shmext_oper_push_get(conn, mod->shm_mod, mod->ly_mod->name, sess->sid, NULL, &has_data,
|
||||
SR_LOCK_READ))) {
|
||||
return err_info;
|
||||
}
|
||||
@@ -2735,7 +2715,7 @@ sr_modinfo_module_data_load(struct sr_mod_info_s *mod_info, struct sr_mod_info_m
|
||||
/* no cached data or unusable */
|
||||
|
||||
if ((mod_info->ds == SR_DS_OPERATIONAL) && (mod_info->ds2 == SR_DS_RUNNING)) {
|
||||
- /* we need the whole runnign DS to avoid not getting parents of oper pull subscriptions and so considering
|
||||
+ /* we need the whole running DS to avoid not getting parents of oper pull subscriptions and so considering
|
||||
* them incorrectly as non-existent */
|
||||
xpaths = NULL;
|
||||
xpath_count = 0;
|
||||
@@ -3839,6 +3819,35 @@ cleanup:
|
||||
return err_info;
|
||||
}
|
||||
|
||||
+static
|
||||
+sr_error_info_t *
|
||||
+sr_modinfo_push_oper_mod_update_sess(sr_session_ctx_t *sess, const char *mod_name, int has_data)
|
||||
+{
|
||||
+ sr_error_info_t *err_info = NULL;
|
||||
+ uint32_t i;
|
||||
+ void *mem;
|
||||
+
|
||||
+ for (i = 0; i < sess->oper_push_mod_count; ++i) {
|
||||
+ if (!strcmp(sess->oper_push_mods[i].name, mod_name)) {
|
||||
+ /* already added, update has_data */
|
||||
+ sess->oper_push_mods[i].has_data = has_data;
|
||||
+ return NULL;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* add new module */
|
||||
+ mem = realloc(sess->oper_push_mods, (i + 1) * sizeof *(sess->oper_push_mods));
|
||||
+ SR_CHECK_MEM_GOTO(!mem, err_info, cleanup);
|
||||
+ sess->oper_push_mods = mem;
|
||||
+
|
||||
+ sess->oper_push_mods[i].name = strdup(mod_name);
|
||||
+ SR_CHECK_MEM_GOTO(!sess->oper_push_mods[i].name, err_info, cleanup);
|
||||
+ sess->oper_push_mods[i].has_data = has_data;
|
||||
+ ++sess->oper_push_mod_count;
|
||||
+cleanup:
|
||||
+ return err_info;
|
||||
+}
|
||||
+
|
||||
sr_error_info_t *
|
||||
sr_modinfo_data_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session, int shmmod_session_del)
|
||||
{
|
||||
@@ -3911,12 +3920,14 @@ sr_modinfo_data_store(struct sr_mod_info_s *mod_info, sr_session_ctx_t *session,
|
||||
} else {
|
||||
/* stored oper data, update info in mod/ext SHM */
|
||||
if ((err_info = sr_shmext_oper_push_update(mod_info->conn, mod->shm_mod, mod->ly_mod->name,
|
||||
- sid, 0, mod_data ? 1 : 0, SR_LOCK_WRITE))) {
|
||||
+ sid, 0, !!mod_data, SR_LOCK_WRITE))) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
- /* remember to discard the push oper data on session stop */
|
||||
- session->push_oper_data = 1;
|
||||
+ /* cache the modified module in the session */
|
||||
+ if ((err_info = sr_modinfo_push_oper_mod_update_sess(session, mod->ly_mod->name, !!mod_data))) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
}
|
||||
}
|
||||
}
|
||||
diff --git a/src/sysrepo.c b/src/sysrepo.c
|
||||
index 614b5454..797de6aa 100644
|
||||
--- a/src/sysrepo.c
|
||||
+++ b/src/sysrepo.c
|
||||
@@ -713,6 +713,7 @@ _sr_session_stop(sr_session_ctx_t *session)
|
||||
{
|
||||
sr_error_info_t *err_info = NULL, *tmp_err;
|
||||
sr_datastore_t ds;
|
||||
+ uint32_t i;
|
||||
|
||||
/* subscriptions need to be freed before, with a WRITE lock */
|
||||
assert(!session->subscription_count && !session->subscriptions);
|
||||
@@ -721,7 +722,7 @@ _sr_session_stop(sr_session_ctx_t *session)
|
||||
tmp_err = sr_session_notif_buf_stop(session);
|
||||
sr_errinfo_merge(&err_info, tmp_err);
|
||||
|
||||
- if (session->push_oper_data) {
|
||||
+ if (session->oper_push_mod_count) {
|
||||
/* free any stored operational data and the SHM ext push oper data entries */
|
||||
_sr_discard_oper_changes(session, NULL, 1, 0);
|
||||
}
|
||||
@@ -755,6 +756,13 @@ _sr_session_stop(sr_session_ctx_t *session)
|
||||
sr_release_data(session->dt[ds].edit);
|
||||
lyd_free_all(session->dt[ds].diff);
|
||||
}
|
||||
+
|
||||
+ /* free any push oper module names */
|
||||
+ for (i = 0; i < session->oper_push_mod_count; ++i) {
|
||||
+ free(session->oper_push_mods[i].name);
|
||||
+ }
|
||||
+ free(session->oper_push_mods);
|
||||
+
|
||||
sr_rwlock_destroy(&session->notif_buf.lock);
|
||||
free(session);
|
||||
|
||||
@@ -4708,6 +4716,10 @@ sr_get_oper_changes(sr_session_ctx_t *session, const char *module_name, sr_data_
|
||||
|
||||
*data = NULL;
|
||||
|
||||
+ if (!session->oper_push_mod_count) {
|
||||
+ return sr_api_ret(session, err_info);
|
||||
+ }
|
||||
+
|
||||
SR_MODINFO_INIT(mod_info, conn, SR_DS_OPERATIONAL, SR_DS_OPERATIONAL);
|
||||
|
||||
/* CONTEXT LOCK */
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
From fef86228fcb8636d53faf43b36afd92b7161162b Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Tue, 14 Jan 2025 08:53:59 +0100
|
||||
Subject: [PATCH 10/22] modinfo REFACTOR formatting and missing doxygen
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/modinfo.c | 11 +++++++++--
|
||||
1 file changed, 9 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/src/modinfo.c b/src/modinfo.c
|
||||
index d5c27672..ff6ffc40 100644
|
||||
--- a/src/modinfo.c
|
||||
+++ b/src/modinfo.c
|
||||
@@ -3819,8 +3819,15 @@ cleanup:
|
||||
return err_info;
|
||||
}
|
||||
|
||||
-static
|
||||
-sr_error_info_t *
|
||||
+/**
|
||||
+ * @brief Update push oper mod data in a session, add a new module if not yet present.
|
||||
+ *
|
||||
+ * @param[in] sess Session to update.
|
||||
+ * @param[in] mod_name Module name.
|
||||
+ * @param[in] has_data Flag to store.
|
||||
+ * @return err_info, NULL on success.
|
||||
+ */
|
||||
+static sr_error_info_t *
|
||||
sr_modinfo_push_oper_mod_update_sess(sr_session_ctx_t *sess, const char *mod_name, int has_data)
|
||||
{
|
||||
sr_error_info_t *err_info = NULL;
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
From a593e1c993ae4dd7aa2942aa046633c8aaeb5ee4 Mon Sep 17 00:00:00 2001
|
||||
From: Irfan <irfan.haslanded@gmail.com>
|
||||
Date: Tue, 14 Jan 2025 08:59:44 +0100
|
||||
Subject: [PATCH 11/22] shm_ext BUGFIX remap READ lock is enough (#3496)
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
`sr_shmext_rpc_sub_remove_dead()` calls `sr_shmext_rpc_sub_stop()`
|
||||
which doesn't need a WRITE `ext_remap_lock`.
|
||||
|
||||
sr_shmext_recover_sub_rpc() already only acquire a READ lock.
|
||||
|
||||
Subscriptions can only be removed, which does not remap the SHM.
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/shm_ext.c | 8 ++++----
|
||||
1 file changed, 4 insertions(+), 4 deletions(-)
|
||||
|
||||
diff --git a/src/shm_ext.c b/src/shm_ext.c
|
||||
index 78930469..917a049a 100644
|
||||
--- a/src/shm_ext.c
|
||||
+++ b/src/shm_ext.c
|
||||
@@ -1476,8 +1476,8 @@ sr_shmext_rpc_sub_remove_dead(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_co
|
||||
sr_mod_rpc_sub_t *shm_sub;
|
||||
char *path = NULL;
|
||||
|
||||
- /* EXT WRITE LOCK */
|
||||
- if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_WRITE, 1, __func__))) {
|
||||
+ /* EXT READ LOCK */
|
||||
+ if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 1, __func__))) {
|
||||
sr_errinfo_free(&err_info);
|
||||
return;
|
||||
}
|
||||
@@ -1502,8 +1502,8 @@ sr_shmext_rpc_sub_remove_dead(sr_conn_ctx_t *conn, off_t *subs, uint32_t *sub_co
|
||||
path = NULL;
|
||||
}
|
||||
|
||||
- /* EXT WRITE UNLOCK */
|
||||
- sr_shmext_conn_remap_unlock(conn, SR_LOCK_WRITE, 1, __func__);
|
||||
+ /* EXT READ UNLOCK */
|
||||
+ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__);
|
||||
}
|
||||
|
||||
sr_error_info_t *
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
From ce908d743bc9f49d5986c748dba71be194b7df89 Mon Sep 17 00:00:00 2001
|
||||
From: Michal Vasko <mvasko@cesnet.cz>
|
||||
Date: Tue, 14 Jan 2025 09:57:49 +0100
|
||||
Subject: [PATCH 12/22] modinfo OPTIMIZE avoid long ext lock holding
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Fixes #3497
|
||||
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
---
|
||||
src/modinfo.c | 77 +++++++++++++++++++++++++++++++--------------------
|
||||
1 file changed, 47 insertions(+), 30 deletions(-)
|
||||
|
||||
diff --git a/src/modinfo.c b/src/modinfo.c
|
||||
index ff6ffc40..f969f363 100644
|
||||
--- a/src/modinfo.c
|
||||
+++ b/src/modinfo.c
|
||||
@@ -4,8 +4,8 @@
|
||||
* @brief routines for working with modinfo structure
|
||||
*
|
||||
* @copyright
|
||||
- * Copyright (c) 2018 - 2023 Deutsche Telekom AG.
|
||||
- * Copyright (c) 2018 - 2023 CESNET, z.s.p.o.
|
||||
+ * Copyright (c) 2018 - 2025 Deutsche Telekom AG.
|
||||
+ * Copyright (c) 2018 - 2025 CESNET, z.s.p.o.
|
||||
*
|
||||
* This source code is licensed under BSD 3-Clause License (the "License").
|
||||
* You may not use this file except in compliance with the License.
|
||||
@@ -1254,49 +1254,69 @@ sr_module_oper_data_load(struct sr_mod_info_mod_s *mod, sr_conn_ctx_t *conn, uin
|
||||
struct lyd_node **mod_oper_data, struct lyd_node **data)
|
||||
{
|
||||
sr_error_info_t *err_info = NULL;
|
||||
- sr_mod_oper_push_t *oper_push;
|
||||
+ sr_mod_oper_push_t *oper_push_dup = NULL, *oper_push_ext;
|
||||
struct lyd_node *mod_data = NULL, *next, *node;
|
||||
const struct lys_module *ly_mod;
|
||||
const char *xpath;
|
||||
struct ly_set *set;
|
||||
- uint32_t i, j, merge_opts;
|
||||
- int last_sid = 0;
|
||||
+ uint32_t i, j, merge_opts, oper_push_count = 0;
|
||||
+ int last_sid = 0, dead_cid = 0;
|
||||
|
||||
/* EXT READ LOCK */
|
||||
if ((err_info = sr_shmext_conn_remap_lock(conn, SR_LOCK_READ, 1, __func__))) {
|
||||
- return err_info;
|
||||
+ goto cleanup;
|
||||
}
|
||||
|
||||
- oper_push = (sr_mod_oper_push_t *)(conn->ext_shm.addr + mod->shm_mod->oper_push_data);
|
||||
- for (i = 0; i < mod->shm_mod->oper_push_data_count; ++i) {
|
||||
- if (!sr_conn_is_alive(oper_push[i].cid)) {
|
||||
- /* EXT READ UNLOCK */
|
||||
- sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__);
|
||||
-
|
||||
- /* recover oper push data of all dead connections */
|
||||
- if ((err_info = sr_shmmod_del_module_oper_data(conn, mod->ly_mod, &mod->state, mod->shm_mod, 1))) {
|
||||
- goto cleanup;
|
||||
+ if (mod->shm_mod->oper_push_data_count) {
|
||||
+ /* make a local copy of the array, with only alive connections */
|
||||
+ oper_push_dup = malloc(mod->shm_mod->oper_push_data_count * sizeof *oper_push_dup);
|
||||
+ if (!oper_push_dup) {
|
||||
+ SR_ERRINFO_MEM(&err_info);
|
||||
+ } else {
|
||||
+ oper_push_ext = (sr_mod_oper_push_t *)(conn->ext_shm.addr + mod->shm_mod->oper_push_data);
|
||||
+ for (i = 0; i < mod->shm_mod->oper_push_data_count; ++i) {
|
||||
+ if (!sr_conn_is_alive(oper_push_ext[i].cid)) {
|
||||
+ /* remember to remove the dead connections */
|
||||
+ dead_cid = 1;
|
||||
+ } else {
|
||||
+ oper_push_dup[oper_push_count] = oper_push_ext[i];
|
||||
+ ++oper_push_count;
|
||||
+ }
|
||||
}
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* EXT READ UNLOCK */
|
||||
+ sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__);
|
||||
+
|
||||
+ if (err_info) {
|
||||
+ goto cleanup;
|
||||
+ }
|
||||
|
||||
- /* call the function again */
|
||||
- err_info = sr_module_oper_data_load(mod, conn, sid, mod_oper_data, data);
|
||||
+ if (dead_cid) {
|
||||
+ /* recover oper push data of all dead connections */
|
||||
+ if ((err_info = sr_shmmod_del_module_oper_data(conn, mod->ly_mod, &mod->state, mod->shm_mod, 1))) {
|
||||
goto cleanup;
|
||||
- } else if (!oper_push[i].has_data) {
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ for (i = 0; i < oper_push_count; ++i) {
|
||||
+ if (!oper_push_dup[i].has_data) {
|
||||
/* no push oper data */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* push data entries are ordered */
|
||||
- assert(!i || (oper_push[i].order > oper_push[i - 1].order));
|
||||
+ assert(!i || (oper_push_dup[i].order > oper_push_dup[i - 1].order));
|
||||
|
||||
- if (oper_push[i].sid == sid) {
|
||||
+ if (oper_push_dup[i].sid == sid) {
|
||||
last_sid = 1;
|
||||
}
|
||||
if (!last_sid || !mod_oper_data) {
|
||||
/* load push oper data for the session */
|
||||
- if ((err_info = sr_module_file_data_append(mod->ly_mod, mod->ds_handle, SR_DS_OPERATIONAL, oper_push[i].cid,
|
||||
- oper_push[i].sid, NULL, 0, &mod_data))) {
|
||||
- goto cleanup_unlock;
|
||||
+ if ((err_info = sr_module_file_data_append(mod->ly_mod, mod->ds_handle, SR_DS_OPERATIONAL,
|
||||
+ oper_push_dup[i].cid, oper_push_dup[i].sid, NULL, 0, &mod_data))) {
|
||||
+ goto cleanup;
|
||||
}
|
||||
} else {
|
||||
use_mod_oper_data:
|
||||
@@ -1324,12 +1344,12 @@ use_mod_oper_data:
|
||||
|
||||
/* select the nodes to remove */
|
||||
if ((err_info = sr_lyd_find_xpath(*data, xpath, &set))) {
|
||||
- goto cleanup_unlock;
|
||||
+ goto cleanup;
|
||||
}
|
||||
|
||||
/* get rid of all redundant results that are descendants of another result */
|
||||
if ((err_info = sr_xpath_set_filter_subtrees(set))) {
|
||||
- goto cleanup_unlock;
|
||||
+ goto cleanup;
|
||||
}
|
||||
|
||||
/* free all the selected subtrees */
|
||||
@@ -1346,7 +1366,7 @@ use_mod_oper_data:
|
||||
merge_opts = LYD_MERGE_WITH_FLAGS;
|
||||
}
|
||||
if ((err_info = sr_lyd_merge_module(data, mod_data, mod->ly_mod, sr_oper_data_merge_cb, NULL, merge_opts))) {
|
||||
- goto cleanup_unlock;
|
||||
+ goto cleanup;
|
||||
}
|
||||
mod_data = NULL;
|
||||
|
||||
@@ -1362,11 +1382,8 @@ use_mod_oper_data:
|
||||
goto use_mod_oper_data;
|
||||
}
|
||||
|
||||
-cleanup_unlock:
|
||||
- /* EXT READ UNLOCK */
|
||||
- sr_shmext_conn_remap_unlock(conn, SR_LOCK_READ, 1, __func__);
|
||||
-
|
||||
cleanup:
|
||||
+ free(oper_push_dup);
|
||||
lyd_free_siblings(mod_data);
|
||||
return err_info;
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
-1501
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user