From 3a4ee8e6701550819adf97cad5302a03aa71487c Mon Sep 17 00:00:00 2001 From: Stefan Gula Date: Mon, 12 Feb 2024 09:31:05 +0100 Subject: [PATCH 1/3] data: introduce store_only flags This patch introduces store_only flag, which allows user to only store value without performing any sort of additional type checks as length, range, pattern, etc. Signed-off-by: Stefan Gula --- cffi/cdefs.h | 1 + libyang/context.py | 11 ++++++++++- libyang/data.py | 24 +++++++++++++++++++----- tests/test_data.py | 8 ++++++++ tests/yang/yolo/yolo-nodetypes.yang | 4 +++- 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/cffi/cdefs.h b/cffi/cdefs.h index 6079ecf..2cf8c39 100644 --- a/cffi/cdefs.h +++ b/cffi/cdefs.h @@ -264,6 +264,7 @@ struct lyd_node { LY_ERR lys_set_implemented(struct lys_module *, const char **); #define LYD_NEW_VAL_OUTPUT ... +#define LYD_NEW_VAL_STORE_ONLY ... #define LYD_NEW_VAL_BIN ... #define LYD_NEW_VAL_CANON ... #define LYD_NEW_META_CLEAR_DFLT ... diff --git a/libyang/context.py b/libyang/context.py index 1300a09..8ed04ff 100644 --- a/libyang/context.py +++ b/libyang/context.py @@ -244,6 +244,7 @@ def create_data_path( parent: Optional[DNode] = None, value: Any = None, update: bool = True, + store_only: bool = False, rpc_output: bool = False, force_return_value: bool = True, ) -> Optional[DNode]: @@ -254,7 +255,9 @@ def create_data_path( value = str(value).lower() elif not isinstance(value, str): value = str(value) - flags = newval_flags(update=update, rpc_output=rpc_output) + flags = newval_flags( + update=update, store_only=store_only, rpc_output=rpc_output + ) dnode = ffi.new("struct lyd_node **") ret = lib.lyd_new_path( parent.cdata if parent else ffi.NULL, @@ -348,6 +351,7 @@ def parse_data( strict: bool = False, validate_present: bool = False, validate_multi_error: bool = False, + store_only: bool = False, ) -> Optional[DNode]: if self.cdata is None: raise RuntimeError("context already destroyed") @@ -358,6 +362,7 @@ def parse_data( opaq=opaq, ordered=ordered, strict=strict, + store_only=store_only, ) validation_flgs = validation_flags( no_state=no_state, @@ -415,6 +420,7 @@ def parse_data_mem( strict: bool = False, validate_present: bool = False, validate_multi_error: bool = False, + store_only: bool = False, ) -> Optional[DNode]: return self.parse_data( fmt, @@ -429,6 +435,7 @@ def parse_data_mem( strict=strict, validate_present=validate_present, validate_multi_error=validate_multi_error, + store_only=store_only, ) def parse_data_file( @@ -444,6 +451,7 @@ def parse_data_file( strict: bool = False, validate_present: bool = False, validate_multi_error: bool = False, + store_only: bool = False, ) -> Optional[DNode]: return self.parse_data( fmt, @@ -458,6 +466,7 @@ def parse_data_file( strict=strict, validate_present=validate_present, validate_multi_error=validate_multi_error, + store_only=store_only, ) def __iter__(self) -> Iterator[Module]: diff --git a/libyang/data.py b/libyang/data.py index 00490db..288303d 100644 --- a/libyang/data.py +++ b/libyang/data.py @@ -79,6 +79,7 @@ def data_format(fmt_string: str) -> int: # ------------------------------------------------------------------------------------- def newval_flags( rpc_output: bool = False, + store_only: bool = False, bin_value: bool = False, canon_value: bool = False, meta_clear_default: bool = False, @@ -91,6 +92,8 @@ def newval_flags( flags = 0 if rpc_output: flags |= lib.LYD_NEW_VAL_OUTPUT + if store_only: + flags |= lib.LYD_NEW_VAL_STORE_ONLY if bin_value: flags |= lib.LYD_NEW_VAL_BIN if canon_value: @@ -112,6 +115,7 @@ def parser_flags( opaq: bool = False, ordered: bool = False, strict: bool = False, + store_only: bool = False, ) -> int: flags = 0 if lyb_mod_update: @@ -126,6 +130,8 @@ def parser_flags( flags |= lib.LYD_PARSE_ORDERED if strict: flags |= lib.LYD_PARSE_STRICT + if store_only: + flags |= lib.LYD_PARSE_STORE_ONLY return flags @@ -314,8 +320,10 @@ def meta_free(self, name): break item = item.next - def new_meta(self, name: str, value: str, clear_dflt: bool = False): - flags = newval_flags(meta_clear_default=clear_dflt) + def new_meta( + self, name: str, value: str, clear_dflt: bool = False, store_only: bool = False + ): + flags = newval_flags(meta_clear_default=clear_dflt, store_only=store_only) ret = lib.lyd_new_meta( ffi.NULL, self.cdata, @@ -391,6 +399,7 @@ def new_path( opt_opaq: bool = False, opt_bin_value: bool = False, opt_canon_value: bool = False, + opt_store_only: bool = False, ): flags = newval_flags( update=opt_update, @@ -398,6 +407,7 @@ def new_path( opaq=opt_opaq, bin_value=opt_bin_value, canon_value=opt_canon_value, + store_only=opt_store_only, ) ret = lib.lyd_new_path( self.cdata, ffi.NULL, str2c(path), str2c(value), flags, ffi.NULL @@ -1062,9 +1072,10 @@ def create_path( path: str, value: Any = None, rpc_output: bool = False, + store_only: bool = False, ) -> Optional[DNode]: return self.context.create_data_path( - path, parent=self, value=value, rpc_output=rpc_output + path, parent=self, value=value, rpc_output=rpc_output, store_only=store_only ) def children(self, no_keys=False) -> Iterator[DNode]: @@ -1188,6 +1199,7 @@ def dict_to_dnode( rpc: bool = False, rpcreply: bool = False, notification: bool = False, + store_only: bool = False, ) -> Optional[DNode]: """ Convert a python dictionary to a DNode object given a YANG module object. The return @@ -1214,6 +1226,8 @@ def dict_to_dnode( Data represents RPC or action output parameters. :arg notification: Data represents notification parameters. + :arg store_only: + Data are being stored regardless of type validation (length, range, pattern, etc.) """ if not dic: return None @@ -1235,7 +1249,7 @@ def _create_leaf(_parent, module, name, value, in_rpc_output=False): value = str(value) n = ffi.new("struct lyd_node **") - flags = newval_flags(rpc_output=in_rpc_output) + flags = newval_flags(rpc_output=in_rpc_output, store_only=store_only) ret = lib.lyd_new_term( _parent, module.cdata, @@ -1273,7 +1287,7 @@ def _create_container(_parent, module, name, in_rpc_output=False): def _create_list(_parent, module, name, key_values, in_rpc_output=False): n = ffi.new("struct lyd_node **") - flags = newval_flags(rpc_output=in_rpc_output) + flags = newval_flags(rpc_output=in_rpc_output, store_only=store_only) ret = lib.lyd_new_list( _parent, module.cdata, diff --git a/tests/test_data.py b/tests/test_data.py index 9ed35dc..9515a58 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1092,3 +1092,11 @@ def test_dnode_leafref_linking(self): self.assertIsInstance(dnode4, DLeaf) self.assertEqual(dnode4.cdata, dnode2.cdata) dnode1.free() + + def test_dnode_store_only(self): + MAIN = {"yolo-nodetypes:test1": 50} + module = self.ctx.load_module("yolo-nodetypes") + dnode = dict_to_dnode(MAIN, module, None, validate=False, store_only=True) + self.assertIsInstance(dnode, DLeaf) + self.assertEqual(dnode.value(), 50) + dnode.free() diff --git a/tests/yang/yolo/yolo-nodetypes.yang b/tests/yang/yolo/yolo-nodetypes.yang index 0732ee7..de4656b 100644 --- a/tests/yang/yolo/yolo-nodetypes.yang +++ b/tests/yang/yolo/yolo-nodetypes.yang @@ -91,7 +91,9 @@ module yolo-nodetypes { } leaf test1 { - type uint8; + type uint8 { + range "2..20"; + } } grouping grp1 { From 17cfe55d50b9544eeed310452f33daed69499c77 Mon Sep 17 00:00:00 2001 From: Stefan Gula Date: Sun, 3 Mar 2024 15:56:15 +0100 Subject: [PATCH 2/3] context: add builtin_plugins_only options This patch adds builtin_plugins_only option, which allows user to use only basic YANG type plugins, instead of using advanced plugins as ipv4-address etc. Signed-off-by: Stefan Gula --- cffi/cdefs.h | 1 + libyang/context.py | 3 +++ tests/test_data.py | 11 +++++++++++ tests/yang/yolo/yolo-nodetypes.yang | 9 +++++++++ 4 files changed, 24 insertions(+) diff --git a/cffi/cdefs.h b/cffi/cdefs.h index 2cf8c39..8165a70 100644 --- a/cffi/cdefs.h +++ b/cffi/cdefs.h @@ -16,6 +16,7 @@ struct ly_ctx; #define LY_CTX_SET_PRIV_PARSED ... #define LY_CTX_LEAFREF_EXTENDED ... #define LY_CTX_LEAFREF_LINKING ... +#define LY_CTX_BUILTIN_PLUGINS_ONLY ... typedef enum { diff --git a/libyang/context.py b/libyang/context.py index 8ed04ff..ea18327 100644 --- a/libyang/context.py +++ b/libyang/context.py @@ -31,6 +31,7 @@ def __init__( explicit_compile: Optional[bool] = False, leafref_extended: bool = False, leafref_linking: bool = False, + builtin_plugins_only: bool = False, yanglib_path: Optional[str] = None, yanglib_fmt: str = "json", cdata=None, # C type: "struct ly_ctx *" @@ -50,6 +51,8 @@ def __init__( options |= lib.LY_CTX_LEAFREF_EXTENDED if leafref_linking: options |= lib.LY_CTX_LEAFREF_LINKING + if builtin_plugins_only: + options |= lib.LY_CTX_BUILTIN_PLUGINS_ONLY # force priv parsed options |= lib.LY_CTX_SET_PRIV_PARSED diff --git a/tests/test_data.py b/tests/test_data.py index 9515a58..790b5cd 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1,6 +1,7 @@ # Copyright (c) 2020 6WIND S.A. # SPDX-License-Identifier: MIT +import gc import json import os import unittest @@ -1100,3 +1101,13 @@ def test_dnode_store_only(self): self.assertIsInstance(dnode, DLeaf) self.assertEqual(dnode.value(), 50) dnode.free() + + def test_dnode_builtin_plugins_only(self): + MAIN = {"yolo-nodetypes:ip-address": "test"} + self.tearDown() + gc.collect() + self.ctx = Context(YANG_DIR, builtin_plugins_only=True) + module = self.ctx.load_module("yolo-nodetypes") + dnode = dict_to_dnode(MAIN, module, None, validate=False, store_only=True) + self.assertIsInstance(dnode, DLeaf) + dnode.free() diff --git a/tests/yang/yolo/yolo-nodetypes.yang b/tests/yang/yolo/yolo-nodetypes.yang index de4656b..0ec00a6 100644 --- a/tests/yang/yolo/yolo-nodetypes.yang +++ b/tests/yang/yolo/yolo-nodetypes.yang @@ -3,6 +3,11 @@ module yolo-nodetypes { namespace "urn:yang:yolo:nodetypes"; prefix sys; + import ietf-inet-types { + prefix inet; + revision-date 2013-07-15; + } + description "YOLO Nodetypes."; @@ -126,4 +131,8 @@ module yolo-nodetypes { anydata any1 { when "../cont2"; } + + leaf ip-address { + type inet:ipv4-address; + } } From 1c6919bdc2cbca37b471ad4389c99b9e1e26d87b Mon Sep 17 00:00:00 2001 From: Stefan Gula Date: Mon, 4 Mar 2024 15:16:44 +0100 Subject: [PATCH 3/3] data: fixing broken DLeaf value and print_dict API This patch fixes broken APIs, which were using no longer valid structs and performing validation step instead of just checking the realtype of data Signed-off-by: Stefan Gula --- cffi/cdefs.h | 6 ++++- libyang/data.py | 57 +++++++++++++++++++++++----------------------- tests/test_data.py | 1 + 3 files changed, 34 insertions(+), 30 deletions(-) diff --git a/cffi/cdefs.h b/cffi/cdefs.h index 8165a70..3fd8e7b 100644 --- a/cffi/cdefs.h +++ b/cffi/cdefs.h @@ -900,9 +900,13 @@ struct lyd_value { ...; }; +struct lyd_value_union { + struct lyd_value value; + ...; +}; + const char * lyd_get_value(const struct lyd_node *); struct lyd_node* lyd_child(const struct lyd_node *); -LY_ERR lyd_value_validate(const struct ly_ctx *, const struct lysc_node *, const char *, size_t, const struct lyd_node *, const struct lysc_type **, const char **); LY_ERR lyd_find_path(const struct lyd_node *, const char *, ly_bool, struct lyd_node **); void lyd_free_siblings(struct lyd_node *); struct lyd_node* lyd_first_sibling(const struct lyd_node *); diff --git a/libyang/data.py b/libyang/data.py index 288303d..07281f0 100644 --- a/libyang/data.py +++ b/libyang/data.py @@ -1119,38 +1119,37 @@ def cdata_leaf_value(cdata, context: "libyang.Context" = None) -> Any: return None val = c2str(val) - term_node = ffi.cast("struct lyd_node_term *", cdata) - val_type = ffi.new("const struct lysc_type **", ffi.NULL) - - # get real value type - ctx = context.cdata if context else ffi.NULL - ret = lib.lyd_value_validate( - ctx, - term_node.schema, - str2c(val), - len(val), - ffi.NULL, - val_type, - ffi.NULL, - ) - if ret in (lib.LY_SUCCESS, lib.LY_EINCOMPLETE): - val_type = val_type[0].basetype - if val_type in Type.STR_TYPES: - return val - if val_type in Type.NUM_TYPES: - return int(val) - if val_type == Type.BOOL: - return val == "true" - if val_type == Type.DEC64: - return float(val) - if val_type == Type.LEAFREF: - return DLeaf.cdata_leaf_value(cdata.value.leafref, context) - if val_type == Type.EMPTY: - return None + if cdata.schema == ffi.NULL: + # opaq node return val - raise TypeError("value type validation error") + if cdata.schema.nodetype == SNode.LEAF: + snode = ffi.cast("struct lysc_node_leaf *", cdata.schema) + elif cdata.schema.nodetype == SNode.LEAFLIST: + snode = ffi.cast("struct lysc_node_leaflist *", cdata.schema) + + # find the real type used + cdata = ffi.cast("struct lyd_node_term *", cdata) + curr_type = snode.type + while curr_type.basetype in (Type.LEAFREF, Type.UNION): + if curr_type.basetype == Type.LEAFREF: + curr_type = cdata.value.realtype + if curr_type.basetype == Type.UNION: + curr_type = cdata.value.subvalue.value.realtype + + val_type = curr_type.basetype + if val_type in Type.STR_TYPES: + return val + if val_type in Type.NUM_TYPES: + return int(val) + if val_type == Type.BOOL: + return val == "true" + if val_type == Type.DEC64: + return float(val) + if val_type == Type.EMPTY: + return None + return val # ------------------------------------------------------------------------------------- diff --git a/tests/test_data.py b/tests/test_data.py index 790b5cd..1728042 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -1110,4 +1110,5 @@ def test_dnode_builtin_plugins_only(self): module = self.ctx.load_module("yolo-nodetypes") dnode = dict_to_dnode(MAIN, module, None, validate=False, store_only=True) self.assertIsInstance(dnode, DLeaf) + self.assertEqual(dnode.value(), "test") dnode.free()