mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 09:13:01 +02:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3fe203005 | ||
|
|
18f7c779c9 | ||
|
|
454207c185 | ||
|
|
d877806e2b | ||
|
|
5ed7182361 | ||
|
|
3f695b6c62 | ||
|
|
4f3605a5f7 | ||
|
|
e89dde0d25 | ||
|
|
e1ecf6b71d |
@@ -95,11 +95,6 @@ jobs:
|
||||
name: artifact-${{ matrix.target }}
|
||||
path: output/*.tar.gz
|
||||
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: artifact-disk-image-${{ matrix.target }}
|
||||
path: output/images/*.qcow2
|
||||
|
||||
release:
|
||||
name: Release Infix ${{ github.ref_name }}
|
||||
needs: build
|
||||
@@ -146,11 +141,6 @@ jobs:
|
||||
for file in *.tar.gz; do
|
||||
sha256sum $file > $file.sha256
|
||||
done
|
||||
if ls *.qcow2 &>/dev/null; then
|
||||
for file in *.qcow2; do
|
||||
sha256sum "$file" > "$file.sha256"
|
||||
done
|
||||
fi
|
||||
|
||||
- name: Extract ChangeLog entry ...
|
||||
run: |
|
||||
@@ -165,7 +155,7 @@ jobs:
|
||||
makeLatest: ${{ steps.rel.outputs.latest }}
|
||||
discussionCategory: ${{ steps.rel.outputs.cat }}
|
||||
bodyFile: release.md
|
||||
artifacts: "*.tar.gz*,*.qcow2*"
|
||||
artifacts: "*.tar.gz*"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
|
||||
@@ -230,6 +230,7 @@ CONFIG_MAC80211_LEDS=y
|
||||
CONFIG_RFKILL=y
|
||||
CONFIG_NET_9P=y
|
||||
CONFIG_NET_9P_VIRTIO=y
|
||||
# CONFIG_ETHTOOL_NETLINK is not set
|
||||
CONFIG_PCI=y
|
||||
CONFIG_PCIEPORTBUS=y
|
||||
CONFIG_PCI_IOV=y
|
||||
@@ -480,6 +481,7 @@ CONFIG_I2C=y
|
||||
CONFIG_I2C_CHARDEV=y
|
||||
CONFIG_I2C_MUX=y
|
||||
CONFIG_I2C_MUX_PCA954x=y
|
||||
CONFIG_I2C_DESIGNWARE_PLATFORM=y
|
||||
CONFIG_I2C_GPIO=m
|
||||
CONFIG_I2C_RK3X=y
|
||||
CONFIG_I2C_SLAVE=y
|
||||
|
||||
@@ -127,7 +127,7 @@ bootdata=
|
||||
diskimg=disk.img
|
||||
bootimg=
|
||||
bootpart=
|
||||
tmpimage=$(mktemp)
|
||||
|
||||
while getopts "a:b:B:n:s:" opt; do
|
||||
case ${opt} in
|
||||
a)
|
||||
@@ -166,7 +166,7 @@ awk \
|
||||
-vimgsize=$imgsize \
|
||||
-vcfgsize=$cfgsize \
|
||||
-vvarsize=$varsize \
|
||||
-vdiskimg=$tmpimage \
|
||||
-vdiskimg=$diskimg \
|
||||
-vbootimg="$bootimg" -vbootpart="$bootpart" \
|
||||
'{
|
||||
sub(/@TOTALSIZE@/, total);
|
||||
@@ -211,7 +211,5 @@ genimage \
|
||||
--rootpath "$root" \
|
||||
--tmppath "$tmp" \
|
||||
--inputpath "$BINARIES_DIR" \
|
||||
--outputpath "$BINARIES_DIR" \
|
||||
--config "$root/genimage.cfg"
|
||||
|
||||
qemu-img convert -c -O qcow2 "$tmpimage" "$BINARIES_DIR/$diskimg"
|
||||
rm "$tmpimage"
|
||||
|
||||
@@ -23,7 +23,7 @@ if [ -n "$IMAGE_ID" ]; then
|
||||
else
|
||||
NAME="$INFIX_ID"-$(echo "$BR2_ARCH" | tr _ - | sed 's/x86-64/x86_64/')
|
||||
fi
|
||||
diskimg=disk.qcow2
|
||||
diskimg=disk.img
|
||||
|
||||
ver()
|
||||
{
|
||||
@@ -48,7 +48,7 @@ fi
|
||||
load_cfg DISK_IMAGE
|
||||
if [ "$DISK_IMAGE" = "y" ]; then
|
||||
ixmsg "Creating Disk Image"
|
||||
diskimg="${NAME}-disk$(ver).qcow2"
|
||||
diskimg="${NAME}-disk$(ver).img"
|
||||
bootcfg=
|
||||
if [ "$DISK_IMAGE_BOOT_DATA" ]; then
|
||||
bootcfg="-b $DISK_IMAGE_BOOT_DATA -B $DISK_IMAGE_BOOT_OFFSET"
|
||||
|
||||
@@ -120,7 +120,7 @@ rootfs_args()
|
||||
echo -n "-device sd-card,drive=mmc "
|
||||
echo -n "-drive id=mmc,file=$CONFIG_QEMU_ROOTFS,if=none,format=raw "
|
||||
elif [ "$CONFIG_QEMU_ROOTFS_VSCSI" = "y" ]; then
|
||||
echo -n "-drive file=qemu.qcow2,if=virtio,format=qcow2,bus=0,unit=0 "
|
||||
echo -n "-drive file=$CONFIG_QEMU_ROOTFS.qcow2,if=virtio,format=qcow2,bus=0,unit=0 "
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -316,13 +316,13 @@ gdb_args()
|
||||
run_qemu()
|
||||
{
|
||||
if [ "$CONFIG_QEMU_ROOTFS_VSCSI" = "y" ]; then
|
||||
if ! qemu-img check "qemu.qcow2"; then
|
||||
rm -f "qemu.qcow2"
|
||||
if ! qemu-img check "${CONFIG_QEMU_ROOTFS}.qcow2"; then
|
||||
rm -f "${CONFIG_QEMU_ROOTFS}.qcow2"
|
||||
fi
|
||||
if [ ! -f "qemu.qcow2" ]; then
|
||||
if [ ! -f "${CONFIG_QEMU_ROOTFS}.qcow2" ]; then
|
||||
echo "Creating qcow2 disk image for Qemu ..."
|
||||
qemu-img create -f qcow2 -o backing_file="$CONFIG_QEMU_ROOTFS" \
|
||||
-F qcow2 "qemu.qcow2" > /dev/null
|
||||
-F raw "${CONFIG_QEMU_ROOTFS}.qcow2" > /dev/null
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
Regular → Executable
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "usage: $0 <quirk-name> <ifname>"
|
||||
exit 1
|
||||
fi
|
||||
quirk=$1
|
||||
ifname=$2
|
||||
if [ -f "/etc/product/interface-quirks.json" ]; then
|
||||
echo "$(jq -r --arg iface "$ifname" --arg quirk "$quirk" '.[$iface][$quirk] // "false"' /etc/product/interface-quirks.json)"
|
||||
else
|
||||
echo "false"
|
||||
fi
|
||||
@@ -43,7 +43,6 @@ while [ "$1" ]; do
|
||||
txqs="$2"
|
||||
shift 2
|
||||
|
||||
[ $(/usr/libexec/infix/has-quirk "broken-mqprio" "$iface") = "true" ] && echo "Skipping $iface, does not support mqprio" && continue
|
||||
[ $txqs -lt 2 ] && continue
|
||||
[ $txqs -gt 8 ] && txqs=8
|
||||
|
||||
|
||||
+1
-1
Submodule buildroot updated: 74fc86b099...06a983c964
@@ -27,14 +27,13 @@ BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.21"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_DBUS_CXX=y
|
||||
@@ -140,7 +139,6 @@ BR2_PACKAGE_CURIOS_HTTPD=y
|
||||
BR2_PACKAGE_CURIOS_NFTABLES=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
BR2_PACKAGE_STATD=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_FACTORY=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
|
||||
@@ -166,8 +164,6 @@ BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
DISK_IMAGE_BOOT_BIN=y
|
||||
|
||||
@@ -27,14 +27,13 @@ BR2_ROOTFS_POST_BUILD_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-build
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.21"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_DBUS_CXX=y
|
||||
@@ -120,7 +119,6 @@ BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_CONFD_TEST_MODE=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
BR2_PACKAGE_STATD=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_FACTORY=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
|
||||
@@ -139,8 +137,6 @@ BR2_PACKAGE_MDNS_ALIAS=y
|
||||
BR2_PACKAGE_LIBINPUT=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
DISK_IMAGE_BOOT_BIN=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
|
||||
@@ -8,6 +8,7 @@ BR2_CCACHE=y
|
||||
BR2_CCACHE_DIR="${BR2_EXTERNAL_INFIX_PATH}/.ccache"
|
||||
BR2_ENABLE_DEBUG=y
|
||||
BR2_GLOBAL_PATCH_DIR="${BR2_EXTERNAL_INFIX_PATH}/patches"
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_TARGET_GENERIC_HOSTNAME="infix"
|
||||
BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit"
|
||||
BR2_INIT_FINIT=y
|
||||
@@ -29,7 +30,7 @@ BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image
|
||||
BR2_ROOTFS_POST_SCRIPT_ARGS="-c $(BR2_EXTERNAL_INFIX_PATH)/board/aarch64/r2s/genimage.cfg"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.10.3"
|
||||
BR2_LINUX_KERNEL_PATCH="$(BR2_EXTERNAL_INFIX_PATH)/board/aarch64/r2s/rk3328-nanopi-r2s-dts.patch"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/aarch64/r2s/linux_defconfig"
|
||||
@@ -41,7 +42,6 @@ BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_INFIX_PATH)/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_LINUX_FIRMWARE=y
|
||||
@@ -74,6 +74,7 @@ BR2_PACKAGE_PYTHON_GUNICORN=y
|
||||
BR2_PACKAGE_LIBSSH_OPENSSL=y
|
||||
BR2_PACKAGE_LIBSSH2=y
|
||||
BR2_PACKAGE_LIBSSH2_OPENSSL=y
|
||||
BR2_PACKAGE_LIBXCRYPT=y
|
||||
BR2_PACKAGE_LIBOPENSSL_BIN=y
|
||||
BR2_PACKAGE_LIBINPUT=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
@@ -177,7 +178,6 @@ INFIX_SUPPORT="mailto:kernelkit@googlegroups.com"
|
||||
BR2_PACKAGE_CONFD=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
BR2_PACKAGE_STATD=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_FACTORY=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
|
||||
@@ -201,10 +201,8 @@ BR2_PACKAGE_PODMAN_DRIVER_DEVICEMAPPER=y
|
||||
BR2_PACKAGE_PODMAN_DRIVER_VFS=y
|
||||
BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
# GNS3_APPLIANCE is not set
|
||||
|
||||
@@ -39,7 +39,6 @@ BR2_LINUX_KERNEL_NEEDS_HOST_OPENSSL=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="$(BR2_EXTERNAL_INFIX_PATH)/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_LINUX_FIRMWARE=y
|
||||
@@ -170,7 +169,6 @@ BR2_PACKAGE_CONFD=y
|
||||
# BR2_PACKAGE_CONFD_TEST_MODE is not set
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
BR2_PACKAGE_STATD=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_FACTORY=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
|
||||
@@ -196,8 +194,6 @@ BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
# GNS3_APPLIANCE is not set
|
||||
|
||||
@@ -26,7 +26,7 @@ BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INF
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.21"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -34,7 +34,6 @@ BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_DBUS_CXX=y
|
||||
@@ -142,7 +141,6 @@ BR2_PACKAGE_CURIOS_HTTPD=y
|
||||
BR2_PACKAGE_CURIOS_NFTABLES=y
|
||||
BR2_PACKAGE_GENCERT=y
|
||||
BR2_PACKAGE_STATD=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_FACTORY=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOTPLUG=y
|
||||
BR2_PACKAGE_FINIT_PLUGIN_HOOK_SCRIPTS=y
|
||||
@@ -168,8 +166,6 @@ BR2_PACKAGE_TETRIS=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
BR2_PACKAGE_HOST_PYTHON_YANGDOC=y
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
TRUSTED_KEYS=y
|
||||
TRUSTED_KEYS_DEVELOPMENT=y
|
||||
GNS3_APPLIANCE_RAM=512
|
||||
|
||||
@@ -7,14 +7,13 @@ BR2_CCACHE=y
|
||||
BR2_CCACHE_DIR="${BR2_EXTERNAL_INFIX_PATH}/.ccache"
|
||||
BR2_ENABLE_DEBUG=y
|
||||
BR2_GLOBAL_PATCH_DIR="${BR2_EXTERNAL_INFIX_PATH}/patches"
|
||||
BR2_DOWNLOAD_FORCE_CHECK_HASHES=y
|
||||
BR2_TARGET_GENERIC_HOSTNAME="ix"
|
||||
BR2_TARGET_GENERIC_ISSUE="Infix by KernelKit"
|
||||
BR2_INIT_FINIT=y
|
||||
BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y
|
||||
BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs"
|
||||
BR2_ROOTFS_MERGED_USR=y
|
||||
# BR2_TARGET_ENABLE_ROOT_LOGIN is not set
|
||||
BR2_ROOTFS_MERGED_USR=y
|
||||
BR2_SYSTEM_BIN_SH_BASH=y
|
||||
BR2_TARGET_GENERIC_GETTY_PORT="@console"
|
||||
BR2_TARGET_GENERIC_GETTY_TERM="xterm"
|
||||
@@ -27,7 +26,7 @@ BR2_ROOTFS_POST_BUILD_SCRIPT="board/qemu/x86_64/post-build.sh ${BR2_EXTERNAL_INF
|
||||
BR2_ROOTFS_POST_IMAGE_SCRIPT="${BR2_EXTERNAL_INFIX_PATH}/board/common/post-image.sh"
|
||||
BR2_LINUX_KERNEL=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.26"
|
||||
BR2_LINUX_KERNEL_CUSTOM_VERSION_VALUE="6.12.21"
|
||||
BR2_LINUX_KERNEL_USE_CUSTOM_CONFIG=y
|
||||
BR2_LINUX_KERNEL_CUSTOM_CONFIG_FILE="${BR2_EXTERNAL_INFIX_PATH}/board/x86_64/linux_defconfig"
|
||||
BR2_LINUX_KERNEL_INSTALL_TARGET=y
|
||||
@@ -35,7 +34,6 @@ BR2_LINUX_KERNEL_NEEDS_HOST_LIBELF=y
|
||||
BR2_PACKAGE_BUSYBOX_CONFIG="${BR2_EXTERNAL_INFIX_PATH}/board/common/busybox_defconfig"
|
||||
BR2_PACKAGE_STRACE=y
|
||||
BR2_PACKAGE_STRESS_NG=y
|
||||
BR2_PACKAGE_SYSREPO_GROUP="sys-cli"
|
||||
BR2_PACKAGE_JQ=y
|
||||
BR2_PACKAGE_E2FSPROGS=y
|
||||
BR2_PACKAGE_DBUS_CXX=y
|
||||
@@ -52,7 +50,10 @@ BR2_PACKAGE_UBOOT_TOOLS_FIT_SIGNATURE_SUPPORT=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_FIT_CHECK_SIGN=y
|
||||
BR2_PACKAGE_UBOOT_TOOLS_MKENVIMAGE=y
|
||||
BR2_PACKAGE_PYTHON3=y
|
||||
BR2_PACKAGE_LIBSSH_OPENSSL=y
|
||||
BR2_PACKAGE_LIBSSH2=y
|
||||
BR2_PACKAGE_LIBSSH2_OPENSSL=y
|
||||
BR2_PACKAGE_LIBXCRYPT=y
|
||||
BR2_PACKAGE_LIBOPENSSL_BIN=y
|
||||
BR2_PACKAGE_LIBCURL_CURL=y
|
||||
BR2_PACKAGE_LIBMNL=y
|
||||
@@ -82,7 +83,6 @@ BR2_PACKAGE_TCPDUMP=y
|
||||
BR2_PACKAGE_WHOIS=y
|
||||
BR2_PACKAGE_BASH_COMPLETION=y
|
||||
BR2_PACKAGE_SUDO=y
|
||||
BR2_PACKAGE_GETENT=y
|
||||
BR2_PACKAGE_KMOD_TOOLS=y
|
||||
BR2_PACKAGE_PWGEN=y
|
||||
BR2_PACKAGE_RAUC=y
|
||||
@@ -138,7 +138,6 @@ BR2_PACKAGE_KLISH_PLUGIN_INFIX=y
|
||||
BR2_PACKAGE_LOWDOWN=y
|
||||
BR2_PACKAGE_MCD=y
|
||||
BR2_PACKAGE_MDNS_ALIAS=y
|
||||
BR2_PACKAGE_SHOW=y
|
||||
BR2_PACKAGE_ROUSETTE=y
|
||||
BR2_PACKAGE_RAUC_INSTALLATION_STATUS=y
|
||||
TRUSTED_KEYS=y
|
||||
|
||||
+4
-27
@@ -3,35 +3,13 @@ Change Log
|
||||
|
||||
All notable changes to the project are documented in this file.
|
||||
|
||||
[v25.05.0][UNRELEASED]
|
||||
-------------------------
|
||||
### Changes
|
||||
- Upgrade Linux kernel to 6.12.26 (LTS)
|
||||
- Upgrade libyang to 3.12.2
|
||||
- Upgrade sysrepo to 3.6.11
|
||||
- Upgrade netopeer2 (NETCONF) to 2.4.1
|
||||
- Add documentation on Infix upgrading and downgrading, issue #1009
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
[v25.04.0][] - 2025-04-30
|
||||
[v25.03.1][] - 2025-04-11
|
||||
-------------------------
|
||||
|
||||
### Changes
|
||||
- Upgrade Linux kernel to 6.12.25 (LTS)
|
||||
- Upgrade Buildroot to 2025.02.1 (LTS)
|
||||
- Format for disk image (for QEMU) has changed to `qcow2`
|
||||
|
||||
### Fixes
|
||||
- Fix #1002: Broken symlink in release package
|
||||
- Fix #1006: NanoPi R2S corrupt startup, regression in Infix v25.02.0
|
||||
- Bump R2S kernel, now same as tier one boards
|
||||
- Fix #1015: Not possible to save custom SSH settings in startup-config
|
||||
- Fix group owner and permissions of `/cfg/backup` directory
|
||||
- Fix extraction of old version for `/cfg/backup/` files
|
||||
- Fix configuration migration issues when upgrading
|
||||
|
||||
|
||||
[v25.03.0][] - 2025-03-31
|
||||
-------------------------
|
||||
|
||||
@@ -1557,9 +1535,8 @@ Supported YANG models in addition to those used by sysrepo and netopeer:
|
||||
- N/A
|
||||
|
||||
[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
|
||||
[UNRELEASED]: https://github.com/kernelkit/infix/compare/v25.03.0...HEAD
|
||||
[v25.03.1]: https://github.com/kernelkit/infix/compare/v25.03.0...v25.03.1
|
||||
[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
|
||||
[v25.01.0]: https://github.com/kernelkit/infix/compare/v24.11.0...v25.01.0
|
||||
|
||||
+2
-3
@@ -349,6 +349,5 @@ can funtion reasonably well without a persistent `/var`, loosing
|
||||
If `var` is not available, Infix will still persist `/var/lib` using
|
||||
`cfg` as the backing storage.
|
||||
|
||||
[^1]: See [Upgrading procedures and boot
|
||||
order](system.md#upgrade-procedures-and-boot-order) for
|
||||
information on upgrading via CLI.
|
||||
[^1]: See [CLI Upgrade](cli/upgrade.md) for information on upgrading
|
||||
via CLI.
|
||||
|
||||
@@ -39,12 +39,7 @@ The secondary partition (`rootfs.1`) has now been upgraded and will be used as
|
||||
the *active* partition on the next boot. Leaving the primary partition, with
|
||||
the version we are currently running, intact in case of trouble.
|
||||
|
||||
See [upgrading procedures and boot order][2] for more information on
|
||||
upgrading.
|
||||
|
||||
[^1]: It is not possible to upgrade the partition we booted from. Thankfully
|
||||
the underlying "rauc" subsystem keeps track of this. Hence, to upgrade
|
||||
both partitions you must reboot to the new version (to verify it works)
|
||||
and then repeat the same command.
|
||||
|
||||
[2]: ../system.md#upgrade-procedures-and-boot-order
|
||||
|
||||
+5
-10
@@ -131,8 +131,6 @@ This rebuilds (and installs) `foo` and `bar`, the `all` target calls
|
||||
on Buildroot to finalize the target filesystem and generate the images.
|
||||
The final `run` argument is explained below.
|
||||
|
||||
|
||||
|
||||
### `confd`
|
||||
|
||||
The Infix `src/confd/` is the engine of the system. Currently it is a
|
||||
@@ -219,24 +217,21 @@ structure of an existing one. However, before making any changes, **always discu
|
||||
them with the Infix core team**. This helps avoid issues later in development and
|
||||
makes pull request reviews smoother.
|
||||
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
Manual testing can be done using Qemu by calling <kbd>make run</kbd>,
|
||||
see also [Infix in Virtual Environments](virtual.md), or on a physical
|
||||
device by upgrading to the latest build or "[netbooting](netboot.md)"
|
||||
and running the image from RAM. The latter is how most board porting
|
||||
work is done -- **much quicker** change-load-test cycles.
|
||||
see also [Infix in Virtual Environments](virtual.md).
|
||||
|
||||
The Infix automated test suite is built around Qemu and [Qeneth][2], see:
|
||||
|
||||
* [Testing](testing.md)
|
||||
* [Docker Image](../test/docker/README.md)
|
||||
|
||||
With any new feature added to Infix, it is essential to include relevant
|
||||
test case(s). See the [Test Development](testing.md#test-development)
|
||||
section for guidance on adding test cases.
|
||||
With any new feature added to Infix, it is essential to include a
|
||||
relevant test case. See the
|
||||
[Test Development](testing.md#test-development) section for guidance
|
||||
on adding test cases.
|
||||
|
||||
|
||||
Reviewing
|
||||
|
||||
@@ -193,11 +193,3 @@ admin@example:/config/web/> edit restconf
|
||||
admin@example:/config/web/restconf/> no enabled
|
||||
admin@example:/config/web/restconf/>
|
||||
```
|
||||
|
||||
# System Upgrade
|
||||
|
||||
See [upgrading procedures and boot order][1] for information on
|
||||
upgrading.
|
||||
|
||||
|
||||
[1]: system.md#upgrade-procedures-and-boot-order
|
||||
|
||||
-130
@@ -1,130 +0,0 @@
|
||||
Netboot HowTo
|
||||
=============
|
||||
|
||||
This document describes how to set up network booting U-Boot devices on
|
||||
a LAN, e.g., when working with an evaluation board or other embedded
|
||||
system. The most secure way to do this is with a local LAN between a PC
|
||||
and the device.
|
||||
|
||||
Instead of setting up everything in U-Boot to download the Linux Image,
|
||||
device tree, and initramfs, we will let U-Boot download a script with
|
||||
instructions to run. When you have multiple systems (boards) this
|
||||
quickly becomes a lot easier to manage.
|
||||
|
||||
> [!NOTE]
|
||||
> Instructions in this HowTo assume a Debian based development system,
|
||||
> e.g., Ubuntu or Linux Mint.
|
||||
|
||||
|
||||
## Network Interface Setup
|
||||
|
||||
For two dedicated network interfaces, here `eth2` and `eth3` (ymmv), we
|
||||
create an old-style interfaces config[^1] with the following content:
|
||||
|
||||
```
|
||||
# /etc/network/interfaces.d/gimli
|
||||
auto eth2
|
||||
iface eth2 inet static
|
||||
address 192.168.0.1
|
||||
netmask 255.255.255.0
|
||||
|
||||
auto eth3
|
||||
iface eth3 inet manual
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
> Use configuration file names in `.d/` directories that make sense and
|
||||
> can easily be remembered. Here we use the hostname of the PC.
|
||||
|
||||
|
||||
## DHCP/TFTP Server Setup
|
||||
|
||||
The examples given here use `dnsmasq`, which provides both DHCP and TFTP
|
||||
server support. The same can be achieved with other implementations.
|
||||
|
||||
Similar to `interfaces.d`, dnsmasq has an `/etc/dnsmasq.d` directory so
|
||||
we can use "snippets" instead of modifying `/etc/dnsmasq.conf` directly.
|
||||
Add a file called `/etc/dnsmasq.d/gimli`.
|
||||
|
||||
Initial content:
|
||||
|
||||
```
|
||||
# Remember IP address handed out to BOOTP clients
|
||||
bootp-dynamic
|
||||
# Disable check-if-ip-address-is-already-used
|
||||
no-ping
|
||||
|
||||
# Enable TFTP server, use /srv/ftp, same as any FTP server, useful
|
||||
# when using the same images for system upgrade as for netbooting.
|
||||
enable-tftp
|
||||
tftp-root=/srv/ftp
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> First of all, make sure you DO NOT accidentally set up dnsmasq so that
|
||||
> it starts acting as a DHCP server also on your office LAN! Follow the
|
||||
> instructions below for more details.
|
||||
|
||||
If you have many interfaces used for lab equipment and only one office
|
||||
LAN interface, then use something like this:
|
||||
|
||||
```
|
||||
# Disable DHCP server on loopback and office LAN (eth0)
|
||||
except-interface=lo
|
||||
except-interface=eth0
|
||||
```
|
||||
|
||||
To further lock this down, we only run the DHCP server on each of the
|
||||
interfaces used for lab equipment, with a dedicated IP range as well:
|
||||
|
||||
```
|
||||
# Currently I have an imx8mp-evk on eth2, so on my system I have a
|
||||
# symlink bootfile-eth2 -> netboot.scr
|
||||
interface=eth2
|
||||
dhcp-range=192.168.0.100,192.168.0.199,1h
|
||||
dhcp-boot=tag:eth2,bootfile-eth2
|
||||
```
|
||||
|
||||
## Bootfile netboot.scr
|
||||
|
||||
The bootfile U-Boot retrieves from the TFTP server is a script that
|
||||
looks like this, `netboot.sh`:
|
||||
|
||||
```sh
|
||||
setenv ramdisk_addr_r 0x58000000
|
||||
setenv fdt_addr_r 0x50400000
|
||||
|
||||
setenv autoboot off
|
||||
tftp ${fdt_addr_r} imx8mp-evk/imx8mp-evk.dtb
|
||||
tftp ${kernel_addr_r} imx8mp-evk/Image
|
||||
tftp ${ramdisk_addr_r} imx8mp-evk/rootfs.squashfs
|
||||
|
||||
setenv bootargs console=ttymxc1,115200 root=/dev/ram0 brd.rd_size=500000 rauc.slot=net
|
||||
booti ${kernel_addr_r} ${ramdisk_addr_r}:${filesize} ${fdt_addr_r}
|
||||
```
|
||||
|
||||
U-Boot cannot read script files directly, so we need to wrap it with a
|
||||
FIT format header, this is done by first converting it on the PC:
|
||||
|
||||
```
|
||||
$ mkimage -T script -d netboot.sh netboot.scr
|
||||
```
|
||||
|
||||
The output is `netboot.scr` which we symlink to above in the dnsmasq
|
||||
setup step.
|
||||
|
||||
|
||||
## U-Boot Commands
|
||||
|
||||
U-Boot is a maze of environment variables, some with values, some wrap
|
||||
commands, and most are undocumented. We will use a prefix for our
|
||||
variables to ensure we do not overwrite anything you may want to use
|
||||
later.
|
||||
|
||||
```sh
|
||||
==> setenv ixboot 'dhcp && source \${fileaddr}'
|
||||
==> saveenv
|
||||
```
|
||||
|
||||
|
||||
[^1]: To prevent NetworkManager from automatically managing the interfaces.
|
||||
-361
@@ -323,368 +323,7 @@ reference ID, stratum, time offsets, frequency, and root delay.
|
||||
> The system uses `chronyd` Network Time Protocol (NTP) daemon. The
|
||||
> output shown here is best explained in the [Chrony documentation][4].
|
||||
|
||||
## Upgrade procedures and boot order
|
||||
|
||||
For resilience purposes, Infix maintains two software
|
||||
images referred to as the _primary_ and _secondary_ partition image.
|
||||
In addition, some bootloaders support [netbooting][6].
|
||||
|
||||
The _boot order_ defines which image is tried first, and is listed
|
||||
with the CLI `show software` command. It also shows Infix version
|
||||
installed per partition, and which image was used when booting (`STATE
|
||||
booted`).
|
||||
|
||||
```
|
||||
admin@example:/> show software
|
||||
BOOT ORDER
|
||||
primary secondary net
|
||||
|
||||
NAME STATE VERSION DATE
|
||||
primary booted v25.01.0 2025-04-25T10:15:00+00:00
|
||||
secondary inactive v25.01.0 2025-04-25T10:07:20+00:00
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
YANG support for upgrading Infix, inspecting and _modifying_ the
|
||||
boot-order, is defined in [infix-system-software][5].
|
||||
|
||||
|
||||
### Upgrading Infix
|
||||
|
||||
Upgrading Infix is done one partition at a time. If the system has
|
||||
booted from one partition, an `upgrade` will apply to the other
|
||||
(inactive) partition.
|
||||
|
||||
1. Download and unpack the release to install. Make the image *pkg*
|
||||
bundle available at some URL[^10]
|
||||
2. Assume the unit has booted the `primary` image. Then running the
|
||||
`upgrade` command installs a new image on the `secondary`
|
||||
partition
|
||||
3. As part of a successful upgrade, the boot-order is implictly
|
||||
changed to boot the newly installed image
|
||||
4. Reboot the unit
|
||||
5. The unit now runs the new image. To upgrade the remaining partition
|
||||
(`primary`), run the same upgrade command again, and (optionally)
|
||||
reboot to verify the upgrade
|
||||
|
||||
> [!CAUTION]
|
||||
> During boot, the unit may [migrate](#configuration-migration) the
|
||||
> startup configuration for any syntax changes. It is therefore
|
||||
> important that you make sure to upgrade the other partition as well
|
||||
> after reboot, of course after having verified your setup.
|
||||
|
||||
The CLI example below shows steps 2-4.
|
||||
|
||||
Upgrade: here the image *pkg bundle* was made available via TFTP.
|
||||
|
||||
```
|
||||
admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.03.1.pkg
|
||||
installing
|
||||
0% Installing
|
||||
0% Determining slot states
|
||||
10% Determining slot states done.
|
||||
...
|
||||
98% Copying image to rootfs.1
|
||||
99% Copying image to rootfs.1
|
||||
99% Copying image to rootfs.1 done.
|
||||
99% Updating slots done.
|
||||
100% Installing done.
|
||||
Installing `tftp://198.18.117.1/infix-aarch64-25.03.1.pkg` succeeded
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
Reboot: The unit will boot on the other partition, with the newly
|
||||
installed image. The `Loading startup-config` step conducts migration
|
||||
of startup configuration if applicable.
|
||||
|
||||
```
|
||||
admin@example:/> reboot
|
||||
[ OK ] Stopping Static routing daemon
|
||||
[ OK ] Stopping Zebra routing daemon
|
||||
...
|
||||
[ OK ] Loading startup-config
|
||||
[ OK ] Verifying self-signed https certificate
|
||||
[ OK ] Update DNS configuration
|
||||
[ OK ] Starting Status daemon
|
||||
|
||||
Infix -- a Network Operating System v25.03.1 (ttyS0)
|
||||
example login: admin
|
||||
Password:
|
||||
.-------.
|
||||
| . . | Infix -- a Network Operating System
|
||||
|-. v .-| https://kernelkit.org
|
||||
'-'---'-'
|
||||
|
||||
Run the command 'cli' for interactive OAM
|
||||
|
||||
admin@example:~$ cli
|
||||
|
||||
See the 'help' command for an introduction to the system
|
||||
|
||||
admin@example:/> show software
|
||||
BOOT ORDER
|
||||
secondary primary net
|
||||
|
||||
NAME STATE VERSION DATE
|
||||
primary inactive v25.01.0 2025-04-25T10:15:00+00:00
|
||||
secondary booted v25.03.1 2025-04-25T10:24:31+00:00
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
As shown, the *boot order* has been updated, so that *secondary* is
|
||||
now the preferred boot source.
|
||||
|
||||
To upgrade the remaining partition (`primary`), run the `upgrade URL`
|
||||
command again, and reboot.
|
||||
|
||||
### Configuration Migration
|
||||
|
||||
The example above illustrated an upgrade from Infix v25.01.0 to
|
||||
v25.03.1. Inbetween these versions, YANG configuration definitions
|
||||
changed slightly (more details given below).
|
||||
|
||||
During boot, Infix inspects the `version` meta information within the
|
||||
startup configuration file to determine if configuration migration is
|
||||
needed. In this specific case, the configuration file has version
|
||||
`1.4` while the booted software expects version `1.5` (the
|
||||
configuration version numbering differs from the Infix image version
|
||||
numbering). The startup configuration is migrated to `1.5`
|
||||
definitions and stored, while a backup previous startup configuration
|
||||
is stored in directory `/cfg/backup/`.
|
||||
|
||||
```
|
||||
admin@example:/> dir /cfg/backup/
|
||||
/cfg/backup/ directory
|
||||
startup-config-1.4.cfg
|
||||
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
The modifications made to the startup configuration can be viewed by
|
||||
comparing the files from the *shell*. An example is shown below.
|
||||
|
||||
```
|
||||
admin@example:/> exit
|
||||
admin@example:~$ diff /cfg/backup/startup-config-1.4.cfg /cfg/startup-config.cfg
|
||||
--- /cfg/backup/startup-config-1.4.cfg
|
||||
+++ /cfg/startup-config.cfg
|
||||
...
|
||||
- "public-key-format": "ietf-crypto-types:ssh-public-key-format",
|
||||
+ "public-key-format": "infix-crypto-types:ssh-public-key-format",
|
||||
...
|
||||
- "private-key-format": "ietf-crypto-types:rsa-private-key-format",
|
||||
+ "private-key-format": "infix-crypto-types:rsa-private-key-format",
|
||||
...
|
||||
- "version": "1.4"
|
||||
+ "version": "1.5"
|
||||
...
|
||||
admin@example:~$
|
||||
```
|
||||
|
||||
### Downgrading Infix
|
||||
|
||||
Downgrading to an earlier Infix version is possible, however,
|
||||
downgrading is **not** guaranteed to work smoothly. In particular,
|
||||
when the unit boots up with the downgraded version, it may fail to
|
||||
apply the *startup config*, and instead apply its [failure config][7].
|
||||
|
||||
We consider two cases: downgrading with or without applying a backup
|
||||
startup configuration before rebooting.
|
||||
|
||||
In both cases we start out with a unit running Infix v25.03.1, and
|
||||
wish to downgrade to v25.01.0.
|
||||
|
||||
```
|
||||
admin@example:/> show software
|
||||
BOOT ORDER
|
||||
primary secondary net
|
||||
|
||||
NAME STATE VERSION DATE
|
||||
primary booted v25.03.1 2025-04-25T11:36:26+00:00
|
||||
secondary inactive v25.03.1 2025-04-25T10:24:31+00:00
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
#### Downgrading when applying a backup startup configuration
|
||||
|
||||
This is the recommended approach to downgrade, given that you have a
|
||||
backup configuration available. The objective is to avoid ending up
|
||||
with the unit in *failure config*.
|
||||
|
||||
1. Find the backup configuration file.
|
||||
2. Run `upgrade URL` to install Infix image to downgrade to.
|
||||
3. Copy backup startup configuration to current startup configuration
|
||||
(from shell).
|
||||
4. Reboot.
|
||||
|
||||
*Find the backup configuration file:*
|
||||
|
||||
Assume you have a backup startup config for the version to downgrade
|
||||
to (here Infix v25.01.0, config `version 1.4`).
|
||||
|
||||
```
|
||||
admin@example:/> dir /cfg/backup/
|
||||
/cfg/backup/ directory
|
||||
startup-config-1.4.cfg
|
||||
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
*Use `upgrade` command to downgrade:*
|
||||
|
||||
```
|
||||
admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.01.0.pkg
|
||||
installing
|
||||
0% Installing
|
||||
0% Determining slot states
|
||||
10% Determining slot states done.
|
||||
...
|
||||
99% Copying image to rootfs.1 done.
|
||||
99% Updating slots done.
|
||||
100% Installing done.
|
||||
Installing `tftp://198.18.117.1/infix-aarch64-25.01.0.pkg` succeeded
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
*Apply the backup configuration file:*
|
||||
|
||||
```
|
||||
admin@example:/> copy /cfg/backup/startup-config-1.4.cfg /cfg/startup-config.cfg
|
||||
Overwrite existing file /cfg/startup-config.cfg (y/N)? y
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
*Reboot:*
|
||||
|
||||
The unit will come up with the applied backup configuration instead of
|
||||
failure config.
|
||||
|
||||
```
|
||||
admin@example:/> reboot
|
||||
[ OK ] Saving system clock to file
|
||||
[ OK ] Stopping Software update service
|
||||
[ OK ] Stopping Status daemon
|
||||
...
|
||||
[ OK ] Bootstrapping YANG datastore
|
||||
[ OK ] Starting Configuration daemon
|
||||
[ OK ] Loading startup-config
|
||||
[ OK ] Update DNS configuration
|
||||
[ OK ] Verifying self-signed https certificate
|
||||
[ OK ] Starting Status daemon
|
||||
|
||||
Infix -- a Network Operating System v25.01.0 (ttyS0)
|
||||
example login:
|
||||
```
|
||||
|
||||
#### Downgrading without applying a backup startup configuration
|
||||
|
||||
This procedure assumes you have access to the unit's console port and
|
||||
its default login credentials[^9].
|
||||
|
||||
1. Downgrade
|
||||
2. Reboot
|
||||
3. Login with unit's default credentials
|
||||
4. Conduct factory reset
|
||||
5. (Then go on configure the unit as you wish)
|
||||
|
||||
*Use `upgrade` command to downgrade:*
|
||||
|
||||
```
|
||||
admin@example:/> upgrade tftp://198.18.117.1/infix-aarch64-25.01.0.pkg
|
||||
installing
|
||||
0% Installing
|
||||
0% Determining slot states
|
||||
10% Determining slot states done.
|
||||
...
|
||||
99% Copying image to rootfs.1 done.
|
||||
99% Updating slots done.
|
||||
100% Installing done.
|
||||
Installing `tftp://198.18.117.1/infix-aarch64-25.01.0.pkg` succeeded
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
*Reboot:*
|
||||
|
||||
Conduct a reboot. During boot, the unit fails to apply the existing
|
||||
startup configuration (config version `1.5` while software expects
|
||||
version `1.4` or earlier), and instead applies its [failure
|
||||
config][7]. This is what is seen on the console when this situation
|
||||
occurs. Note that the login prompt displays `failed` as part of the
|
||||
*hostname*.
|
||||
|
||||
```
|
||||
admin@example:/> reboot
|
||||
[ OK ] Saving system clock to file
|
||||
[ OK ] Stopping Software update service
|
||||
[ OK ] Stopping Status daemon
|
||||
...
|
||||
[ OK ] Verifying SSH host keys
|
||||
[ OK ] Bootstrapping YANG datastore
|
||||
[ OK ] Starting Configuration daemon
|
||||
[FAIL] Loading startup-config
|
||||
[ OK ] Loading failure-config
|
||||
[ OK ] Verifying self-signed https certificate
|
||||
[ OK ] Starting Status daemon
|
||||
|
||||
Infix -- a Network Operating System v25.01.0 (ttyS0)
|
||||
|
||||
ERROR: Corrupt startup-config, system has reverted to default login credentials
|
||||
failed-00-00-00 login:
|
||||
```
|
||||
|
||||
To remedy a situation like this, you can login with the unit's *default
|
||||
login credentials*, preferrably via a [console port][8].
|
||||
The unit's default credentials are typically printed on a sticker on
|
||||
the unit.
|
||||
|
||||
```
|
||||
failed-00-00-00 login: admin
|
||||
Password:
|
||||
|
||||
Run the command 'cli' for interactive OAM
|
||||
|
||||
admin@failed-00-00-00:~$
|
||||
```
|
||||
|
||||
When it is *safe* from a network operations perspective, you can
|
||||
conduct a factory reset and reboot. It is recommended to remove the
|
||||
unit from any production network before doing this, as a factory reset
|
||||
may enable undesired connectivity between the unit's ports.
|
||||
|
||||
```
|
||||
admin@failed-00-00-00:~$ factory
|
||||
Factory reset device (y/N)? y
|
||||
factory: scheduled factory reset on next boot.
|
||||
Reboot now to perform reset, (y/N)? y
|
||||
[ OK ] Saving system time (UTC) to RTC
|
||||
[ OK ] Stopping mDNS alias advertiser
|
||||
...
|
||||
[ OK ] Starting Configuration daemon
|
||||
[ OK ] Loading startup-config
|
||||
[ OK ] Update DNS configuration
|
||||
[ OK ] Verifying self-signed https certificate
|
||||
[ OK ] Starting Status daemon
|
||||
[ OK ] Starting Status daemon
|
||||
|
||||
|
||||
Please press Enter to activate this console.
|
||||
|
||||
Infix -- a Network Operating System v25.01.0 (ttyS0)
|
||||
example login:
|
||||
```
|
||||
|
||||
Continued configuration is done as with any unit after factory reset.
|
||||
|
||||
[1]: https://www.rfc-editor.org/rfc/rfc7317
|
||||
[2]: https://github.com/kernelkit/infix/blob/main/src/confd/yang/infix-system%402024-02-29.yang
|
||||
[3]: https://www.rfc-editor.org/rfc/rfc8341
|
||||
[4]: https://chrony-project.org/doc/4.6.1/chronyc.html
|
||||
[5]: https://github.com/kernelkit/infix/blob/main/src/confd/yang/confd/infix-system-software.yang
|
||||
[6]: boot.md#system-configuration
|
||||
[7]: introduction.md#system-boot
|
||||
[8]: management.md#console-port
|
||||
[^9]: In failure config, Infix puts all Ethernet ports as individual
|
||||
interfaces. With direct access, one can connect with e.g., SSH,
|
||||
using link local IPv6 addresses. This as an alternative to
|
||||
connecting via a console port.
|
||||
[^10]: Set up an FTP/TFTP/SFTP or HTTP/HTTPS server on the same LAN.
|
||||
|
||||
@@ -34,7 +34,6 @@ source "$BR2_EXTERNAL_INFIX_PATH/package/python-libyang/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/python-statd/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/python-yangdoc/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/skeleton-init-finit/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/show/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/tetris/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/libyang-cpp/Config.in"
|
||||
source "$BR2_EXTERNAL_INFIX_PATH/package/sysrepo-cpp/Config.in"
|
||||
|
||||
+4
-24
@@ -35,30 +35,13 @@ define CONFD_INSTALL_EXTRA
|
||||
cp $(CONFD_PKGDIR)/avahi.service $(TARGET_DIR)/etc/avahi/services/netconf.service
|
||||
endef
|
||||
|
||||
NETOPEER2_SEARCHPATH=$(TARGET_DIR)/usr/share/yang/modules/netopeer2/
|
||||
SYSREPO_SEARCHPATH=$(TARGET_DIR)/usr/share/yang/modules/sysrepo/
|
||||
LIBNETCONF2_SEARCHPATH=$(TARGET_DIR)/usr/share/yang/modules/libnetconf2/
|
||||
CONFD_SEARCHPATH=$(TARGET_DIR)/usr/share/yang/modules/confd/
|
||||
TEST_MODE_SEARCHPATH=$(TARGET_DIR)/usr/share/yang/modules/test-mode/
|
||||
ROUSETTE_SEARCHPATH=$(TARGET_DIR)/usr/share/yang/modules/rousette/
|
||||
COMMON_SYSREPO_ENV = \
|
||||
SYSREPO_SHM_PREFIX=$(CONFD_SYSREPO_SHM_PREFIX) \
|
||||
SYSREPOCTL_EXECUTABLE="$(HOST_DIR)/bin/sysrepoctl" \
|
||||
SYSREPOCFG_EXECUTABLE="$(HOST_DIR)/bin/sysrepocfg" \
|
||||
SEARCH_PATH="$(NETOPEER2_SEARCHPATH) $(SYSREPO_SEARCHPATH) $(LIBNETCONF2_SEARCHPATH) $(TEST_MODE_SEARCHPATH) $(CONFD_SEARCHPATH) $(ROUSETTE_SEARCHPATH)"
|
||||
|
||||
SEARCH_PATH="$(TARGET_DIR)/usr/share/yang/modules/confd/"
|
||||
|
||||
define CONFD_INSTALL_YANG_MODULES
|
||||
$(COMMON_SYSREPO_ENV) \
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/sysrepo.inc
|
||||
$(COMMON_SYSREPO_ENV) \
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/libnetconf2.inc
|
||||
$(COMMON_SYSREPO_ENV) \
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/netopeer2.inc
|
||||
$(COMMON_SYSREPO_ENV) \
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/rousette.inc
|
||||
$(COMMON_SYSREPO_ENV) \
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/test-mode.inc
|
||||
$(COMMON_SYSREPO_ENV) \
|
||||
$(BR2_EXTERNAL_INFIX_PATH)/utils/srload $(@D)/yang/confd.inc
|
||||
endef
|
||||
@@ -71,17 +54,14 @@ endef
|
||||
endif
|
||||
|
||||
define CONFD_PERMISSIONS
|
||||
/etc/sysrepo/data/ r 660 root sys-cli - - - - -
|
||||
/etc/sysrepo/data d 770 root sys-cli - - - - -
|
||||
/etc/sysrepo/data/ r 660 root wheel - - - - -
|
||||
/etc/sysrepo/data d 770 root wheel - - - - -
|
||||
endef
|
||||
|
||||
define CONFD_EMPTY_SYSREPO
|
||||
rm -rf $(TARGET_DIR)/etc/sysrepo/data/
|
||||
endef
|
||||
define CONFD_CLEANUP
|
||||
rm -f /dev/shm/$(CONFD_SYSREPO_SHM_PREFIX)*
|
||||
endef
|
||||
CONFD_PRE_INSTALL_TARGET_HOOKS += CONFD_EMPTY_SYSREPO
|
||||
|
||||
CONFD_PRE_INSTALL_TARGET_HOOKS += CONFD_CLEANUP
|
||||
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_EXTRA
|
||||
CONFD_POST_INSTALL_TARGET_HOOKS += CONFD_INSTALL_YANG_MODULES
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# From https://github.com/troglobit/finit/releases/
|
||||
sha256 b6a0a2f98c860cf9fe5dfe7e3601d922957ad7880ae29919176ab960b7b96e70 finit-4.12.tar.gz
|
||||
sha256 7e49a3df58c5aedfff9537b7ff34b9238fc28b523d4dbacc316d606c7af5e335 finit-4.11.tar.gz
|
||||
|
||||
# Locally calculated
|
||||
sha256 2fd62c0fe6ea6d1861669f4c87bda83a0b5ceca64f4baa4d16dd078fbd218c14 LICENSE
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#
|
||||
################################################################################
|
||||
|
||||
FINIT_VERSION = 4.12
|
||||
FINIT_VERSION = 4.11
|
||||
FINIT_SITE = https://github.com/troglobit/finit/releases/download/$(FINIT_VERSION)
|
||||
FINIT_LICENSE = MIT
|
||||
FINIT_LICENSE_FILES = LICENSE
|
||||
|
||||
@@ -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 01/18] added support for RpcYang in Context::parseOp
|
||||
Subject: [PATCH 1/8] added support for RpcYang in Context::parseOp
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 02/18] throw when lyd_validate_all returns error
|
||||
Subject: [PATCH 2/8] throw when lyd_validate_all returns error
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 03/18] remove a misleading comment
|
||||
Subject: [PATCH 3/8] remove a misleading comment
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 04/18] schema: improve `List::keys()` not to use `std::move`
|
||||
Subject: [PATCH 4/8] 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: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
`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 05/18] schema: make leaf-list's `default` statement available
|
||||
Subject: [PATCH 5/8] schema: make leaf-list's `default` statement available
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 06/18] schema: Make choice and case statements available
|
||||
Subject: [PATCH 6/8] schema: Make choice and case statements available
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 07/18] Wrap lyd_change_term for changing the value for a
|
||||
Subject: [PATCH 7/8] 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: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Previously, the code would require a newPath(...,
|
||||
libyang::CreationOptions::Update), which is quite a mouthful.
|
||||
|
||||
@@ -1,652 +0,0 @@
|
||||
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 09/18] upstream stopped reporting schema-mounts node
|
||||
Subject: [PATCH 8/8] upstream stopped reporting schema-mounts node
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Change-Id: I940769d38d56fcfda3e1408c92331fdb00c161e9
|
||||
Signed-off-by: Mattias Walström <lazzer@gmail.com>
|
||||
@@ -1,39 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,462 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
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/44] restconf: report the list key values when they differ
|
||||
Subject: [PATCH 01/20] 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: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] uri: rename url-encoding to percent-encoding
|
||||
Subject: [PATCH 02/20] uri: rename url-encoding to percent-encoding
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] uri: correct x3 rule class names
|
||||
Subject: [PATCH 03/20] uri: correct x3 rule class names
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] restconf: parser should work on raw percent-encoded
|
||||
Subject: [PATCH 04/20] 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: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] restconf: list key values checking must respect
|
||||
Subject: [PATCH 05/20] 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: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: test querying lists with union keys
|
||||
Subject: [PATCH 06/20] tests: test querying lists with union keys
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] error handling in sysrepo has changed
|
||||
Subject: [PATCH 07/20] error handling in sysrepo has changed
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] restconf: support fields query parameter
|
||||
Subject: [PATCH 08/20] restconf: support fields query parameter
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] cmake: adhere to CMP0167
|
||||
Subject: [PATCH 09/20] cmake: adhere to CMP0167
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] Fix compatibility with pam_wrapper 1.1.6+
|
||||
Subject: [PATCH 10/20] Fix compatibility with pam_wrapper 1.1.6+
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: add missing pragma once
|
||||
Subject: [PATCH 11/20] tests: add missing pragma once
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: extend clientRequest wrappers interface and use
|
||||
Subject: [PATCH 12/20] tests: extend clientRequest wrappers interface and use
|
||||
it
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: move stuff from the header file into cpp file
|
||||
Subject: [PATCH 13/20] 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: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: rename datastoreUtils to event_watchers
|
||||
Subject: [PATCH 14/20] tests: rename datastoreUtils to event_watchers
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: rename NotificationWatcher
|
||||
Subject: [PATCH 15/20] tests: rename NotificationWatcher
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: make NotificationWatcher reusable
|
||||
Subject: [PATCH 16/20] tests: make NotificationWatcher reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: helper function to construct server URI
|
||||
Subject: [PATCH 17/20] tests: helper function to construct server URI
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: make SSEClient reusable
|
||||
Subject: [PATCH 18/20] tests: make SSEClient reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] tests: fix deadlock in tests
|
||||
Subject: [PATCH 19/20] tests: fix deadlock in tests
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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/44] restconf: make as_restconf_notification reusable
|
||||
Subject: [PATCH 20/20] restconf: make as_restconf_notification reusable
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
The YANG-PUSH notifications are supposed to be wrapped in this too, so
|
||||
this needs to be accessible from multiple places.
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,196 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,356 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
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
@@ -1,31 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
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
|
||||
|
||||
+7
-18
@@ -1,25 +1,17 @@
|
||||
From f7307481fc4ca167592acf925136e5f8a51e2150 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= <jan.kundrat@cesnet.cz>
|
||||
From: Jan Kundrát <jan.kundrat@cesnet.cz>
|
||||
Date: Wed, 18 Dec 2024 17:26:15 +0100
|
||||
Subject: [PATCH 21/44] Update dependencies
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Subject: [PATCH] Update dependencies
|
||||
|
||||
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 @@ find_package(PkgConfig)
|
||||
@@ -74,7 +74,7 @@
|
||||
pkg_check_modules(nghttp2 REQUIRED IMPORTED_TARGET libnghttp2_asio>=0.0.90 libnghttp2)
|
||||
find_package(Boost REQUIRED CONFIG COMPONENTS system thread)
|
||||
|
||||
@@ -32,7 +24,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 @@ TEST_CASE("writing data")
|
||||
@@ -772,7 +772,7 @@
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -41,7 +33,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -855,7 +855,7 @@ TEST_CASE("writing data")
|
||||
@@ -855,7 +855,7 @@
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -50,7 +42,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1560,7 +1560,7 @@ TEST_CASE("writing data")
|
||||
@@ -1560,7 +1560,7 @@
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -59,7 +51,7 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1626,7 +1626,7 @@ TEST_CASE("writing data")
|
||||
@@ -1626,7 +1626,7 @@
|
||||
{
|
||||
"error-type": "protocol",
|
||||
"error-tag": "invalid-value",
|
||||
@@ -68,6 +60,3 @@ index 0932984..d37ab03 100644
|
||||
}
|
||||
]
|
||||
}
|
||||
--
|
||||
2.43.0
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
config BR2_PACKAGE_SHOW
|
||||
bool "show"
|
||||
help
|
||||
Tool to retrieve and present operational data from sysrepo.
|
||||
|
||||
https://github.com/kernelkit/infix
|
||||
@@ -1,19 +0,0 @@
|
||||
################################################################################
|
||||
#
|
||||
# show
|
||||
#
|
||||
################################################################################
|
||||
|
||||
SHOW_VERSION = 1.0
|
||||
SHOW_LICENSE = MIT
|
||||
SHOW_LICENSE_FILES = LICENSE
|
||||
SHOW_SITE_METHOD = local
|
||||
SHOW_SITE = $(BR2_EXTERNAL_INFIX_PATH)/src/show
|
||||
SHOW_REDISTRIBUTE = NO
|
||||
|
||||
define SHOW_INSTALL_TARGET_CMDS
|
||||
$(TARGET_MAKE_ENV) $(TARGET_CONFIGURE_OPTS) $(MAKE) -C $(@D) \
|
||||
DESTDIR="$(TARGET_DIR)" install
|
||||
endef
|
||||
|
||||
$(eval $(generic-package))
|
||||
@@ -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 01/20] error handling changes in upstream sysrepo
|
||||
Subject: [PATCH 1/6] error handling changes in upstream sysrepo
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 02/20] Fix error message when sr_session_start fails
|
||||
Subject: [PATCH 2/6] Fix error message when sr_session_start fails
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 03/20] We don't support sysrepo v3 yet
|
||||
Subject: [PATCH 3/6] We don't support sysrepo v3 yet
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 04/20] refactor: don't use `module` as a variable name
|
||||
Subject: [PATCH 4/6] 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: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
...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 05/20] DRY: a dummy leaf XPath
|
||||
Subject: [PATCH 5/6] DRY: a dummy leaf XPath
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
Organization: Wires
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
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 06/20] build: a single place to define package version
|
||||
Subject: [PATCH 6/6] 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
|
||||
Organization: Addiva Elektronik
|
||||
|
||||
Don't ask me how I found out that I need this :).
|
||||
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
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
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user