diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7e8cd22..7ae99e37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,10 +34,8 @@ Expected: June 2021
### New features
-* Yang Deviation/deviate [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211)
+* Yang deviation [deviation statement not yet support #211](https://github.com/clicon/clixon/issues/211)
* See RFC7950 Sec 5.6.3
- * Implemented: "not-supported" and "add"
- * Not yet: "replace" and "delete"
### Minor features
diff --git a/lib/src/clixon_proto.c b/lib/src/clixon_proto.c
index 3b17eecc..d40f408a 100644
--- a/lib/src/clixon_proto.c
+++ b/lib/src/clixon_proto.c
@@ -770,11 +770,21 @@ send_msg_notify_xml(clicon_handle h,
}
/*! Look for a text pattern in an input string, one char at a time
- * @param[in] tag What to look for
- * @param[in] ch New input character
- * @param[in,out] state A state integer holding how far we have parsed.
- * @retval 0 No, we havent detected end tag
- * @retval 1 Yes, we have detected end tag!
+ * @param[in] tag What to look for
+ * @param[in] ch New input character
+ * @param[in,out] state A state integer holding how far we have parsed.
+ * @retval 0 No, we havent detected end tag
+ * @retval 1 Yes, we have detected end tag!
+ * @code
+ * int state = 0;
+ * char ch;
+ * while (1) {
+ * // read ch
+ * if (detect_endtag("mypattern", ch, &state)) {
+ * // mypattern is matched
+ * }
+ * }
+ * @endcode
*/
int
detect_endtag(char *tag,
diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c
index e9f8e620..55a3fd42 100644
--- a/lib/src/clixon_yang.c
+++ b/lib/src/clixon_yang.c
@@ -517,7 +517,8 @@ ys_prune(yang_stmt *yp,
* @param[in] ys Yang node to remove
* @retval 0 OK
* @retval -1 Error
- * @see ys_prune if parent and position is known
+ * @see ys_prune if parent and position is know
+ * @see ys_free Deallocate yang node
* @note Do not call this in a loop of yang children (unless you know what you are doing)
*/
static int
@@ -1608,6 +1609,7 @@ yang_deviation(yang_stmt *ys,
yang_stmt *yd;
yang_stmt *yc;
yang_stmt *yc1;
+ yang_stmt *ytc;
char *devop;
clicon_handle h = (clicon_handle)arg;
enum rfc_6020 kw;
@@ -1648,8 +1650,7 @@ yang_deviation(yang_stmt *ys,
else if (strcmp(devop, "add") == 0){
yc = NULL;
while ((yc = yn_each(yd, yc)) != NULL) {
- /* If a property can only appear once, the property MUST NOT
- exist in the target node. */
+ /* If a property can only appear once, the property MUST NOT exist in the target node. */
kw = yang_keyword_get(yc);
if (yang_find(ytarget, kw, NULL) != NULL){
if (yang_cardinality_interval(h,
@@ -1674,8 +1675,48 @@ yang_deviation(yang_stmt *ys,
}
}
else if (strcmp(devop, "replace") == 0){
+ yc = NULL;
+ while ((yc = yn_each(yd, yc)) != NULL) {
+ /* The properties to replace MUST exist in the target node.*/
+ kw = yang_keyword_get(yc);
+ if ((ytc = yang_find(ytarget, kw, NULL)) == NULL){
+ clicon_err(OE_YANG, 0, "deviation %s: \"%s %s\" replaced but node does not exist in target %s",
+ nodeid,
+ yang_key2str(kw), yang_argument_get(yc),
+ yang_argument_get(ytarget));
+ goto done;
+ }
+ /* Remove old */
+ if (ys_prune_self(ytc) < 0)
+ goto done;
+ if (ys_free(ytc) < 0)
+ goto done;
+ /* Make a copy of deviate child and insert. */
+ if ((yc1 = ys_dup(yc)) == NULL)
+ goto done;
+ if (yn_insert(ytarget, yc1) < 0)
+ goto done;
+ }
}
else if (strcmp(devop, "delete") == 0){
+ yc = NULL;
+ while ((yc = yn_each(yd, yc)) != NULL) {
+ /* The substatement's keyword MUST match a corresponding keyword in the target node, and the
+ * argument's string MUST be equal to the corresponding keyword's argument string in the
+ * target node. */
+ kw = yang_keyword_get(yc);
+ if ((ytc = yang_find(ytarget, kw, NULL)) == NULL){
+ clicon_err(OE_YANG, 0, "deviation %s: \"%s %s\" replaced but node does not exist in target %s",
+ nodeid,
+ yang_key2str(kw), yang_argument_get(yc),
+ yang_argument_get(ytarget));
+ goto done;
+ }
+ if (ys_prune_self(ytc) < 0)
+ goto done;
+ if (ys_free(ytc) < 0)
+ goto done;
+ }
}
else{ /* Shouldnt happen, lex/yacc takes it */
clicon_err(OE_YANG, EINVAL, "%s: invalid deviate operator", devop);
diff --git a/lib/src/clixon_yang_parse.y b/lib/src/clixon_yang_parse.y
index a6b979de..fd939aa0 100644
--- a/lib/src/clixon_yang_parse.y
+++ b/lib/src/clixon_yang_parse.y
@@ -1508,12 +1508,6 @@ notification_substmt : if_feature_stmt { _PARSE_DEBUG("notification-substmt ->
| { _PARSE_DEBUG("notification-substmt -> "); }
;
-/* deviation /oc-sys:system/oc-sys:config/oc-sys:hostname {
- deviate not-supported;
- }
- * XXX abs-schema-nodeid-str is too difficult, it needs the + semantics
-
-*/
deviation_stmt : K_DEVIATION string
{ if (ysp_add_push(_yy, Y_DEVIATION, $2, NULL) == NULL) _YYERROR("deviation_stmt"); }
'{' deviation_substmts '}'
@@ -1569,13 +1563,13 @@ deviate_add_substmt : units_stmt { _PARSE_DEBUG("deviate-add-substmt -> units
deviate_delete_stmt : K_DEVIATE D_DELETE ';'
- { if (ysp_add(_yy, Y_DEVIATE, strdup("add") /* D_NOT_SUPPORTED*/, NULL) == NULL) _YYERROR("notification_stmt");
- _PARSE_DEBUG("deviate-delete-stmt -> DEVIATE add ;"); }
+ { if (ysp_add(_yy, Y_DEVIATE, strdup("delete"), NULL) == NULL) _YYERROR("notification_stmt");
+ _PARSE_DEBUG("deviate-delete-stmt -> DEVIATE delete ;"); }
| K_DEVIATE D_DELETE
- { if (ysp_add_push(_yy, Y_DEVIATE, strdup("add"), NULL) == NULL) _YYERROR("deviate_stmt"); }
+ { if (ysp_add_push(_yy, Y_DEVIATE, strdup("delete"), NULL) == NULL) _YYERROR("deviate_stmt"); }
'{' deviate_delete_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("deviate_stmt");
- _PARSE_DEBUG("deviate-delete-stmt -> DEVIATE add { deviate-substmts }"); }
+ _PARSE_DEBUG("deviate-delete-stmt -> DEVIATE delete { deviate-delete-substmts }"); }
;
deviate_delete_substmts : deviate_delete_substmts deviate_delete_substmt
@@ -1590,15 +1584,14 @@ deviate_delete_substmt : units_stmt { _PARSE_DEBUG("deviate-delete-substmt -> un
| { _PARSE_DEBUG("deviate-delete-substmt -> "); }
;
-
deviate_replace_stmt : K_DEVIATE D_REPLACE ';'
- { if (ysp_add(_yy, Y_DEVIATE, strdup("add") /* D_NOT_SUPPORTED*/, NULL) == NULL) _YYERROR("notification_stmt");
- _PARSE_DEBUG("deviate-replace-stmt -> DEVIATE add ;"); }
+ { if (ysp_add(_yy, Y_DEVIATE, strdup("replace"), NULL) == NULL) _YYERROR("notification_stmt");
+ _PARSE_DEBUG("deviate-replace-stmt -> DEVIATE replace ;"); }
| K_DEVIATE D_REPLACE
- { if (ysp_add_push(_yy, Y_DEVIATE, strdup("add"), NULL) == NULL) _YYERROR("deviate_stmt"); }
+ { if (ysp_add_push(_yy, Y_DEVIATE, strdup("replace"), NULL) == NULL) _YYERROR("deviate_stmt"); }
'{' deviate_replace_substmts '}'
{ if (ystack_pop(_yy) < 0) _YYERROR("deviate_stmt");
- _PARSE_DEBUG("deviate-replace-stmt -> DEVIATE add { deviate-substmts }"); }
+ _PARSE_DEBUG("deviate-replace-stmt -> DEVIATE replace { deviate-replace-substmts }"); }
;
deviate_replace_substmts : deviate_replace_substmts deviate_replace_substmt
@@ -1688,40 +1681,40 @@ yang_stmt : action_stmt { _PARSE_DEBUG("yang-stmt -> action-stmt");
| leaf_stmt { _PARSE_DEBUG("yang-stmt -> leaf-stmt");}
| length_stmt { _PARSE_DEBUG("yang-stmt -> length-stmt");}
| list_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | mandatory_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | max_elements_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | min_elements_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | modifier_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | module_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | must_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | namespace_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
+ | mandatory_stmt { _PARSE_DEBUG("yang-stmt -> mandatory-stmt");}
+ | max_elements_stmt { _PARSE_DEBUG("yang-stmt -> max-elements-stmt");}
+ | min_elements_stmt { _PARSE_DEBUG("yang-stmt -> min-elements-stmt");}
+ | modifier_stmt { _PARSE_DEBUG("yang-stmt -> modifier-stmt");}
+ | module_stmt { _PARSE_DEBUG("yang-stmt -> module-stmt");}
+ | must_stmt { _PARSE_DEBUG("yang-stmt -> must-stmt");}
+ | namespace_stmt { _PARSE_DEBUG("yang-stmt -> namespace-stmt");}
| notification_stmt { _PARSE_DEBUG("yang-stmt -> notification-stmt");}
- | ordered_by_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | organization_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | output_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | path_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | pattern_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | position_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | prefix_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | presence_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | range_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | reference_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | refine_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | require_instance_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | revision_date_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | revision_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
+ | ordered_by_stmt { _PARSE_DEBUG("yang-stmt -> ordered-by-stmt");}
+ | organization_stmt { _PARSE_DEBUG("yang-stmt -> organization-stmt");}
+ | output_stmt { _PARSE_DEBUG("yang-stmt -> output-stmt");}
+ | path_stmt { _PARSE_DEBUG("yang-stmt -> path-stmt");}
+ | pattern_stmt { _PARSE_DEBUG("yang-stmt -> pattern-stmt");}
+ | position_stmt { _PARSE_DEBUG("yang-stmt -> position-stmt");}
+ | prefix_stmt { _PARSE_DEBUG("yang-stmt -> prefix-stmt");}
+ | presence_stmt { _PARSE_DEBUG("yang-stmt -> presence-stmt");}
+ | range_stmt { _PARSE_DEBUG("yang-stmt -> range-stmt");}
+ | reference_stmt { _PARSE_DEBUG("yang-stmt -> reference-stmt");}
+ | refine_stmt { _PARSE_DEBUG("yang-stmt -> refine-stmt");}
+ | require_instance_stmt { _PARSE_DEBUG("yang-stmt -> require-instance-stmt");}
+ | revision_date_stmt { _PARSE_DEBUG("yang-stmt -> revision-date-stmt");}
+ | revision_stmt { _PARSE_DEBUG("yang-stmt -> revision-stmt");}
| rpc_stmt { _PARSE_DEBUG("yang-stmt -> rpc-stmt");}
- | status_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | submodule_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
+ | status_stmt { _PARSE_DEBUG("yang-stmt -> status-stmt");}
+ | submodule_stmt { _PARSE_DEBUG("yang-stmt -> submodule-stmt");}
| typedef_stmt { _PARSE_DEBUG("yang-stmt -> typedef-stmt");}
- | type_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | unique_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | units_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | uses_augment_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | uses_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | value_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | when_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
- | yang_version_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");}
+ | type_stmt { _PARSE_DEBUG("yang-stmt -> type-stmt");}
+ | unique_stmt { _PARSE_DEBUG("yang-stmt -> unique-stmt");}
+ | units_stmt { _PARSE_DEBUG("yang-stmt -> units-stmt");}
+ | uses_augment_stmt { _PARSE_DEBUG("yang-stmt -> uses-augment-stmt");}
+ | uses_stmt { _PARSE_DEBUG("yang-stmt -> uses-stmt");}
+ | value_stmt { _PARSE_DEBUG("yang-stmt -> value-stmt");}
+ | when_stmt { _PARSE_DEBUG("yang-stmt -> when-stmt");}
+ | yang_version_stmt { _PARSE_DEBUG("yang-stmt -> yang-version-stmt");}
/* | yin_element_stmt { _PARSE_DEBUG("yang-stmt -> list-stmt");} */
;
diff --git a/test/test_yang_deviation.sh b/test/test_yang_deviation.sh
index cf00cb7c..654a6385 100755
--- a/test/test_yang_deviation.sh
+++ b/test/test_yang_deviation.sh
@@ -35,11 +35,15 @@ module example-base{
prefix base;
namespace "urn:example:base";
container system {
- must "daytime or time";
- leaf daytime{ /* not supported removes this */
+ must "daytime or time"; /* deviate delete removes this */
+ leaf daytime{ /* deviate not-supported removes this */
+ type string;
+ }
+ leaf time{
type string;
}
list name-server {
+ max-elements 1; /* deviate replace replaces to "max.elements 3" here */
key name;
leaf name {
type string;
@@ -52,7 +56,7 @@ module example-base{
}
leaf type {
type string;
- /* add rule adds default here */
+ /* deviate add adds "default admin" here */
}
}
}
@@ -60,13 +64,17 @@ module example-base{
EOF
# Args:
-# 0: daytime implemented: true/false
-# 1: admin type default: true/false
+# 1: daytime implemented: true/false
+# 2: admin type default: true/false
+# 3: mustdate default: true/false
+# 4: maxelement of name-server is 1: true/false (if false the # is 3)
function testrun()
{
daytime=$1
admindefault=$2
-
+ mustdate=$3
+ maxel1=$4
+
new "test params: -f $cfg"
if [ "$BE" -ne 0 ]; then
@@ -82,29 +90,55 @@ function testrun()
new "wait backend"
wait_backend
- if ! $daytime; then # Not supported - dont continue
- new "Add example-base daytime - should not be supported"
- expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOSept17]]>]]>" "applicationunknown-elementdaytimeerrorFailed to find YANG spec of XML node: daytime with parent: system in namespace: urn:example:base]]>]]"
+ new "Add user bob"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLObob]]>]]>" "]]>]]"
+
+ if $mustdate; then # fail since there is neither date or daytime (delete rule)
+ new "netconf validate expect error"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^applicationoperation-failederrormust xpath validation failed]]>]]>$"
else
+ new "netconf validate ok"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "]]>]]"
+ fi
+
+ new "Add time"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "]]>]]"
+
+ new "netconf validate ok"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "]]>]]"
+
+ if $daytime; then # not-supported rule
new "Add example-base daytime - supported"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOSept17]]>]]>" "]]>]]"
+ else # Not supported
+ new "Add example-base daytime - expect error not supported"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOSept17]]>]]>" "applicationunknown-elementdaytimeerrorFailed to find YANG spec of XML node: daytime with parent: system in namespace: urn:example:base]]>]]"
+ fi # daytime supported
- new "Add user bob"
- expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLObob]]>]]>" "]]>]]"
-
- new "netconf commit"
- expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$"
+ new "netconf commit"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$"
- if $admindefault; then
- new "Get type admin expected"
- expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^bobadmin]]>]]>$"
+ if $admindefault; then # add rule
+ new "Get type admin expected"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^bobadmin]]>]]>$"
# XXX Cannot select a default value??
-# expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" foo
- else
- new "Get type none expected"
- expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$"
- fi
+ # expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" foo
+ else
+ new "Get type none expected"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$"
fi
+
+ # Add 2 name-servers
+ new "Add two name-servers"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLOaabb]]>]]>" "]]>]]"
+ if $maxel1; then # add two and check if it fails
+ new "netconf validate 2 element fail"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^protocoloperation-failedtoo-many-elementserror/system/name-server]]>]]>$"
+ else
+ new "netconf validate 2 elements ok"
+ expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "]]>]]"
+ fi
+
if [ "$BE" -ne 0 ]; then
new "Kill backend"
# Check if premature kill
@@ -128,8 +162,8 @@ module example-deviations{
}
}
EOF
-new "daytime supported"
-testrun true false
+new "1. daytime supported"
+testrun true false true true
# Example from RFC 7950 Sec 7.20.3.3
cat < $fyangdev
@@ -145,10 +179,10 @@ module example-deviations{
}
}
EOF
-new "daytime not supported"
-testrun false false
+new "2. daytime not supported"
+testrun false false true true
-# Example from RFC 7950 Sec 7.20.3.3
+# Add example from RFC 7950 Sec 7.20.3.3
cat < $fyangdev
module example-deviations{
yang-version 1.1;
@@ -164,8 +198,47 @@ module example-deviations{
}
}
EOF
-new "deviate add, check admin default"
-testrun true true
+new "3. deviate add, check admin default"
+testrun true true true true
+
+# Delete example from RFC 7950 Sec 7.20.3.3
+cat < $fyangdev
+module example-deviations{
+ yang-version 1.1;
+ prefix md;
+ namespace "urn:example:deviations";
+ import example-base {
+ prefix base;
+ }
+ deviation /base:system/base:name-server {
+ deviate replace {
+ max-elements 3;
+ }
+ }
+}
+EOF
+new "4. deviate replace"
+testrun true false true false
+
+# Replace example from RFC 7950 Sec 7.20.3.3
+cat < $fyangdev
+module example-deviations{
+ yang-version 1.1;
+ prefix md;
+ namespace "urn:example:deviations";
+ import example-base {
+ prefix base;
+ }
+ deviation /base:system {
+ deviate delete {
+ must "daytime or time";
+ }
+ }
+}
+EOF
+
+new "5. deviate delete"
+testrun true false false true
rm -rf "$dir"