diff --git a/tests/yanglint/interactive/data_operational.test b/tests/yanglint/interactive/data_operational.test index c0c7b1cb06..4d1aa465e2 100644 --- a/tests/yanglint/interactive/data_operational.test +++ b/tests/yanglint/interactive/data_operational.test @@ -1,7 +1,7 @@ source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/interactive/ly.tcl" : "ly.tcl"}] set ddir "$::env(TESTS_DIR)/data" -set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types" +set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification/Extensions input data types" test data_operational_twice {it is not allowed to specify more than one --operational parameter} { -setup $ly_setup -cleanup $ly_cleanup -body { diff --git a/tests/yanglint/non-interactive/data_operational.test b/tests/yanglint/non-interactive/data_operational.test index 82e861e029..b023b1f4d3 100644 --- a/tests/yanglint/non-interactive/data_operational.test +++ b/tests/yanglint/non-interactive/data_operational.test @@ -2,7 +2,7 @@ source [expr {[info exists ::env(TESTS_DIR)] ? "$env(TESTS_DIR)/non-interactive/ set mdir "$::env(YANG_MODULES_DIR)" set ddir "$::env(TESTS_DIR)/data" -set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types" +set err1 "Operational datastore takes effect only with RPCs/Actions/Replies/Notification/Extensions input data types" test data_operational_twice {it is not allowed to specify more than one --operational parameter} { ly_cmd_err "-t notif -O $ddir/modconfig.xml -O $ddir/modleaf.xml" "cannot be set multiple times" diff --git a/tools/lint/cmd.c b/tools/lint/cmd.c index 8b2ec5d8f2..1f1e791f5b 100644 --- a/tools/lint/cmd.c +++ b/tools/lint/cmd.c @@ -70,7 +70,7 @@ COMMAND commands[] = { }, { "data", cmd_data_opt, cmd_data_dep, cmd_data_store, cmd_data_process, cmd_data_help, NULL, - "Load, validate and optionally print instance data", "d:ef:F:hmo:O:R:r:nt:x:" + "Load, validate and optionally print instance data", "d:ef:F:hmo:O:R:r:nt:x:k:" }, { "list", cmd_list_opt, cmd_list_dep, cmd_list_exec, NULL, cmd_list_help, NULL, diff --git a/tools/lint/cmd.h b/tools/lint/cmd.h index e9ee5b4920..4bb9085f20 100644 --- a/tools/lint/cmd.h +++ b/tools/lint/cmd.h @@ -381,6 +381,15 @@ int cmd_debug_dep(struct yl_opt *yo, int posc); */ int cmd_debug_store(struct ly_ctx **ctx, struct yl_opt *yo, const char *posv); +/** + * @brief Store the values: mod_name, name, argument of extension. + * + * @param[in] extension_id String in format "::". + * @param[in,out] yo Options for yanglint. + * @return 0 on success. + */ +int parse_ext_string(const char *extension_id, struct yl_opt *yo); + /** * @brief Set debug logging. * diff --git a/tools/lint/cmd_data.c b/tools/lint/cmd_data.c index 44fb237d06..1ca43cc3f7 100644 --- a/tools/lint/cmd_data.c +++ b/tools/lint/cmd_data.c @@ -3,6 +3,7 @@ * @author Michal Vasko * @author Radek Krejci * @author Adam Piecek + * @author Juraj Budai * @brief 'data' command of the libyang's yanglint tool. * * Copyright (c) 2015-2023 CESNET, z.s.p.o. @@ -29,6 +30,7 @@ #include "libyang.h" #include "common.h" +#include "compat.h" #include "yl_opt.h" static void @@ -68,7 +70,9 @@ cmd_data_help_type(void) " element without ).\n" " nc-notif - Similar to 'notif' but expect and check also the NETCONF\n" " envelope with element and its\n" - " sibling as the actual notification.\n"); + " sibling as the actual notification.\n" + " ext - Validates extension data based on loaded YANG modules.\n" + " Need to be used with -k parameter.\n"); } static void @@ -139,7 +143,10 @@ cmd_data_help(void) " existence is also checked in these operational data.\n" " -R FILE, --reply-rpc=FILE\n" " Provide source RPC for parsing of the 'nc-reply' TYPE. The FILE\n" - " is supposed to contain the source 'nc-rpc' operation of the reply.\n"); + " is supposed to contain the source 'nc-rpc' operation of the reply.\n" + " -k, --ext-inst \n" + " Name of extension instance in format:\n" + " ::\n"); cmd_data_help_format(); cmd_data_help_in_format(); printf(" -o OUTFILE, --output=OUTFILE\n" @@ -166,6 +173,7 @@ cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) {"not-strict", no_argument, NULL, 'n'}, {"type", required_argument, NULL, 't'}, {"xpath", required_argument, NULL, 'x'}, + {"ext-inst", required_argument, NULL, 'k'}, {NULL, 0, NULL, 0} }; @@ -255,6 +263,12 @@ cmd_data_opt(struct yl_opt *yo, const char *cmdline, char ***posv, int *posc) return 1; } break; + case 'k': /* --ext-inst */ + if (parse_ext_string(optarg, yo)) { + YLMSG_E("Invalid name of extension instance."); + return 1; + } + break; case 'h': /* --help */ cmd_data_help(); @@ -307,8 +321,8 @@ cmd_data_dep(struct yl_opt *yo, int posc) return 1; } - if (yo->data_operational.path && !yo->data_type) { - YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notification input data types."); + if (yo->data_operational.path && (!yo->data_type && !yo->data_ext)) { + YLMSG_W("Operational datastore takes effect only with RPCs/Actions/Replies/Notification/Extensions input data types."); yo->data_operational.path = NULL; } @@ -346,6 +360,23 @@ cmd_data_dep(struct yl_opt *yo, int posc) } } + if (yo->data_ext && !yo->mod_name) { + if (yo->interactive) { + YLMSG_E("When using '-i' the '-k' parameter need to be also set."); + } else { + YLMSG_E("When using '-t ext' the '-k' parameter need to be also set."); + } + return 1; + } + if (!yo->data_ext && yo->mod_name) { + if (yo->interactive) { + YLMSG_E("When using '-k' parameter the '-i' need to be also set."); + } else { + YLMSG_E("When using '-k' parameter the '-t ext' need to be also set."); + } + return 1; + } + return 0; } @@ -417,6 +448,35 @@ evaluate_xpath(const struct lyd_node *tree, const char *xpath) return 0; } +int +parse_ext_string(const char *extension_instance, struct yl_opt *yo) +{ + const char *start = extension_instance; + char *end; + + end = strchr(start, ':'); + if (!end) { + return -1; + } + yo->mod_name = strndup(start, end - start); + start = end + 1; + + end = strchr(start, ':'); + if (!end) { + return -1; + } + yo->name = strndup(start, end - start); + start = end + 1; + + if (*start == '\0') { + return -1; + } + + yo->argument = strdup(start); + + return 0; +} + /** * @brief Checking that a parent data node exists in the datastore for the nested-notification and action. * @@ -472,95 +532,191 @@ check_operation_parent(struct lyd_node *op, struct lyd_node *oper_tree, struct c } /** - * @brief Process the input data files - parse, validate and print according to provided options. + * @brief Iterate trough modules to find extension instance * * @param[in] ctx libyang context with schema. + * @param[in] yo context for yanglint. + * @return 0 on success. + */ +static int +find_extension(struct ly_ctx *ctx, struct yl_opt *yo) +{ + struct lys_module *module; + uint32_t idx = 0; + LY_ARRAY_COUNT_TYPE i; + + while ((module = ly_ctx_get_module_iter(ctx, &idx))) { + if (!strcmp(module->name, yo->mod_name)) { + break; + } + } + + if (!module) { + YLMSG_E("Cannot find the \"%s\" name in yang modules.", yo->name); + return 1; + } + + /* get the extension from module that user is looking for */ + LY_ARRAY_FOR(module->compiled->exts, i) { + if (!strcmp(module->compiled->exts[i].def->name, yo->name) && + !strcmp(module->compiled->exts[i].argument, yo->argument)) { + yo->ext = &module->compiled->exts[i]; + return 0; + } + } + return 1; +} + +/** + * @brief Parses input data based on its type and returns the corresponding data tree. + * @param[in] ctx libyang context with schema. * @param[in] type The type of data in the input files. - * @param[in] merge Flag if the data should be merged before validation. - * @param[in] out_format Data format for printing. - * @param[in] out The output handler for printing. + * @param[in] input_f Data input file. * @param[in] parse_options Parser options. * @param[in] validate_options Validation options. - * @param[in] print_options Printer options. - * @param[in] operational Optional operational datastore file information for the case of an extended validation of - * operation(s). + * @param[out] tree Pointer to the top-level data tree parsed from the input. + * @param[out] op Pointer to the specific operation node. * @param[in] reply_rpc Source RPC operation file information for parsing NETCONF rpc-reply. - * @param[in] inputs Set of file informations of input data files. - * @param[in] xpaths The set of XPaths to be evaluated on the processed data tree, basic information about the resulting set - * is printed. Alternative to data printing. * @return LY_ERR value. */ static LY_ERR -process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT out_format, - struct ly_out *out, uint32_t parse_options, uint32_t validate_options, uint32_t print_options, - struct cmdline_file *operational, struct cmdline_file *reply_rpc, struct ly_set *inputs, - struct ly_set *xpaths) +parse_input_by_type(struct ly_ctx *ctx, enum lyd_type type, struct cmdline_file *input_f, + uint32_t parse_options, uint32_t validate_options, struct lyd_node **tree, + struct lyd_node **op, struct cmdline_file *reply_rpc) { - LY_ERR ret = LY_SUCCESS; - struct lyd_node *tree = NULL, *op = NULL, *envp = NULL, *merged_tree = NULL, *oper_tree = NULL; - const char *xpath; - struct ly_set *set = NULL; - /* additional operational datastore */ - if (operational && operational->in) { - ret = lyd_parse_data(ctx, NULL, operational->in, operational->format, LYD_PARSE_ONLY, 0, &oper_tree); + LY_ERR ret = LY_SUCCESS; + struct lyd_node *envp = NULL; + + switch (type) { + case LYD_TYPE_DATA_YANG: + ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, parse_options, validate_options, tree); + break; + case LYD_TYPE_RPC_YANG: + case LYD_TYPE_REPLY_YANG: + case LYD_TYPE_NOTIF_YANG: + ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, tree, op); + break; + case LYD_TYPE_RPC_NETCONF: + case LYD_TYPE_NOTIF_NETCONF: + ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &envp, op); + + /* adjust pointers */ + for (*tree = *op; lyd_parent(*tree); *tree = lyd_parent(*tree)) {} + break; + case LYD_TYPE_REPLY_NETCONF: + /* parse source RPC operation */ + assert(reply_rpc && reply_rpc->in); + ret = lyd_parse_op(ctx, NULL, reply_rpc->in, reply_rpc->format, LYD_TYPE_RPC_NETCONF, &envp, op); if (ret) { - YLMSG_E("Failed to parse operational datastore file \"%s\".", operational->path); + YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".", reply_rpc->path); goto cleanup; } + + /* adjust pointers */ + for (*tree = *op; lyd_parent(*tree); *tree = lyd_parent(*tree)) {} + + /* free input */ + lyd_free_siblings(lyd_child(*op)); + + /* we do not care */ + lyd_free_all(envp); + envp = NULL; + + ret = lyd_parse_op(ctx, *op, input_f->in, input_f->format, type, &envp, NULL); + break; + default: + YLMSG_E("Internal error (%s:%d).", __FILE__, __LINE__); + goto cleanup; } - for (uint32_t u = 0; u < inputs->count; ++u) { - struct cmdline_file *input_f = (struct cmdline_file *)inputs->objs[u]; + cleanup: + lyd_free_all(envp); + envp = NULL; + return ret; +} - switch (type) { - case LYD_TYPE_DATA_YANG: - ret = lyd_parse_data(ctx, NULL, input_f->in, input_f->format, parse_options, validate_options, &tree); - break; - case LYD_TYPE_RPC_YANG: - case LYD_TYPE_REPLY_YANG: - case LYD_TYPE_NOTIF_YANG: - ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &tree, &op); - break; - case LYD_TYPE_RPC_NETCONF: - case LYD_TYPE_NOTIF_NETCONF: - ret = lyd_parse_op(ctx, NULL, input_f->in, input_f->format, type, &envp, &op); +/** + * @brief Parses and validates data for a specific YANG extension instance. + * + * @param[in] ctx libyang context with schema. + * @param[in] yo context for yanglint. + * @param[in] input_f Data input file. + * @param[out] tree Extension data tree. + * @param[in] oper_tree operational data tree. + * @return LY_ERR value. + */ +static LY_ERR +parse_extension_instance(struct ly_ctx *ctx, struct yl_opt *yo, struct cmdline_file *input_f, struct lyd_node **tree, struct lyd_node *oper_tree) +{ - /* adjust pointers */ - for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} - break; - case LYD_TYPE_REPLY_NETCONF: - /* parse source RPC operation */ - assert(reply_rpc && reply_rpc->in); - ret = lyd_parse_op(ctx, NULL, reply_rpc->in, reply_rpc->format, LYD_TYPE_RPC_NETCONF, &envp, &op); - if (ret) { - YLMSG_E("Failed to parse source NETCONF RPC operation file \"%s\".", reply_rpc->path); - goto cleanup; - } + LY_ERR ret = LY_SUCCESS; - /* adjust pointers */ - for (tree = op; lyd_parent(tree); tree = lyd_parent(tree)) {} + if (find_extension(ctx, yo)) { + YLMSG_E("Extension '%s:%s:%s' not found in module.", yo->mod_name, yo->name, yo->argument); + return LY_ENOTFOUND; + } - /* free input */ - lyd_free_siblings(lyd_child(op)); + if ((ret = lyd_parse_ext_data(yo->ext, NULL, input_f->in, input_f->format, LYD_PARSE_ONLY, 0, tree))) { + YLMSG_E("Parsing of extension data failed.") + return ret; + } - /* we do not care */ - lyd_free_all(envp); - envp = NULL; + if (!(*tree)) { + YLMSG_E("Nothing to validate in the extension input data."); + return LY_EDENIED; + } - ret = lyd_parse_op(ctx, op, input_f->in, input_f->format, type, &envp, NULL); - break; - default: - YLMSG_E("Internal error (%s:%d).", __FILE__, __LINE__); + if ((*tree)->next) { + YLMSG_E("Yanglint does not support more than one top-level node in extension data."); + return LY_EDENIED; + } + + /* Operational data is present */ + if (oper_tree) { + lyd_insert_sibling(*tree, oper_tree, &oper_tree); + ret = lyd_validate_all(tree, ctx, yo->data_validate_options, NULL); + lyd_unlink_tree(*tree); + } else { + ret = lyd_validate_all(tree, ctx, yo->data_validate_options, NULL); + } + + yo->data_ext = 0; + return ret; +} + +int +cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo) +{ + LY_ERR ret = LY_SUCCESS; + struct lyd_node *tree = NULL, *op = NULL, *merged_tree = NULL, *oper_tree = NULL; + const char *xpath; + struct ly_set *set = NULL; + + /* additional operational datastore */ + if (yo->data_operational.in) { + ret = lyd_parse_data(ctx, NULL, yo->data_operational.in, yo->data_operational.format, LYD_PARSE_ONLY, 0, &oper_tree); + if (ret) { + YLMSG_E("Failed to parse operational datastore file \"%s\".", yo->data_operational.path); goto cleanup; } + } + + for (uint32_t u = 0; u < yo->data_inputs.count; ++u) { + struct cmdline_file *input_f = (struct cmdline_file *)yo->data_inputs.objs[u]; + + if (yo->data_ext) { + ret = parse_extension_instance(ctx, yo, input_f, &tree, oper_tree); + } else { + ret = parse_input_by_type(ctx, yo->data_type, input_f, yo->data_parse_options, yo->data_validate_options, &tree, &op, &yo->reply_rpc); + } if (ret) { YLMSG_E("Failed to parse input data file \"%s\".", input_f->path); goto cleanup; } - if (merge) { + if (yo->data_merge) { /* merge the data so far parsed for later validation and print */ if (!merged_tree) { merged_tree = tree; @@ -572,29 +728,29 @@ process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT o } } tree = NULL; - } else if (out_format) { + } else if (yo->data_out_format) { /* print */ - switch (type) { + switch (yo->data_type) { case LYD_TYPE_DATA_YANG: - lyd_print_all(out, tree, out_format, print_options); + lyd_print_all(yo->out, tree, yo->data_out_format, yo->data_print_options); break; case LYD_TYPE_RPC_YANG: case LYD_TYPE_REPLY_YANG: case LYD_TYPE_NOTIF_YANG: case LYD_TYPE_RPC_NETCONF: case LYD_TYPE_NOTIF_NETCONF: - lyd_print_tree(out, tree, out_format, print_options); + lyd_print_tree(yo->out, tree, yo->data_out_format, yo->data_print_options); break; case LYD_TYPE_REPLY_NETCONF: /* just the output */ - lyd_print_tree(out, lyd_child(tree), out_format, print_options); + lyd_print_tree(yo->out, lyd_child(tree), yo->data_out_format, yo->data_print_options); break; default: assert(0); } } else { /* validation of the RPC/Action/reply/Notification with the operational datastore, if any */ - switch (type) { + switch (yo->data_type) { case LYD_TYPE_DATA_YANG: /* already validated */ break; @@ -614,16 +770,16 @@ process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT o assert(0); } if (ret) { - if (operational->path) { + if (yo->data_operational.path) { YLMSG_E("Failed to validate input data file \"%s\" with operational datastore \"%s\".", - input_f->path, operational->path); + input_f->path, yo->data_operational.path); } else { YLMSG_E("Failed to validate input data file \"%s\".", input_f->path); } goto cleanup; } - if ((ret = check_operation_parent(op, oper_tree, operational))) { + if ((ret = check_operation_parent(op, oper_tree, &yo->data_operational))) { goto cleanup; } } @@ -631,25 +787,23 @@ process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT o /* next iter */ lyd_free_all(tree); tree = NULL; - lyd_free_all(envp); - envp = NULL; } - if (merge) { + if (yo->data_merge) { /* validate the merged result */ - ret = lyd_validate_all(&merged_tree, ctx, validate_options, NULL); + ret = lyd_validate_all(&merged_tree, ctx, yo->data_validate_options, NULL); if (ret) { YLMSG_E("Merged data are not valid."); goto cleanup; } - if (out_format) { + if (yo->data_out_format) { /* and print it */ - lyd_print_all(out, merged_tree, out_format, print_options); + lyd_print_all(yo->out, merged_tree, yo->data_out_format, yo->data_print_options); } - for (uint32_t u = 0; xpaths && (u < xpaths->count); ++u) { - xpath = (const char *)xpaths->objs[u]; + for (uint32_t u = 0; u < yo->data_xpath.count; ++u) { + xpath = (const char *)yo->data_xpath.objs[u]; ly_set_free(set, NULL); ret = lys_find_xpath(ctx, NULL, xpath, LYS_FIND_NO_MATCH_ERROR, &set); if (ret || !set->count) { @@ -665,22 +819,12 @@ process_data(struct ly_ctx *ctx, enum lyd_type type, uint8_t merge, LYD_FORMAT o cleanup: lyd_free_all(tree); - lyd_free_all(envp); lyd_free_all(merged_tree); lyd_free_all(oper_tree); ly_set_free(set, NULL); - return ret; -} - -int -cmd_data_process(struct ly_ctx *ctx, struct yl_opt *yo) -{ - /* parse, validate and print data */ - if (process_data(ctx, yo->data_type, yo->data_merge, yo->data_out_format, yo->out, yo->data_parse_options, - yo->data_validate_options, yo->data_print_options, &yo->data_operational, &yo->reply_rpc, - &yo->data_inputs, &yo->data_xpath)) { + if (ret) { return 1; + } else { + return 0; } - - return 0; } diff --git a/tools/lint/examples/README.md b/tools/lint/examples/README.md index 93d3c2a699..d240018241 100644 --- a/tools/lint/examples/README.md +++ b/tools/lint/examples/README.md @@ -18,6 +18,7 @@ Available commands: load Load a new schema from the searchdirs print Print a module data Load, validate and optionally print instance data + ext Validate extension data list List all the loaded modules feature Print all features of module(s) with their state searchpath Print/set the search path(s) for schemas @@ -534,3 +535,22 @@ $ yanglint -f json -t config -p . -Y sm-context-main.xml -x sm-context-extension } } ``` + +## Validating extension data + +This command sequence uses `yanglint` to load YANG modules and validate a file (`ext-data.xml`) that contains YANG extension, an instance of a `yang-errors` structure defined by `ietf-restconf`. However, because this extension data references operational data, the validation process also requires that corresponding operational data. + +Preparation: + +``` +> clear +> add ietf-restconf@2017-01-26.yang +> add example-jukebox.yang +> data -t ext --ext-inst ietf-restconf:yang-data:yang-errors -O operational-data.xml ext-data.xml +``` + +Output: + +No output is printed, it means that the data is valid. + + diff --git a/tools/lint/examples/example-jukebox.yang b/tools/lint/examples/example-jukebox.yang new file mode 100644 index 0000000000..8e1df0414f --- /dev/null +++ b/tools/lint/examples/example-jukebox.yang @@ -0,0 +1,239 @@ +module example-jukebox { + + namespace "http://example.com/ns/example-jukebox"; + prefix "jbox"; + + organization "Example, Inc."; + contact "support at example.com"; + description "Example Jukebox Data Model Module"; + revision "2016-08-15" { + description "Initial version."; + reference "example.com document 1-4673"; + } + + identity genre { + description "Base for all genre types"; + } + + // abbreviated list of genre classifications + identity alternative { + base genre; + description "Alternative music"; + } + identity blues { + base genre; + description "Blues music"; + } + identity country { + base genre; + description "Country music"; + } + identity jazz { + base genre; + description "Jazz music"; + } + identity pop { + base genre; + description "Pop music"; + } + identity rock { + base genre; + description "Rock music"; + } + + container jukebox { + presence + "An empty container indicates that the jukebox + service is available"; + + description + "Represents a jukebox resource, with a library, playlists, + and a play operation."; + + container library { + + description "Represents the jukebox library resource."; + + list artist { + key name; + + description + "Represents one artist resource within the + jukebox library resource."; + + leaf name { + type string { + length "1 .. max"; + } + description "The name of the artist."; + } + + list album { + key name; + + description + "Represents one album resource within one + artist resource, within the jukebox library."; + + leaf name { + type string { + length "1 .. max"; + } + description "The name of the album."; + } + + leaf genre { + type identityref { base genre; } + description + "The genre identifying the type of music on + the album."; + } + + leaf year { + type uint16 { + range "1900 .. max"; + } + description "The year the album was released"; + } + + container admin { + description + "Administrative information for the album."; + + leaf label { + type string; + description "The label that released the album."; + } + leaf catalogue-number { + type string; + description "The album's catalogue number."; + } + } + + list song { + key name; + + description + "Represents one song resource within one + album resource, within the jukebox library."; + + leaf name { + type string { + length "1 .. max"; + } + description "The name of the song"; + } + leaf location { + type string; + mandatory true; + description + "The file location string of the + media file for the song"; + } + leaf format { + type string; + description + "An identifier string for the media type + for the file associated with the + 'location' leaf for this entry."; + } + leaf length { + type uint32; + units "seconds"; + description + "The duration of this song in seconds."; + } + } // end list 'song' + } // end list 'album' + } // end list 'artist' + + leaf artist-count { + type uint32; + units "songs"; + config false; + description "Number of artists in the library"; + } + leaf album-count { + type uint32; + units "albums"; + config false; + description "Number of albums in the library"; + } + leaf song-count { + type uint32; + units "songs"; + config false; + description "Number of songs in the library"; + } + } // end library + + list playlist { + key name; + + description + "Example configuration data resource"; + + leaf name { + type string; + description + "The name of the playlist."; + } + leaf description { + type string; + description + "A comment describing the playlist."; + } + list song { + key index; + ordered-by user; + + description + "Example nested configuration data resource"; + + leaf index { // not really needed + type uint32; + description + "An arbitrary integer index for this playlist song."; + } + leaf id { + type instance-identifier; + mandatory true; + description + "Song identifier. Must identify an instance of + /jukebox/library/artist/album/song/name."; + } + } + } + + container player { + description + "Represents the jukebox player resource."; + + leaf gap { + type decimal64 { + fraction-digits 1; + range "0.0 .. 2.0"; + } + units "tenths of seconds"; + description "Time gap between each song"; + } + } + } + + rpc play { + description "Control function for the jukebox player"; + input { + leaf playlist { + type string; + mandatory true; + description "playlist name"; + } + leaf song-number { + type uint32; + mandatory true; + description "Song number in playlist to play"; + } + } + } +} + diff --git a/tools/lint/examples/ext-data.xml b/tools/lint/examples/ext-data.xml new file mode 100644 index 0000000000..513b5b30f6 --- /dev/null +++ b/tools/lint/examples/ext-data.xml @@ -0,0 +1,8 @@ + + + protocol + data-exists + /jbox:jukebox/jbox:library + Data already exists; cannot create new resource + + diff --git a/tools/lint/examples/ietf-restconf@2017-01-26.yang b/tools/lint/examples/ietf-restconf@2017-01-26.yang new file mode 100644 index 0000000000..b47455b816 --- /dev/null +++ b/tools/lint/examples/ietf-restconf@2017-01-26.yang @@ -0,0 +1,278 @@ +module ietf-restconf { + yang-version 1.1; + namespace "urn:ietf:params:xml:ns:yang:ietf-restconf"; + prefix "rc"; + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: + WG List: + + Author: Andy Bierman + + + Author: Martin Bjorklund + + + Author: Kent Watsen + "; + + description + "This module contains conceptual YANG specifications + for basic RESTCONF media type definitions used in + RESTCONF protocol messages. + + Note that the YANG definitions within this module do not + represent configuration data of any kind. + The 'restconf-media-type' YANG extension statement + provides a normative syntax for XML and JSON + message-encoding purposes. + + Copyright (c) 2017 IETF Trust and the persons identified as + authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8040; see + the RFC itself for full legal notices."; + + revision 2017-01-26 { + description + "Initial revision."; + reference + "RFC 8040: RESTCONF Protocol."; + } + + extension yang-data { + argument name { + yin-element true; + } + description + "This extension is used to specify a YANG data template that + represents conceptual data defined in YANG. It is + intended to describe hierarchical data independent of + protocol context or specific message-encoding format. + Data definition statements within a yang-data extension + specify the generic syntax for the specific YANG data + template, whose name is the argument of the 'yang-data' + extension statement. + + Note that this extension does not define a media type. + A specification using this extension MUST specify the + message-encoding rules, including the content media type. + + The mandatory 'name' parameter value identifies the YANG + data template that is being defined. It contains the + template name. + + This extension is ignored unless it appears as a top-level + statement. It MUST contain data definition statements + that result in exactly one container data node definition. + An instance of a YANG data template can thus be translated + into an XML instance document, whose top-level element + corresponds to the top-level container. + The module name and namespace values for the YANG module using + the extension statement are assigned to instance document data + conforming to the data definition statements within + this extension. + + The substatements of this extension MUST follow the + 'data-def-stmt' rule in the YANG ABNF. + + The XPath document root is the extension statement itself, + such that the child nodes of the document root are + represented by the data-def-stmt substatements within + this extension. This conceptual document is the context + for the following YANG statements: + + - must-stmt + - when-stmt + - path-stmt + - min-elements-stmt + - max-elements-stmt + - mandatory-stmt + - unique-stmt + - ordered-by + - instance-identifier data type + + The following data-def-stmt substatements are constrained + when used within a 'yang-data' extension statement. + + - The list-stmt is not required to have a key-stmt defined. + - The if-feature-stmt is ignored if present. + - The config-stmt is ignored if present. + - The available identity values for any 'identityref' + leaf or leaf-list nodes are limited to the module + containing this extension statement and the modules + imported into that module. + "; + } + + rc:yang-data yang-errors { + uses errors; + } + + rc:yang-data yang-api { + uses restconf; + } + + grouping errors { + description + "A grouping that contains a YANG container + representing the syntax and semantics of a + YANG Patch error report within a response message."; + + container errors { + description + "Represents an error report returned by the server if + a request results in an error."; + + list error { + description + "An entry containing information about one + specific error that occurred while processing + a RESTCONF request."; + reference + "RFC 6241, Section 4.3."; + + leaf error-type { + type enumeration { + enum transport { + description + "The transport layer."; + } + enum rpc { + description + "The rpc or notification layer."; + } + enum protocol { + description + "The protocol operation layer."; + } + enum application { + description + "The server application layer."; + } + } + mandatory true; + description + "The protocol layer where the error occurred."; + } + + leaf error-tag { + type string; + mandatory true; + description + "The enumerated error-tag."; + } + + leaf error-app-tag { + type string; + description + "The application-specific error-tag."; + } + + leaf error-path { + type instance-identifier; + description + "The YANG instance identifier associated + with the error node."; + } + + leaf error-message { + type string; + description + "A message describing the error."; + } + + anydata error-info { + description + "This anydata value MUST represent a container with + zero or more data nodes representing additional + error information."; + } + } + } + } + + grouping restconf { + description + "Conceptual grouping representing the RESTCONF + root resource."; + + container restconf { + description + "Conceptual container representing the RESTCONF + root resource."; + + container data { + description + "Container representing the datastore resource. + Represents the conceptual root of all state data + and configuration data supported by the server. + The child nodes of this container can be any data + resources that are defined as top-level data nodes + from the YANG modules advertised by the server in + the 'ietf-yang-library' module."; + } + + container operations { + description + "Container for all operation resources. + + Each resource is represented as an empty leaf with the + name of the RPC operation from the YANG 'rpc' statement. + + For example, the 'system-restart' RPC operation defined + in the 'ietf-system' module would be represented as + an empty leaf in the 'ietf-system' namespace. This is + a conceptual leaf and will not actually be found in + the module: + + module ietf-system { + leaf system-reset { + type empty; + } + } + + To invoke the 'system-restart' RPC operation: + + POST /restconf/operations/ietf-system:system-restart + + To discover the RPC operations supported by the server: + + GET /restconf/operations + + In XML, the YANG module namespace identifies the module: + + + + In JSON, the YANG module name identifies the module: + + { 'ietf-system:system-restart' : [null] } + "; + } + leaf yang-library-version { + type string { + pattern '\d{4}-\d{2}-\d{2}'; + } + config false; + mandatory true; + description + "Identifies the revision date of the 'ietf-yang-library' + module that is implemented by this RESTCONF server. + Indicates the year, month, and day in YYYY-MM-DD + numeric format."; + } + } + } + +} diff --git a/tools/lint/examples/operational-data.xml b/tools/lint/examples/operational-data.xml new file mode 100644 index 0000000000..34b5d283bf --- /dev/null +++ b/tools/lint/examples/operational-data.xml @@ -0,0 +1,18 @@ + + + + The Beatles + + Abbey Road + jbox:rock + 1969 + + Come Together + /music/beatles/cometogether.mp3 + mp3 + 259 + + + + + diff --git a/tools/lint/main_ni.c b/tools/lint/main_ni.c index 8c51e32abc..a8933d5694 100644 --- a/tools/lint/main_ni.c +++ b/tools/lint/main_ni.c @@ -154,7 +154,9 @@ help(int shortout) " notif - Notification instance of a YANG notification.\n" " nc-notif - Similar to 'notif' but expect and check also the NETCONF\n" " envelope with element and its\n" - " sibling as the actual notification.\n\n"); + " sibling as the actual notification.\n" + " ext - Validates extension data based on loaded YANG modules.\n" + " Need to be used with -k parameter.\n\n"); printf(" -d MODE, --default=MODE\n" " Print data with default values, according to the MODE\n" @@ -215,6 +217,10 @@ help(int shortout) printf(" -J, --json-null\n" " Allow usage of JSON empty values ('null') within input data\n\n"); + printf(" -k, --ext-inst\n" + " Name of extension instance in format: ::.\n" + " Need to be used with -t ext parameter.\n\n"); + printf(" -G GROUPS, --debug=GROUPS\n" #ifndef NDEBUG " Enable printing of specific debugging message group\n" @@ -499,7 +505,7 @@ fill_context(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx) yo->line_length = 0; opterr = 0; - while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:XJx:G:", options, &opt_index)) != -1) { + while ((opt = getopt_long(argc, argv, "hvVQf:I:p:DF:iP:qs:neE:t:d:lL:o:O:R:myY:XJx:G:k:", options, &opt_index)) != -1) { switch (opt) { case 'h': /* --help */ help(0); @@ -688,7 +694,12 @@ fill_context(int argc, char *argv[], struct yl_opt *yo, struct ly_ctx **ctx) return -1; } break; - + case 'k': /* --ext-inst */ + if (parse_ext_string(optarg, yo)) { + YLMSG_E("Invalid name of extension instance."); + return -1; + } + break; default: YLMSG_E("Invalid option or missing argument: -%c.", optopt); return -1; diff --git a/tools/lint/yl_opt.c b/tools/lint/yl_opt.c index 7cd855f457..c9432810e0 100644 --- a/tools/lint/yl_opt.c +++ b/tools/lint/yl_opt.c @@ -86,6 +86,11 @@ yl_opt_erase(struct yl_opt *yo) /* context */ free(yo->searchpaths); + /*extension instance string*/ + free(yo->mod_name); + free(yo->name); + free(yo->argument); + /* --reply-rpc */ ly_in_free(yo->reply_rpc.in, 1); @@ -193,6 +198,8 @@ yl_opt_update_data_type(const char *arg, struct yl_opt *yo) yo->data_type = LYD_TYPE_NOTIF_NETCONF; } else if (!strcasecmp(arg, "data")) { /* default option */ + } else if (!strcasecmp(arg, "ext")) { + yo->data_ext = 1; } else { return 1; } diff --git a/tools/lint/yl_opt.h b/tools/lint/yl_opt.h index d66ae4de91..50e4b752ae 100644 --- a/tools/lint/yl_opt.h +++ b/tools/lint/yl_opt.h @@ -112,10 +112,17 @@ struct yl_opt { */ /* various options based on --type option */ enum lyd_type data_type; + ly_bool data_ext; uint32_t data_parse_options; uint32_t data_validate_options; uint32_t data_print_options; + /* unique identifier of extension instance*/ + char *mod_name; + char *name; + char *argument; + struct lysc_ext_instance *ext; + /* flag for --merge option */ uint8_t data_merge;