diff --git a/doc/sphinx/arm/ext-netconf.rst b/doc/sphinx/arm/ext-netconf.rst index c496376816..29c634a205 100644 --- a/doc/sphinx/arm/ext-netconf.rst +++ b/doc/sphinx/arm/ext-netconf.rst @@ -22,21 +22,10 @@ Installing NETCONF To get its NETCONF capabilities, Kea requires the v2 versions of libyang and Sysrepo. The specific versions that have been thoroughly tested with Kea are: -* libyang v2.1.4 -* sysrepo v2.2.12 -* libyang-cpp v1.1.0 (ae7d649ea75da081725c119dd553b2ef3121a6f8) -* sysrepo-cpp v1.1.0 (02634174ffc60568301c3d9b9b7cf710cff6a586) - -.. note:: - - For users who are unable to upgrade to one of the versions of libyang - and Sysrepo listed above, these are the oldest versions known to work - reliably with current Kea releases: - - * libyang v2.0.256 (56d4e07ef1cdeab3eb2e6700247f83ec9148edcc) - * sysrepo v2.1.84 - * libyang-cpp v1.1.0 (7824d9a862f2dc1d8ad4f6a90ab6cee9200f7c81) - * sysrepo-cpp v1.1.0 (e66b2f0c53a428eeb743d355cf86fb30e8e491f1) +* libyang v3.12.2 (da7272e19d9e27d1bfdd68108fa9dce25fbdf5e8) +* sysrepo v3.6.11 (b2d60c137aa5179af2af0ce1243d4147c4e5f974) +* libyang-cpp v3 (f3cd4e05462a16e81d6bfd0c4a5b385cf88a8549) +* sysrepo-cpp v3 (fe4edfa3998fdf312099ee1fb08a06983b6907f6) .. note:: diff --git a/hammer.py b/hammer.py index 926e6587d4..f223e2b2b0 100755 --- a/hammer.py +++ b/hammer.py @@ -7,6 +7,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # pylint: disable=broad-exception-caught +# pylint: disable=logging-fstring-interpolation """Hammer - Kea development environment management tool.""" @@ -1211,23 +1212,25 @@ def _install_gtest_sources(): def _install_libyang_from_sources(ignore_errors=False): """Install libyang from sources.""" - for prefix in ['/usr', '/usr/local']: - libyang_so_candidates = [f'{prefix}/lib/libyang.so', f'{prefix}/lib64/libyang.so'] - libyang_header = f'{prefix}/include/libyang/version.h' - if (any(os.path.exists(i) for i in libyang_so_candidates) and os.path.exists(libyang_header) and - execute(f"grep -F '#define LY_VERSION_MAJOR 2' '{libyang_header}'", raise_error=False) == 0): - log.info('libyang is already installed at %s.', libyang_header) - return + version = '3.12.2' - version = 'v2.1.4' + libdirs = [f'{usr}/{lib}' for usr in ['/usr', '/usr/local'] for lib in ['lib', 'lib64']] + for libdir in libdirs: + pc_file = f'{libdir}/pkgconfig/libyang.pc' + if os.path.exists(pc_file): + with open(pc_file, encoding='utf-8') as file: + for line in file: + if line.rstrip('\n') == f'Version: {version}': + log.info(f'libyang is already installed: {pc_file}.') + return execute('rm -rf ~/.hammer-tmp') execute('mkdir -p ~/.hammer-tmp') try: execute('git clone https://github.com/CESNET/libyang.git ~/.hammer-tmp/libyang') - execute(f'git checkout {version}', cwd='~/.hammer-tmp/libyang') + execute(f'git checkout v{version}', cwd='~/.hammer-tmp/libyang') execute('mkdir ~/.hammer-tmp/libyang/build') - execute('cmake -DBUILD_TESTING=OFF -DCMAKE_C_FLAGS="-Wno-incompatible-pointer-types" ..', + execute('cmake -DBUILD_TESTING=OFF ..', cwd='~/.hammer-tmp/libyang/build') execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/libyang/build') execute('sudo make install', cwd='~/.hammer-tmp/libyang/build') @@ -1244,15 +1247,17 @@ def _install_libyang_from_sources(ignore_errors=False): def _install_sysrepo_from_sources(ignore_errors=False): """Install sysrepo from sources.""" - for prefix in ['/usr', '/usr/local']: - sysrepo_so_candidates = [f'{prefix}/lib/libsysrepo.so', f'{prefix}/lib64/libsysrepo.so'] - sysrepo_header = f'{prefix}/include/sysrepo/version.h' - if (any(os.path.exists(i) for i in sysrepo_so_candidates) and os.path.exists(sysrepo_header) and - execute(f"grep -F '#define SR_VERSION_MAJOR 7' '{sysrepo_header}'", raise_error=False) == 0): - log.info('sysrepo is already installed at %s.', sysrepo_header) - return + version = '3.6.11' - version = 'v2.2.12' + libdirs = [f'{usr}/{lib}' for usr in ['/usr', '/usr/local'] for lib in ['lib', 'lib64']] + for libdir in libdirs: + pc_file = f'{libdir}/pkgconfig/sysrepo.pc' + if os.path.exists(pc_file): + with open(pc_file, encoding='utf-8') as file: + for line in file: + if line.rstrip('\n') == f'Version: {version}': + log.info(f'sysrepo is already installed: {pc_file}.') + return # Create repository for YANG modules and change ownership to current user. execute('sudo mkdir -p /etc/sysrepo') @@ -1262,7 +1267,7 @@ def _install_sysrepo_from_sources(ignore_errors=False): execute('mkdir -p ~/.hammer-tmp') try: execute('git clone https://github.com/sysrepo/sysrepo.git ~/.hammer-tmp/sysrepo') - execute(f'git checkout {version}', cwd='~/.hammer-tmp/sysrepo') + execute(f'git checkout v{version}', cwd='~/.hammer-tmp/sysrepo') execute('mkdir ~/.hammer-tmp/sysrepo/build') execute('cmake -DBUILD_TESTING=OFF -DREPO_PATH=/etc/sysrepo ..', cwd='~/.hammer-tmp/sysrepo/build') execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/sysrepo/build') @@ -1280,33 +1285,23 @@ def _install_sysrepo_from_sources(ignore_errors=False): def _install_libyang_cpp_from_sources(ignore_errors=False): """Install libyang-cpp from sources.""" - for prefix_lib in ['/usr/lib', '/usr/lib64', '/usr/local/lib', '/usr/local/lib64']: - libyang_cpp_so = f'{prefix_lib}/libyang-cpp.so' - libyang_cpp_pc = f'{prefix_lib}/pkgconfig/libyang-cpp.pc' - if (os.path.exists(libyang_cpp_so) and os.path.exists(libyang_cpp_pc) and - execute(f"grep -F 'Version: 1.1.0' '{libyang_cpp_pc}'", raise_error=False) == 0): - log.info('libyang-cpp is already installed at %s.', libyang_cpp_so) - return + version = '3' - version = 'ae7d649ea75da081725c119dd553b2ef3121a6f8' + libdirs = [f'{usr}/{lib}' for usr in ['/usr', '/usr/local'] for lib in ['lib', 'lib64']] + for libdir in libdirs: + pc_file = f'{libdir}/pkgconfig/libyang-cpp.pc' + if os.path.exists(pc_file): + with open(pc_file, encoding='utf-8') as file: + for line in file: + if line.rstrip('\n') == f'Version: {version}': + log.info(f'libyang-cpp is already installed: {pc_file}.') + return execute('rm -rf ~/.hammer-tmp') execute('mkdir -p ~/.hammer-tmp') try: execute('git clone https://github.com/CESNET/libyang-cpp.git ~/.hammer-tmp/libyang-cpp') - execute(f'git checkout {version}', cwd='~/.hammer-tmp/libyang-cpp') - # New cpp compiler is more picky about missing headers. (ex. Fedora 40) - execute("""git apply < -+#include - #include -EOF -""", cwd='~/.hammer-tmp/libyang-cpp') + execute(f'git checkout v{version}', cwd='~/.hammer-tmp/libyang-cpp') execute('mkdir ~/.hammer-tmp/libyang-cpp/build') execute('cmake -DBUILD_TESTING=OFF .. ', cwd='~/.hammer-tmp/libyang-cpp/build') execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/libyang-cpp/build') @@ -1324,21 +1319,23 @@ EOF def _install_sysrepo_cpp_from_sources(ignore_errors=False): """Install sysrepo-cpp from sources.""" - for prefix_lib in ['/usr/lib', '/usr/lib64', '/usr/local/lib', '/usr/local/lib64']: - sysrepo_cpp_so = f'{prefix_lib}/libsysrepo-cpp.so' - sysrepo_cpp_pc = f'{prefix_lib}/pkgconfig/sysrepo-cpp.pc' - if (os.path.exists(sysrepo_cpp_so) and os.path.exists(sysrepo_cpp_pc) and - execute(f"grep -F 'Version: 1.1.0' '{sysrepo_cpp_pc}'", raise_error=False) == 0): - log.info('sysrepo-cpp is already installed at %s.', sysrepo_cpp_so) - return + version = '3' - version = '02634174ffc60568301c3d9b9b7cf710cff6a586' + libdirs = [f'{usr}/{lib}' for usr in ['/usr', '/usr/local'] for lib in ['lib', 'lib64']] + for libdir in libdirs: + pc_file = f'{libdir}/pkgconfig/sysrepo-cpp.pc' + if os.path.exists(pc_file): + with open(pc_file, encoding='utf-8') as file: + for line in file: + if line.rstrip('\n') == f'Version: {version}': + log.info(f'sysrepo-cpp is already installed: {pc_file}.') + return execute('rm -rf ~/.hammer-tmp') execute('mkdir -p ~/.hammer-tmp') try: execute('git clone https://github.com/sysrepo/sysrepo-cpp.git ~/.hammer-tmp/sysrepo-cpp') - execute(f'git checkout {version}', cwd='~/.hammer-tmp/sysrepo-cpp') + execute(f'git checkout v{version}', cwd='~/.hammer-tmp/sysrepo-cpp') execute('mkdir ~/.hammer-tmp/sysrepo-cpp/build') execute('cmake -DBUILD_TESTING=OFF .. ', cwd='~/.hammer-tmp/sysrepo-cpp/build') execute('make -j $(nproc || gnproc || echo 1)', cwd='~/.hammer-tmp/sysrepo-cpp/build') diff --git a/src/bin/netconf/tests/netconf_unittests.cc b/src/bin/netconf/tests/netconf_unittests.cc index 23da4c91b7..822cb7b0f8 100644 --- a/src/bin/netconf/tests/netconf_unittests.cc +++ b/src/bin/netconf/tests/netconf_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2025 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -1164,7 +1164,7 @@ TEST_F(NetconfAgentTest, noValidate) { "BOGUS", LeafBaseType::String, true } }); EXPECT_THROW_MSG(repr.set(tree1, *agent_->running_sess_), sysrepo::Error, - "Session::applyChanges: Couldn't apply changes: SR_ERR_CALLBACK_FAILED"); + "Session::applyChanges: Couldn't apply changes: SR_ERR_VALIDATION_FAILED\n Validation failed (SR_ERR_VALIDATION_FAILED)"); } } // namespace diff --git a/src/lib/yang/tests/translator_option_data_unittests.cc b/src/lib/yang/tests/translator_option_data_unittests.cc index 96a827f88b..ca4fea2623 100644 --- a/src/lib/yang/tests/translator_option_data_unittests.cc +++ b/src/lib/yang/tests/translator_option_data_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2025 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -183,15 +183,18 @@ TEST_F(TranslatorOptionDataListTestv4, set) { EXPECT_THROW_MSG(sess_->setItem(xoption + "/code", s_code), sysrepo::Error, "Session::setItem: Couldn't set " "'/kea-dhcp4-server:config/option-data[code='100'][space='dns'][data='12121212']/code' to " - "'15': SR_ERR_INVAL_ARG"); + "'15': SR_ERR_INVAL_ARG\n Editing list key \"code\" is not supported, edit list instances " + "instead. (SR_ERR_INVAL_ARG)"); EXPECT_THROW_MSG(sess_->setItem(xoption + "/space", s_space), sysrepo::Error, "Session::setItem: Couldn't set " "'/kea-dhcp4-server:config/option-data[code='100'][space='dns'][data='12121212']/space' to " - "'dhcp4': SR_ERR_INVAL_ARG"); + "'dhcp4': SR_ERR_INVAL_ARG\n Editing list key \"space\" is not supported, edit list instances " + "instead. (SR_ERR_INVAL_ARG)"); EXPECT_THROW_MSG(sess_->setItem(xoption + "/data", s_data), sysrepo::Error, "Session::setItem: Couldn't set " "'/kea-dhcp4-server:config/option-data[code='100'][space='dns'][data='12121212']/data' to " - "'12121212': SR_ERR_INVAL_ARG"); + "'12121212': SR_ERR_INVAL_ARG\n Editing list key \"data\" is not supported, edit list " + "instances instead. (SR_ERR_INVAL_ARG)"); // Setting the list element directly should work. EXPECT_NO_THROW_LOG(sess_->setItem(xoption, std::nullopt)); @@ -228,15 +231,18 @@ TEST_F(TranslatorOptionDataListTestv6, set) { EXPECT_THROW_MSG(sess_->setItem(xoption + "/code", s_code), sysrepo::Error, "Session::setItem: Couldn't set " "'/kea-dhcp6-server:config/option-data[code='100'][space='dns'][data='12121212']/code' to " - "'15': SR_ERR_INVAL_ARG"); + "'15': SR_ERR_INVAL_ARG\n Editing list key \"code\" is not supported, edit list instances " + "instead. (SR_ERR_INVAL_ARG)"); EXPECT_THROW_MSG(sess_->setItem(xoption + "/space", s_space), sysrepo::Error, "Session::setItem: Couldn't set " "'/kea-dhcp6-server:config/option-data[code='100'][space='dns'][data='12121212']/space' to " - "'dhcp6': SR_ERR_INVAL_ARG"); + "'dhcp6': SR_ERR_INVAL_ARG\n Editing list key \"space\" is not supported, edit list instances " + "instead. (SR_ERR_INVAL_ARG)"); EXPECT_THROW_MSG(sess_->setItem(xoption + "/data", s_data), sysrepo::Error, "Session::setItem: Couldn't set " "'/kea-dhcp6-server:config/option-data[code='100'][space='dns'][data='12121212']/data' to " - "'12121212': SR_ERR_INVAL_ARG"); + "'12121212': SR_ERR_INVAL_ARG\n Editing list key \"data\" is not supported, edit list instances " + "instead. (SR_ERR_INVAL_ARG)"); // Setting the list element directly should work. EXPECT_NO_THROW_LOG(sess_->setItem(xoption, std::nullopt)); diff --git a/src/lib/yang/tests/translator_shared_network_unittests.cc b/src/lib/yang/tests/translator_shared_network_unittests.cc index e8977132ee..36ceadbb4d 100644 --- a/src/lib/yang/tests/translator_shared_network_unittests.cc +++ b/src/lib/yang/tests/translator_shared_network_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2025 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -155,8 +155,9 @@ TEST_F(TranslatorSharedNetworksTestKeaV6, getList) { "\"subnet\": \"2001:db8:101::/48\" }, " "{ \"id\": 102, \"subnet\": \"2001:db8:102::/48\" } ] }"; + // Since v3, lists seem to be ordered by keys. Shouldn't be too big of a problem. const string exp_both = - "[ " + exp_net1 + ", " + exp_net2 + " ]"; + "[ " + exp_net2 + ", " + exp_net1 + " ]"; // Create the subnet1: 2001:db8:1::/48 #1 in shared network foo. const string& xsubnet1 = xnetwork1 + "/subnet6[id='1']/subnet"; diff --git a/src/lib/yang/tests/translator_unittests.cc b/src/lib/yang/tests/translator_unittests.cc index 96e0d2d05e..7bb4e9bab3 100644 --- a/src/lib/yang/tests/translator_unittests.cc +++ b/src/lib/yang/tests/translator_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2024 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2025 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -344,13 +344,17 @@ TEST_F(TranslatorTest, getItem) { // Not existing. xpath = "/keatest-module:main/no_such_node"; - EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath)); + EXPECT_THROW_MSG(element = translator->getItemFromAbsoluteXpath(xpath), NetconfError, + "getting item at '/keatest-module:main/no_such_node': Session::getData: " + "Couldn't get '/keatest-module:main/no_such_node': SR_ERR_NOT_FOUND"); EXPECT_FALSE(element); element.reset(); // Check error. xpath = "null"; - EXPECT_NO_THROW_LOG(element = translator->getItemFromAbsoluteXpath(xpath)); + EXPECT_THROW_MSG(element = translator->getItemFromAbsoluteXpath(xpath), NetconfError, + "getting item at 'null': Session::getData: Couldn't get 'null': " + "SR_ERR_NOT_FOUND"); EXPECT_FALSE(element); } diff --git a/src/lib/yang/testutils/translator_test.cc b/src/lib/yang/testutils/translator_test.cc index 36012db557..92b2bf2a64 100644 --- a/src/lib/yang/testutils/translator_test.cc +++ b/src/lib/yang/testutils/translator_test.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2025 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -27,7 +27,7 @@ YangRepr::YangReprItem::getUnionType(Value const& value) { static_assert( std::is_same< Value, std::variant, + uint64_t, bool, Empty, Binary, string, InstanceIdentifier, Decimal64, vector, Enum, IdentityRef>>::value, "Value type has changed. The if statement needs to be adjusted to include all alternatives " "of the std::variant."); @@ -56,7 +56,7 @@ YangRepr::YangReprItem::getUnionType(Value const& value) { return LeafBaseType::Binary; } else if (holds_alternative(value)) { return LeafBaseType::String; - } else if (holds_alternative>(value)) { + } else if (holds_alternative(value)) { return LeafBaseType::InstanceIdentifier; } else if (holds_alternative(value)) { return LeafBaseType::Dec64;