* YANG schema mount RFC 8528, Initial commit (work in progress)
* Keep track of YANG unknowns with ys_cvec of EXTENSION * C-API: Init ys_cvec to NULL, added yang_cvec_add() and adjusted code to use it
This commit is contained in:
parent
8451a20db7
commit
b3dcee9639
21 changed files with 552 additions and 79 deletions
|
|
@ -43,7 +43,12 @@ Expected: beginning of 2023
|
|||
|
||||
### New features
|
||||
|
||||
* Netconf monitoring, part 2
|
||||
* YANG schema mount RFC 8528 (work in progress)
|
||||
* Restrictions:
|
||||
* only schema-ref=inline, not shared-schema
|
||||
* Standards: RFC 8528
|
||||
* Enable `YANG_SCHEMA_MOUNT`
|
||||
* Netconf monitoring RFC 6022 , part 2
|
||||
* Datastores and sessions
|
||||
* Added clixon-specific transport identities: cli, snmp, netconf, restconf
|
||||
* Added source-host fro native restonf, but no other transports
|
||||
|
|
|
|||
|
|
@ -297,6 +297,19 @@ get_client_statedata(clicon_handle h,
|
|||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
#ifdef YANG_SCHEMA_MOUNT
|
||||
if ((ret = schema_mounts_state_get(h, yspec, xpath, nsc, xret, &xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
if (clixon_netconf_internal_error(xerr, " . Internal error, schema_mounts_state_get returned invalid XML", NULL) < 0)
|
||||
goto done;
|
||||
if (*xret)
|
||||
xml_free(*xret);
|
||||
*xret = xerr;
|
||||
xerr = NULL;
|
||||
goto fail;
|
||||
}
|
||||
#endif
|
||||
/* Use plugin state callbacks */
|
||||
if ((ret = clixon_plugin_statedata_all(h, yspec, nsc, xpath, wdef, xret)) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -266,12 +266,6 @@ expand_dbvar(void *h,
|
|||
/* Transform api-path to xpath for netconf */
|
||||
if (api_path2xpath(api_path, yspec, &xpath, &nsc, NULL) < 0)
|
||||
goto done;
|
||||
if (nsc != NULL){
|
||||
cvec_free(nsc);
|
||||
nsc = NULL;
|
||||
}
|
||||
if (xml_nsctx_yang(y, &nsc) < 0)
|
||||
goto done;
|
||||
if ((cbxpath = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||
goto done;
|
||||
|
|
|
|||
|
|
@ -143,7 +143,8 @@ match_list_keys(yang_stmt *y,
|
|||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
switch (yang_keyword_get(y)){
|
||||
case Y_LIST:
|
||||
cvk = yang_cvec_get(y); /* Use Y_LIST cache, see ys_populate_list() */
|
||||
if ((cvk = yang_cvec_get(y)) == NULL) /* Use Y_LIST cache, see ys_populate_list() */
|
||||
break;
|
||||
cvi = NULL;
|
||||
while ((cvi = cvec_each(cvk, cvi)) != NULL) {
|
||||
keyname = cv_string_get(cvi);
|
||||
|
|
|
|||
|
|
@ -900,7 +900,8 @@ snmp_yang2xpath_cb(yang_stmt *ys,
|
|||
}
|
||||
switch (yang_keyword_get(ys)){
|
||||
case Y_LIST:
|
||||
cvk = yang_cvec_get(ys); /* Use Y_LIST cache, see ys_populate_list() */
|
||||
if ((cvk = yang_cvec_get(ys)) == NULL) /* Use Y_LIST cache, see ys_populate_list() */
|
||||
break;
|
||||
/* Iterate over individual keys */
|
||||
assert(keyvec && cvec_len(cvk) == cvec_len(keyvec));
|
||||
for (i=0; i<cvec_len(cvk); i++){
|
||||
|
|
|
|||
|
|
@ -191,3 +191,9 @@
|
|||
* To keep the previous behavior (as in 6.0) set this option with #define
|
||||
*/
|
||||
#undef NETCONF_DEFAULT_RETRIEVAL_REPORT_ALL
|
||||
|
||||
/*! Development option for RFC 8528 YANG schema mount
|
||||
* Work-in-progress
|
||||
* See also test/test_yang_schema_mount.sh
|
||||
*/
|
||||
#undef YANG_SCHEMA_MOUNT
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ extern "C" {
|
|||
#include <clixon/clixon_xml_sort.h>
|
||||
#include <clixon/clixon_yang_parse_lib.h>
|
||||
#include <clixon/clixon_yang_module.h>
|
||||
#include <clixon/clixon_yang_schema_mount.h>
|
||||
#include <clixon/clixon_netconf_monitoring.h>
|
||||
#include <clixon/clixon_stream.h>
|
||||
#include <clixon/clixon_proto.h>
|
||||
|
|
|
|||
|
|
@ -132,8 +132,8 @@ typedef int (plgdaemon_t)(clicon_handle); /* Plugin pre/post daemon
|
|||
*/
|
||||
typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
|
||||
|
||||
/* For yang extension handling.
|
||||
* Called at parsing of yang module containing a statement of an extension.
|
||||
/* For yang extension/unknown handling.
|
||||
* Called at parsing of yang module containing an unknown statement of an extension.
|
||||
* A plugin may identify the extension by its name, and perform actions
|
||||
* on the yang statement, such as transforming the yang.
|
||||
* A callback is made for every statement, which means that several calls per
|
||||
|
|
@ -294,7 +294,7 @@ struct clixon_plugin_api{
|
|||
plginit2_t *ca_init; /* Clixon plugin Init (implicit) */
|
||||
plgstart_t *ca_start; /* Plugin start */
|
||||
plgexit_t *ca_exit; /* Plugin exit */
|
||||
plgextension_t *ca_extension; /* Yang extension handler */
|
||||
plgextension_t *ca_extension; /* Yang extension/unknown handler */
|
||||
union {
|
||||
struct { /* cli-specific */
|
||||
cli_prompthook_t *ci_prompt; /* Prompt hook */
|
||||
|
|
|
|||
|
|
@ -165,6 +165,9 @@ typedef enum yang_bind yang_bind;
|
|||
typedef struct xml cxobj; /* struct defined in clicon_xml.c */
|
||||
|
||||
/*! Callback function type for xml_apply
|
||||
*
|
||||
* @param[in] x XML node
|
||||
* @param[in] arg General-purpose argument
|
||||
* @retval -1 Error, aborted at first error encounter, return -1 to end user
|
||||
* @retval 0 OK, continue
|
||||
* @retval 1 Abort, dont continue with others, return 1 to end user
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ int modstate_diff_free(modstate_diff_t *);
|
|||
|
||||
int yang_modules_init(clicon_handle h);
|
||||
char *yang_modules_revision(clicon_handle h);
|
||||
|
||||
int yang_modules_state_build(clicon_handle h, yang_stmt *yspec, char *msid, int brief, cbuf *cb);
|
||||
int yang_modules_state_get(clicon_handle h, yang_stmt *yspec, char *xpath,
|
||||
cvec *nsc, int brief, cxobj **xret);
|
||||
int clixon_module_upgrade(clicon_handle h, cxobj *xt, modstate_diff_t *msd, cbuf *cb);
|
||||
|
|
|
|||
55
lib/clixon/clixon_yang_schema_mount.h
Normal file
55
lib/clixon/clixon_yang_schema_mount.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2023 Olof Hagsand
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _CLIXON_YANG_SCHEMA_MOUNT_H_
|
||||
#define _CLIXON_YANG_SCHEMA_MOUNT_H_
|
||||
|
||||
/*
|
||||
* Constants
|
||||
*/
|
||||
|
||||
/* RFC 8528 YANG Schema Mount
|
||||
*/
|
||||
#define YANG_SCHEMA_MOUNT_NAMESPACE "urn:ietf:params:xml:ns:yang:ietf-yang-schema-mount"
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int yang_schema_mount_point(yang_stmt *y);
|
||||
|
||||
int schema_mounts_state_get(clicon_handle h, yang_stmt *yspec, char *xpath, cvec *nsc, cxobj **xret, cxobj **xerr);
|
||||
int yang_schema_unknown(clicon_handle h, yang_stmt *yext, yang_stmt *ys);
|
||||
|
||||
#endif /* _CLIXON_YANG_SCHEMA_MOUNT_H_ */
|
||||
|
|
@ -84,7 +84,8 @@ SRC = clixon_sig.c clixon_uid.c clixon_log.c clixon_err.c clixon_event.c \
|
|||
clixon_xml_default.c clixon_xml_bind.c clixon_json.c clixon_proc.c \
|
||||
clixon_yang.c clixon_yang_type.c clixon_yang_module.c clixon_netconf_monitoring.c \
|
||||
clixon_yang_parse_lib.c clixon_yang_sub_parse.c \
|
||||
clixon_yang_cardinality.c clixon_xml_changelog.c clixon_xml_nsctx.c \
|
||||
clixon_yang_cardinality.c clixon_yang_schema_mount.c \
|
||||
clixon_xml_changelog.c clixon_xml_nsctx.c \
|
||||
clixon_path.c clixon_validate.c clixon_validate_minmax.c \
|
||||
clixon_hash.c clixon_options.c clixon_data.c clixon_plugin.c \
|
||||
clixon_proto.c clixon_proto_client.c \
|
||||
|
|
|
|||
|
|
@ -527,6 +527,7 @@ text_modify(clicon_handle h,
|
|||
}
|
||||
}
|
||||
x1name = xml_name(x1);
|
||||
|
||||
if (yang_keyword_get(y0) == Y_LEAF_LIST ||
|
||||
yang_keyword_get(y0) == Y_LEAF){
|
||||
/* This is a check that a leaf does not have sub-elements
|
||||
|
|
@ -897,6 +898,7 @@ text_modify(clicon_handle h,
|
|||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
||||
x0c = x0vec[i++];
|
||||
x1cname = xml_name(x1c);
|
||||
|
||||
yc = yang_find_datanode(y0, x1cname);
|
||||
if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t,
|
||||
yc, op,
|
||||
|
|
@ -963,10 +965,8 @@ text_modify(clicon_handle h,
|
|||
|
||||
/*! Modify a top-level base tree x0 with modification tree x1
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] x0 Base xml tree (can be NULL in add scenarios)
|
||||
* @param[in] x0t Top level of existing tree, eg needed for NACM rules
|
||||
* @param[in] x1 XML tree which modifies base
|
||||
* @param[in] x1t Request root node (nacm needs this)
|
||||
* @param[in] x0t Base xml tree (can be NULL in add scenarios)
|
||||
* @param[in] x1t XML tree which modifies base
|
||||
* @param[in] yspec Top-level yang spec (if y is NULL)
|
||||
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
|
||||
* @param[in] username User name of requestor for nacm
|
||||
|
|
@ -980,9 +980,7 @@ text_modify(clicon_handle h,
|
|||
*/
|
||||
static int
|
||||
text_modify_top(clicon_handle h,
|
||||
cxobj *x0,
|
||||
cxobj *x0t,
|
||||
cxobj *x1,
|
||||
cxobj *x1t,
|
||||
yang_stmt *yspec,
|
||||
enum operation_type op,
|
||||
|
|
@ -1002,7 +1000,7 @@ text_modify_top(clicon_handle h,
|
|||
char *createstr = NULL;
|
||||
|
||||
/* Check for operations embedded in tree according to netconf */
|
||||
if ((ret = attr_ns_value(x1,
|
||||
if ((ret = attr_ns_value(x1t,
|
||||
"operation", NETCONF_BASE_NAMESPACE,
|
||||
cbret, &opstr)) < 0)
|
||||
goto done;
|
||||
|
|
@ -1011,25 +1009,25 @@ text_modify_top(clicon_handle h,
|
|||
if (opstr != NULL)
|
||||
if (xml_operation(opstr, &op) < 0)
|
||||
goto done;
|
||||
if ((ret = attr_ns_value(x1, "objectcreate", NULL, cbret, &createstr)) < 0)
|
||||
if ((ret = attr_ns_value(x1t, "objectcreate", NULL, cbret, &createstr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
/* Special case if incoming x1 is empty, top-level only <config/> */
|
||||
if (xml_child_nr_type(x1, CX_ELMNT) == 0){
|
||||
if (xml_child_nr_type(x0, CX_ELMNT)){ /* base tree not empty */
|
||||
/* Special case if incoming x1t is empty, top-level only <config/> */
|
||||
if (xml_child_nr_type(x1t, CX_ELMNT) == 0){
|
||||
if (xml_child_nr_type(x0t, CX_ELMNT)){ /* base tree not empty */
|
||||
switch(op){
|
||||
case OP_DELETE:
|
||||
case OP_REMOVE:
|
||||
case OP_REPLACE:
|
||||
if (!permit && xnacm){
|
||||
if ((ret = nacm_datanode_write(h, x0, x0t, NACM_DELETE, username, xnacm, cbret)) < 0)
|
||||
if ((ret = nacm_datanode_write(h, x0t, x0t, NACM_DELETE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
while ((x0c = xml_child_i(x0, 0)) != 0)
|
||||
while ((x0c = xml_child_i(x0t, 0)) != 0)
|
||||
if (xml_purge(x0c) < 0)
|
||||
goto done;
|
||||
break;
|
||||
|
|
@ -1057,25 +1055,25 @@ text_modify_top(clicon_handle h,
|
|||
/* Special case top-level replace */
|
||||
else if (op == OP_REPLACE || op == OP_DELETE){
|
||||
if (createstr != NULL){
|
||||
if (xml_child_nr_type(x0, CX_ELMNT)) /* base tree not empty */
|
||||
if (xml_child_nr_type(x0t, CX_ELMNT)) /* base tree not empty */
|
||||
clicon_data_set(h, "objectexisted", "true");
|
||||
else
|
||||
clicon_data_set(h, "objectexisted", "false");
|
||||
}
|
||||
if (!permit && xnacm){
|
||||
if ((ret = nacm_datanode_write(h, x1, x1t, NACM_UPDATE, username, xnacm, cbret)) < 0)
|
||||
if ((ret = nacm_datanode_write(h, x1t, x1t, NACM_UPDATE, username, xnacm, cbret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
permit = 1;
|
||||
}
|
||||
while ((x0c = xml_child_i(x0, 0)) != 0)
|
||||
while ((x0c = xml_child_i(x0t, 0)) != 0)
|
||||
if (xml_purge(x0c) < 0)
|
||||
goto done;
|
||||
}
|
||||
/* Loop through children of the modification tree */
|
||||
x1c = NULL;
|
||||
while ((x1c = xml_child_each(x1, x1c, CX_ELMNT)) != NULL) {
|
||||
while ((x1c = xml_child_each(x1t, x1c, CX_ELMNT)) != NULL) {
|
||||
x1cname = xml_name(x1c);
|
||||
/* Get yang spec of the child */
|
||||
yc = NULL;
|
||||
|
|
@ -1101,7 +1099,7 @@ text_modify_top(clicon_handle h,
|
|||
}
|
||||
}
|
||||
/* See if there is a corresponding node in the base tree */
|
||||
if (match_base_child(x0, x1c, yc, &x0c) < 0)
|
||||
if (match_base_child(x0t, x1c, yc, &x0c) < 0)
|
||||
goto done;
|
||||
if (x0c && (yc != xml_spec(x0c))){
|
||||
/* There is a match but is should be replaced (choice)*/
|
||||
|
|
@ -1109,7 +1107,7 @@ text_modify_top(clicon_handle h,
|
|||
goto done;
|
||||
x0c = NULL;
|
||||
}
|
||||
if ((ret = text_modify(h, x0c, x0, x0t, x1c, x1t,
|
||||
if ((ret = text_modify(h, x0c, x0t, x0t, x1c, x1t,
|
||||
yc, op,
|
||||
username, xnacm, permit, cbret)) < 0)
|
||||
goto done;
|
||||
|
|
@ -1228,7 +1226,7 @@ xmldb_put(clicon_handle h,
|
|||
* Modify base tree x with modification x1. This is where the
|
||||
* new tree is made.
|
||||
*/
|
||||
if ((ret = text_modify_top(h, x0, x0, x1, x1, yspec, op, username, xnacm, permit, cbret)) < 0)
|
||||
if ((ret = text_modify_top(h, x0, x1, yspec, op, username, xnacm, permit, cbret)) < 0)
|
||||
goto done;
|
||||
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
||||
if (ret == 0){
|
||||
|
|
|
|||
|
|
@ -1344,6 +1344,7 @@ api_path_resolve(clixon_path *cplist,
|
|||
int i;
|
||||
cg_var *cva;
|
||||
cg_var *cvy;
|
||||
cvec *cvk;
|
||||
|
||||
if ((cp = cplist) != NULL){
|
||||
do {
|
||||
|
|
@ -1372,7 +1373,8 @@ api_path_resolve(clixon_path *cplist,
|
|||
}
|
||||
}
|
||||
else if (yang_keyword_get(yc) == Y_LIST){
|
||||
if (cvec_len(cp->cp_cvk) > cvec_len(yang_cvec_get(yc))){
|
||||
cvk = yang_cvec_get(yc);
|
||||
if (cvec_len(cp->cp_cvk) > cvec_len(cvk)){
|
||||
clicon_err(OE_YANG, ENOENT, "Number of keys in key-value list does not match Yang list");
|
||||
goto fail;
|
||||
}
|
||||
|
|
@ -1384,7 +1386,7 @@ api_path_resolve(clixon_path *cplist,
|
|||
cv_name_get(cva));
|
||||
goto fail;
|
||||
}
|
||||
cvy = cvec_i(yang_cvec_get(yc), i++);
|
||||
cvy = cvec_i(cvk, i++);
|
||||
cv_name_set(cva, cv_string_get(cvy));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -227,7 +227,6 @@ check_unique_list_direct(cxobj *x,
|
|||
char *str;
|
||||
cvec *cvk;
|
||||
|
||||
cvk = yang_cvec_get(yu);
|
||||
/* If list and is sorted by system, then it is assumed elements are in key-order which is optimized
|
||||
* Other cases are "unique" constraint or list sorted by user which is quadratic in complexity
|
||||
* This second case COULD be optimized if binary insert is made on the vec vector.
|
||||
|
|
|
|||
|
|
@ -137,8 +137,8 @@ strip_body_objects(cxobj *xt)
|
|||
*
|
||||
* @param[in] xt XML tree node
|
||||
* @param[out] xerr Reason for failure, or NULL
|
||||
* @retval 1 OK Yang assignment made
|
||||
* @retval 2 OK Yang assignment not made because yang parent is anyxml or anydata
|
||||
* @retval 1 OK Yang assignment made
|
||||
* @retval 0 Yang assigment not made and xerr set
|
||||
* @retval -1 Error
|
||||
* @note retval = 2 is special
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ xml_default_choice(yang_stmt *yc,
|
|||
yang_stmt *yca = NULL;
|
||||
yang_stmt *ydef;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
clicon_debug(2, "%s", __FUNCTION__);
|
||||
/* 1. Is there a default case and no child under this choice?
|
||||
*/
|
||||
x = NULL;
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@
|
|||
#include "clixon_yang_parse_lib.h"
|
||||
#include "clixon_yang_cardinality.h"
|
||||
#include "clixon_yang_type.h"
|
||||
#include "clixon_yang_schema_mount.h"
|
||||
#include "clixon_yang_internal.h" /* internal included by this file only, not API*/
|
||||
|
||||
#ifdef XML_EXPLICIT_INDEX
|
||||
|
|
@ -274,6 +275,7 @@ yang_cv_set(yang_stmt *ys,
|
|||
|
||||
/*! Get yang statement CLIgen variable vector
|
||||
* @param[in] ys Yang statement node
|
||||
* @note To add entries, use yang_cvec_add(), since this function may return NULL
|
||||
*/
|
||||
cvec*
|
||||
yang_cvec_get(yang_stmt *ys)
|
||||
|
|
@ -297,6 +299,41 @@ yang_cvec_set(yang_stmt *ys,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*! Add new value to yang cvec, create if not exist
|
||||
*
|
||||
* @param[in] ys Yang statement
|
||||
* @param[in] type Append a new cv to the vector with this type
|
||||
* @param[in] name Name of variable
|
||||
* @retval cv The new cligen variable
|
||||
* @retval NULL Error
|
||||
*/
|
||||
static cg_var *
|
||||
yang_cvec_add(yang_stmt *ys,
|
||||
enum cv_type type,
|
||||
char *name)
|
||||
|
||||
{
|
||||
cg_var *cv;
|
||||
cvec *cvv;
|
||||
|
||||
if ((cvv = yang_cvec_get(ys)) == NULL){
|
||||
if ((cvv = cvec_new(0)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cvec_new");
|
||||
return NULL;
|
||||
}
|
||||
yang_cvec_set(ys, cvv);
|
||||
}
|
||||
if ((cv = cvec_add(cvv, type)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cvec_add");
|
||||
return NULL;
|
||||
}
|
||||
if (cv_name_set(cv, name) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cv_name_set(%s)", name);
|
||||
return NULL;
|
||||
}
|
||||
return cv;
|
||||
}
|
||||
|
||||
/*! Get yang stmt flags, used for internal algorithms
|
||||
* @param[in] ys Yang statement
|
||||
* @param[in] flag Flags value(s) to get, see YANG_FLAG_*
|
||||
|
|
@ -594,7 +631,6 @@ yang_stmt *
|
|||
ys_new(enum rfc_6020 keyw)
|
||||
{
|
||||
yang_stmt *ys;
|
||||
cvec *cvv;
|
||||
|
||||
if ((ys = malloc(sizeof(*ys))) == NULL){
|
||||
clicon_err(OE_YANG, errno, "malloc");
|
||||
|
|
@ -602,13 +638,6 @@ ys_new(enum rfc_6020 keyw)
|
|||
}
|
||||
memset(ys, 0, sizeof(*ys));
|
||||
ys->ys_keyword = keyw;
|
||||
/* The cvec contains stmt-specific variables. Only few stmts need variables so the
|
||||
cvec could be lazily created to save some heap and cycles. */
|
||||
if ((cvv = cvec_new(0)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cvec_new");
|
||||
return NULL;
|
||||
}
|
||||
yang_cvec_set(ys, cvv);
|
||||
_stats_yang_nr++;
|
||||
return ys;
|
||||
}
|
||||
|
|
@ -1082,13 +1111,17 @@ yang_match(yang_stmt *yn,
|
|||
return match;
|
||||
}
|
||||
|
||||
|
||||
/*! Find child data node with matching argument (container, leaf, list, leaf-list)
|
||||
*
|
||||
* @param[in] yn Yang node, current context node.
|
||||
* @param[in] argument if NULL, match any(first) argument. XXX is that really a case?
|
||||
* @param[in] argument Argument that child should match with
|
||||
* @retval ymatch Matching child
|
||||
* @retval NULL No match or error
|
||||
*
|
||||
* @see yang_find Looks for any node
|
||||
* @note May deviate from RFC since it explores choice/case not just return it.
|
||||
* XXX: differentiate between not found and error
|
||||
*/
|
||||
yang_stmt *
|
||||
yang_find_datanode(yang_stmt *yn,
|
||||
|
|
@ -1099,7 +1132,16 @@ yang_find_datanode(yang_stmt *yn,
|
|||
yang_stmt *yspec;
|
||||
yang_stmt *ysmatch = NULL;
|
||||
char *name;
|
||||
#ifdef YANG_SCHEMA_MOUNT
|
||||
int ret;
|
||||
|
||||
/* Sanity-check mount-point extension */
|
||||
if ((ret = yang_schema_mount_point(yn)) < 0)
|
||||
goto done;
|
||||
if (ret == 1){
|
||||
; // NYI
|
||||
}
|
||||
#endif
|
||||
ys = NULL;
|
||||
while ((ys = yn_each(yn, ys)) != NULL){
|
||||
if (yang_keyword_get(ys) == Y_CHOICE){ /* Look for its children */
|
||||
|
|
@ -1109,14 +1151,11 @@ yang_find_datanode(yang_stmt *yn,
|
|||
ysmatch = yang_find_datanode(yc, argument);
|
||||
else
|
||||
if (yang_datanode(yc)){
|
||||
if (argument == NULL)
|
||||
ysmatch = yc;
|
||||
else
|
||||
if (yc->ys_argument && strcmp(argument, yc->ys_argument) == 0)
|
||||
ysmatch = yc;
|
||||
}
|
||||
if (ysmatch)
|
||||
goto match; // maybe break?
|
||||
goto done; // maybe break?
|
||||
}
|
||||
} /* Y_CHOICE */
|
||||
else if (yang_keyword_get(ys) == Y_INPUT ||
|
||||
|
|
@ -1131,7 +1170,7 @@ yang_find_datanode(yang_stmt *yn,
|
|||
if (ys->ys_argument && strcmp(argument, ys->ys_argument) == 0)
|
||||
ysmatch = ys;
|
||||
if (ysmatch)
|
||||
goto match; // maybe break?
|
||||
goto done; // maybe break?
|
||||
}
|
||||
}
|
||||
/* Special case: if not match and yang node is module or submodule, extend
|
||||
|
|
@ -1150,7 +1189,7 @@ yang_find_datanode(yang_stmt *yn,
|
|||
}
|
||||
}
|
||||
}
|
||||
match:
|
||||
done:
|
||||
return ysmatch;
|
||||
}
|
||||
|
||||
|
|
@ -2265,14 +2304,12 @@ bound_add(yang_stmt *ys,
|
|||
char *reason = NULL;
|
||||
int ret = 1;
|
||||
|
||||
if ((cv = cvec_add(ys->ys_cvec, cvtype)) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cvec_add");
|
||||
if (ys == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "ys is NULL");
|
||||
goto done;
|
||||
}
|
||||
if (cv_name_set(cv, name) == NULL){
|
||||
clicon_err(OE_YANG, errno, "cv_name_set(%s)", name);
|
||||
if ((cv = yang_cvec_add(ys, cvtype, name)) == NULL)
|
||||
goto done;
|
||||
}
|
||||
if (cvtype == CGV_DEC64)
|
||||
cv_dec64_n_set(cv, fraction_digits);
|
||||
if (strcmp(val, "min") == 0)
|
||||
|
|
@ -2480,7 +2517,6 @@ ys_populate_identity(clicon_handle h,
|
|||
int retval = -1;
|
||||
yang_stmt *yc = NULL;
|
||||
yang_stmt *ybaseid;
|
||||
cg_var *cv;
|
||||
char *baseid;
|
||||
char *prefix = NULL;
|
||||
char *id = NULL;
|
||||
|
|
@ -2526,17 +2562,10 @@ ys_populate_identity(clicon_handle h,
|
|||
if (cvec_find(idrefvec, idref) != NULL)
|
||||
continue;
|
||||
/* Add derived id to ybaseid */
|
||||
if ((cv = cv_new(CGV_STRING)) == NULL){
|
||||
if (yang_cvec_add(ybaseid, CGV_STRING, idref) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cv_new");
|
||||
goto done;
|
||||
}
|
||||
/* add prefix */
|
||||
cv_name_set(cv, idref);
|
||||
cvec_append_var(idrefvec, cv); /* cv copied */
|
||||
if (cv){
|
||||
cv_free(cv);
|
||||
cv = NULL;
|
||||
}
|
||||
/* Transitive to the root */
|
||||
if (ys_populate_identity(h, ybaseid, idref) < 0)
|
||||
goto done;
|
||||
|
|
@ -2662,11 +2691,20 @@ ys_populate_unique(clicon_handle h,
|
|||
}
|
||||
|
||||
/*! Populate unknown node with extension
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] ys The yang statement (unknown) to populate.
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* RFC 7950 Sec 7.19:
|
||||
* If no "argument" statement is present, the keyword expects no argument when
|
||||
* it is used.
|
||||
* A pointer to the unknwon node is stored in the cvec of the extension:
|
||||
* 1 n
|
||||
* yang-extension ext1 (cvec) ---> unknown of ext1
|
||||
* (yext) (ys)
|
||||
*
|
||||
* @note this breaks if yangs are freed
|
||||
* Note there is some complexity in different yang modules with unknown-statements.
|
||||
* y0) The location of the extension definition. E.g. extension autocli-op
|
||||
* y1) The location of the unknown-statement (ys). This is for example: cl:autocli-op hide.
|
||||
|
|
@ -2717,6 +2755,10 @@ ys_populate_unknown(clicon_handle h,
|
|||
clicon_debug(1, "plugin_extension() failed");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
#ifdef YANG_SCHEMA_MOUNT
|
||||
if (yang_schema_unknown(h, yext, ys) < 0)
|
||||
goto done;
|
||||
#endif
|
||||
/* Make extension callbacks that may alter yang structure
|
||||
* Note: this may be a "layering" violation: assuming plugins are loaded
|
||||
|
|
@ -2724,6 +2766,12 @@ ys_populate_unknown(clicon_handle h,
|
|||
*/
|
||||
if (clixon_plugin_extension_all(h, yext, ys) < 0)
|
||||
goto done;
|
||||
#if 1
|
||||
/* Add unknown to vector in extension */
|
||||
if ((cv = yang_cvec_add(yext, CGV_VOID, yang_argument_get(ys))) == NULL)
|
||||
goto done;
|
||||
cv_void_set(cv, ys);
|
||||
#endif
|
||||
retval = 0;
|
||||
done:
|
||||
if (prefix)
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ struct yang_stmt{
|
|||
Y_TYPE & identity: store all derived
|
||||
types as <module>:<id> list
|
||||
Y_UNIQUE: vector of descendant schema node ids
|
||||
Y_EXTENSION: vector of instantiated UNKNOWNSo
|
||||
*/
|
||||
yang_type_cache *ys_typecache; /* If ys_keyword==Y_TYPE, cache all typedef data except unions */
|
||||
char *ys_when_xpath; /* Special conditional for a "when"-associated augment/uses xpath */
|
||||
|
|
|
|||
|
|
@ -192,8 +192,8 @@ yang_modules_revision(clicon_handle h)
|
|||
* If also CLICON_MODULE_LIBRARY_RFC7895 is set, module-state is built according to RFC7895 instead
|
||||
* @see RFC8525
|
||||
*/
|
||||
static int
|
||||
yms_build(clicon_handle h,
|
||||
int
|
||||
yang_modules_state_build(clicon_handle h,
|
||||
yang_stmt *yspec,
|
||||
char *msid,
|
||||
int brief,
|
||||
|
|
@ -361,7 +361,7 @@ yang_modules_state_get(clicon_handle h,
|
|||
goto done;
|
||||
}
|
||||
/* Build a cb string: <modules-state>... */
|
||||
if (yms_build(h, yspec, msid, brief, cb) < 0)
|
||||
if (yang_modules_state_build(h, yspec, msid, brief, cb) < 0)
|
||||
goto done;
|
||||
/* Parse cb, x is on the form: <top><modules-state>...
|
||||
* Note, list is not sorted since it is state (should not be)
|
||||
|
|
@ -391,7 +391,6 @@ yang_modules_state_get(clicon_handle h,
|
|||
/* Remove everything that is not marked */
|
||||
if (xml_tree_prune_flagged_sub(x, XML_FLAG_MARK, 1, NULL) < 0)
|
||||
goto done;
|
||||
|
||||
if ((ret = netconf_trymerge(x, yspec, xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
|
|
|
|||
346
lib/src/clixon_yang_schema_mount.c
Normal file
346
lib/src/clixon_yang_schema_mount.c
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2023 Olof Hagsand
|
||||
|
||||
This file is part of CLIXON.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
the GNU General Public License Version 3 or later (the "GPL"),
|
||||
in which case the provisions of the GPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of the GPL, and not to allow others to
|
||||
use your version of this file under the terms of Apache License version 2,
|
||||
indicate your decision by deleting the provisions above and replace them with
|
||||
the notice and other provisions required by the GPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the Apache License version 2 or the GPL.
|
||||
|
||||
***** END LICENSE BLOCK *****
|
||||
|
||||
* RFC 8525 Yang schema mount support
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clixon */
|
||||
#include "clixon_queue.h"
|
||||
#include "clixon_hash.h"
|
||||
#include "clixon_handle.h"
|
||||
#include "clixon_err.h"
|
||||
#include "clixon_log.h"
|
||||
#include "clixon_yang.h"
|
||||
#include "clixon_xml.h"
|
||||
#include "clixon_xml_io.h"
|
||||
#include "clixon_yang_module.h"
|
||||
#include "clixon_netconf_lib.h"
|
||||
|
||||
#include "clixon_yang_schema_mount.h"
|
||||
|
||||
/*! Check if y is a RFC 8525 YANG schema mount
|
||||
*
|
||||
* Check if:
|
||||
* - y is CONTAINER or LIST, AND
|
||||
* - y has YANG schema mount "mount-point" as child element, AND
|
||||
* - the extension label matches y (see note below)
|
||||
* If so, then return 1
|
||||
* @param[in] y Yang statement
|
||||
* @retval 1 Yes, y is a RFC 8525 YANG mount-point
|
||||
* @retval 0 No, y is not
|
||||
* @retval -1 Error
|
||||
* @note That this may be a restriction on the usage of "label". The RFC is somewhat unclear.
|
||||
*/
|
||||
int
|
||||
yang_schema_mount_point(yang_stmt *y)
|
||||
{
|
||||
int retval = -1;
|
||||
enum rfc_6020 keyw;
|
||||
int exist = 0;
|
||||
char *value = NULL;
|
||||
|
||||
if (y == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "y is NULL");
|
||||
goto done;
|
||||
}
|
||||
keyw = yang_keyword_get(y);
|
||||
if (keyw != Y_CONTAINER && keyw != Y_LIST)
|
||||
goto fail;
|
||||
if (yang_extension_value(y, "mount-point", YANG_SCHEMA_MOUNT_NAMESPACE, &exist, &value) < 0)
|
||||
goto done;
|
||||
if (exist == 0)
|
||||
goto fail;
|
||||
if (value == NULL)
|
||||
goto fail;
|
||||
retval = 1;
|
||||
done:
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Find schema mounts - callback function for xml_apply
|
||||
*
|
||||
* @param[in] x XML node
|
||||
* @param[in] arg cvec, if match add node
|
||||
* @retval -1 Error, aborted at first error encounter, return -1 to end user
|
||||
* @retval 0 OK, continue
|
||||
* @retval 1 Abort, dont continue with others, return 1 to end user
|
||||
* @retval 2 Locally abort this subtree, continue with others
|
||||
*/
|
||||
static int
|
||||
find_schema_mounts(cxobj *x,
|
||||
void *arg)
|
||||
{
|
||||
int ret;
|
||||
yang_stmt *y;
|
||||
cvec *cvv = (cvec *)arg;
|
||||
cg_var *cv;
|
||||
|
||||
if ((y = xml_spec(x)) == NULL)
|
||||
return 2;
|
||||
if (yang_config(y) == 0)
|
||||
return 2;
|
||||
if ((ret = yang_schema_mount_point(y)) < 0)
|
||||
return -1;
|
||||
if (ret == 0)
|
||||
return 0;
|
||||
if ((cv = cvec_add(cvv, CGV_VOID)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cvec_add");
|
||||
return -1;
|
||||
}
|
||||
cv_void_set(cv, x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Find mount-points and return yang-library state
|
||||
*
|
||||
* Brute force: traverse whole XML, match all x that have ymount as yspec
|
||||
* Add yang-library state for all x
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[in] xpath XML Xpath
|
||||
* @param[in] nsc XML Namespace context for xpath
|
||||
* @param[in,out] xret Existing XML tree, merge x into this
|
||||
* @param[out] xerr XML error tree, if retval = 0
|
||||
* @retval 1 OK
|
||||
* @retval 0 Validation failed, error in xret
|
||||
* @retval -1 Error (fatal)
|
||||
*
|
||||
* RFC 8528 Section 3.4:
|
||||
* A schema for a mount point contained in a mounted module can be
|
||||
* specified by implementing the "ietf-yang-library" and
|
||||
* "ietf-yang-schema-mount" modules in the mounted schema and specifying
|
||||
* the schemas in exactly the same way as the top-level schema.
|
||||
* Alt: see snmp_yang2xml to get instances instead of brute force traverse of whole tree
|
||||
*/
|
||||
static int
|
||||
schema_mounts_yang_library(clicon_handle h,
|
||||
yang_stmt *yspec,
|
||||
char *xpath,
|
||||
cvec *nsc,
|
||||
cxobj **xret,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
cvec *cvv = NULL;
|
||||
cg_var *cv;
|
||||
cxobj *xmp; /* xml mount-point */
|
||||
cxobj *xylib = NULL; /* xml yang-lib */
|
||||
cbuf *cb = NULL;
|
||||
char *msid = "mount-point"; /* modules-set-id dummy */
|
||||
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clicon_err(OE_UNIX, 0, "clicon buffer");
|
||||
goto done;
|
||||
}
|
||||
if ((cvv = cvec_new(0)) == NULL){
|
||||
clicon_err(OE_UNIX, errno, "cvec_new");
|
||||
goto done;
|
||||
}
|
||||
if (xml_apply(*xret, CX_ELMNT, find_schema_mounts, cvv) < 0)
|
||||
goto done;
|
||||
cv = NULL;
|
||||
while ((cv = cvec_each(cvv, cv)) != NULL) {
|
||||
xmp = cv_void_get(cv);
|
||||
/* addsub here */
|
||||
cbuf_reset(cb);
|
||||
// XXX change yspec to mount-point
|
||||
/* Build a cb string: <modules-state>... */
|
||||
if (yang_modules_state_build(h, yspec, msid, 0, cb) < 0)
|
||||
goto done;
|
||||
/* Parse cb, x is on the form: <top><modules-state>...
|
||||
* Note, list is not sorted since it is state (should not be)
|
||||
*/
|
||||
if (clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &xylib, NULL) < 0){
|
||||
if (xret && netconf_operation_failed_xml(xret, "protocol", clicon_err_reason)< 0)
|
||||
goto done;
|
||||
goto fail;
|
||||
}
|
||||
if (xml_rootchild(xylib, 0, &xylib) < 0)
|
||||
goto done;
|
||||
if (xml_addsub(xmp, xylib) < 0)
|
||||
goto done;
|
||||
xylib = NULL;
|
||||
}
|
||||
retval = 1;
|
||||
done:
|
||||
if (cvv)
|
||||
cvec_free(cvv);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Get modules state according to RFC 8528
|
||||
*
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] yspec Yang spec
|
||||
* @param[in] xpath XML Xpath
|
||||
* @param[in] nsc XML Namespace context for xpath
|
||||
* @param[in,out] xret Existing XML tree, merge x into this
|
||||
* @param[out] xerr XML error tree, if retval = 0
|
||||
* @retval 1 OK
|
||||
* @retval 0 Validation failed, error in xret
|
||||
* @retval -1 Error (fatal)
|
||||
* @note Only "inline" specification of mounted schema supported, not "shared schema"
|
||||
*/
|
||||
int
|
||||
schema_mounts_state_get(clicon_handle h,
|
||||
yang_stmt *yspec,
|
||||
char *xpath,
|
||||
cvec *nsc,
|
||||
cxobj **xret,
|
||||
cxobj **xerr)
|
||||
{
|
||||
int retval = -1;
|
||||
cbuf *cb = NULL;
|
||||
int ret;
|
||||
yang_stmt *yext;
|
||||
yang_stmt *ymount;
|
||||
yang_stmt *ymodext;
|
||||
yang_stmt *ymod;
|
||||
cg_var *cv;
|
||||
cg_var *cv1;
|
||||
char *label;
|
||||
cvec *cvv;
|
||||
cxobj *x1 = NULL;
|
||||
|
||||
if ((ymodext = yang_find(yspec, Y_MODULE, "ietf-yang-schema-mount")) == NULL ||
|
||||
(yext = yang_find(ymodext, Y_EXTENSION, "mount-point")) == NULL){
|
||||
goto ok;
|
||||
// clicon_err(OE_YANG, 0, "yang schema mount-point extension not found");
|
||||
// goto done;
|
||||
}
|
||||
if ((cvv = yang_cvec_get(yext)) != NULL){
|
||||
if ((cb = cbuf_new()) ==NULL){
|
||||
clicon_err(OE_XML, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cb, "<schema-mounts xmlns=\"%s\">", YANG_SCHEMA_MOUNT_NAMESPACE); // XXX only if hit
|
||||
cv = NULL;
|
||||
while ((cv = cvec_each(cvv, cv)) != NULL){
|
||||
ymount = (yang_stmt*)cv_void_get(cv);
|
||||
ymod = ys_module(ymount);
|
||||
if ((cv1 = yang_cv_get(ymount)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "mount-point extension must have label");
|
||||
goto done;
|
||||
}
|
||||
label = cv_string_get(cv1);
|
||||
cprintf(cb, "<mount-point>");
|
||||
cprintf(cb, "<module>%s</module>", yang_argument_get(ymod));
|
||||
cprintf(cb, "<label>%s</label>", label);
|
||||
cprintf(cb, "<inline/>");
|
||||
cprintf(cb, "</mount-point>");
|
||||
}
|
||||
cprintf(cb, "</schema-mounts>");
|
||||
if ((ret = clixon_xml_parse_string(cbuf_get(cb), YB_MODULE, yspec, &x1, xerr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
if ((ret = netconf_trymerge(x1, yspec, xret)) < 0)
|
||||
goto done;
|
||||
if (ret == 0)
|
||||
goto fail;
|
||||
}
|
||||
/* Find mount-points and return yang-library state */
|
||||
if (0 && schema_mounts_yang_library(h, yspec, xpath, nsc, xret, xerr) < 0)
|
||||
goto done;
|
||||
ok:
|
||||
retval = 1;
|
||||
done:
|
||||
if (x1)
|
||||
xml_free(x1);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
fail:
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! Callback for yang schema mount-point extension
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] yext Yang node of extension
|
||||
* @param[in] ys Yang node of (unknown) statement belonging to extension
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
// XXX his may not even be necessary
|
||||
*/
|
||||
int
|
||||
yang_schema_unknown(clicon_handle h,
|
||||
yang_stmt *yext,
|
||||
yang_stmt *ys)
|
||||
{
|
||||
int retval = -1;
|
||||
char *extname;
|
||||
char *modname;
|
||||
yang_stmt *ymod;
|
||||
cg_var *cv;
|
||||
char *label;
|
||||
|
||||
ymod = ys_module(yext);
|
||||
modname = yang_argument_get(ymod);
|
||||
extname = yang_argument_get(yext);
|
||||
if (strcmp(modname, "ietf-yang-schema-mount") != 0 || strcmp(extname, "mount-point") != 0)
|
||||
goto ok;
|
||||
if ((cv = yang_cv_get(ys)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "mount-point extension must have label");
|
||||
goto done;
|
||||
}
|
||||
label = cv_string_get(cv);
|
||||
clicon_debug(1, "%s Enabled extension:%s:%s label:%s", __FUNCTION__, modname, extname, label);
|
||||
// XXX his may not even be necessary
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue