diff --git a/iproute2 b/iproute2 index 6b79bc8..d9b886d 160000 --- a/iproute2 +++ b/iproute2 @@ -1 +1 @@ -Subproject commit 6b79bc819fefa18e2ff6e522d303ecb1b1c62cb4 +Subproject commit d9b886d745ada3b8481e041ceca579c6f3acbea3 diff --git a/src/lib/change_cmdgen.c b/src/lib/change_cmdgen.c index e199823..a86d2b5 100644 --- a/src/lib/change_cmdgen.c +++ b/src/lib/change_cmdgen.c @@ -18,22 +18,6 @@ #define CMD_LINE_SIZE 1024 -typedef enum { - // list extensions - CMD_START_EXT, - CMD_ADD_EXT, - CMD_DELETE_EXT, - CMD_UPDATE_EXT, - - // leaf extensions - ARG_NAME_EXT, - FLAG_EXT, - VALUE_ONLY_EXT, - VALUE_ONLY_ON_UPDATE_EXT - - -} extension_t; - char *yang_ext_map[] = { [CMD_START_EXT] = "cmd-start", [CMD_ADD_EXT] = "cmd-add", @@ -75,8 +59,29 @@ void free_argv(char **argv, int argc) free(argv); } -/* +void free_cmds_info(struct cmd_info **cmds_info) +{ + // Free the memory allocated for cmds + for (int i = 0; i < CMDS_ARRAY_SIZE; i++) { + if (cmds_info[i] != NULL) { + // Free the argv array for cmd_args[i] + for (int j = 0; j < cmds_info[i]->argc; j++) { + free(cmds_info[i]->argv[j]); + } + // Free the argv array itself + free(cmds_info[i]->argv); + // Free the cmd_args struct itself + free(cmds_info[i]); + } + } + // Free the cmds array itself + free(cmds_info); +} + +/** * if input is "iproute2-ip-link:dummy" it return "dummy" + * @param [in] input string to be stripped. + * @return the extracted string */ char *strip_yang_iden_prefix(const char *input) { @@ -110,7 +115,11 @@ char *strip_yang_iden_prefix(const char *input) } } - +/** + * get the yang operation from the lyd_node (create, replace, delete) + * @param [in]dnode lyd_node + * @return oper_t the time of the operation found on this lyd_node. + */ oper_t get_operation(const struct lyd_node *dnode) { struct lyd_meta *next; @@ -130,8 +139,11 @@ oper_t get_operation(const struct lyd_node *dnode) return UNKNOWN_OPR; } -/* - * return true if the node has ipr2cgen:cmd-start extension. +/** + * check if this node is cmd-start node. + * example in yang the list nexthop is where the cmd-start ext added. + * @param [in] dnode lyd_node + * @return 1 if ext found. */ int is_startcmd_node(struct lyd_node *dnode) { @@ -147,10 +159,12 @@ int is_startcmd_node(struct lyd_node *dnode) return 0; } -/* - * this function search if the provided ex_t exist in the node, - * if not exist it return EXIT_FAILURE, if exist return EXIT_SUCCESS, - * if value is not NULL, the extension value will be dup to value. +/** + * get the extension from lyd_node, + * @param [in] ex_t extension_t to be captured from lyd_node. + * @param [in] dnode lyd_node where to search for provided ext. + * @param [out] value extension value if found. can be null. + * @return EXIT_SUCCESS if ext found, EXIT_FAILURE if not found */ int get_extension(extension_t ex_t, const struct lyd_node *dnode, char **value) { @@ -170,8 +184,11 @@ int get_extension(extension_t ex_t, const struct lyd_node *dnode, char **value) return EXIT_FAILURE; } -/* - * this function will convert the command line string to argc, argv +/** + * parse the command line and convert it to argc, argv + * @param [in] command command line string "ip link add ..." + * @param [out] argc parsed argv count + * @param [out] argv parsed argv */ void parse_command(const char *command, int *argc, char ***argv) { @@ -207,8 +224,15 @@ void parse_command(const char *command, int *argc, char ***argv) free(cmd_copy); } -void add_command(char *cmd_line, struct cmd_info **cmds, int *cmd_idx, - char **oper2cmd_prefix, const struct lyd_node *start_dnode) +/** + * this function take cmd_line string, convert it to argc, argv and then append it to the cmds array + * @param [in] cmd_line command line string "ip link add name lo0 type dummy" + * @param [in] start_dnode start_cmd node to be added to the cmd_info struct. + * @param [in,out] cmds array of cmd_info. + * @param [in,out] cmd_idx current index of cmds. + */ +void add_command(char *cmd_line,const struct lyd_node *start_dnode, + struct cmd_info **cmds, int *cmd_idx) { int argc; char **argv; @@ -237,20 +261,21 @@ struct cmd_info **lyd2cmds(const struct lyd_node *change_node) char *result; int cmd_idx = 0; char cmd_line[CMD_LINE_SIZE] = {0}; - struct cmd_info **cmds = malloc(CMDS_ARRAY_SIZE * sizeof(struct cmd_info *)); - // Set all elements of the array to NULL - for (int i = 0; i < CMDS_ARRAY_SIZE; i++) { - cmds[i] = NULL; - } lyd_print_mem(&result, change_node, LYD_XML, 0); printf("--%s", result); + struct cmd_info **cmds = malloc(CMDS_ARRAY_SIZE * sizeof(struct cmd_info *)); if (cmds == NULL) { fprintf(stderr, "Memory allocation failed\n"); return NULL; } + // Set all elements of the array to NULL + for (int i = 0; i < CMDS_ARRAY_SIZE; i++) { + cmds[i] = NULL; + } + // this will hold the add, update and delete cmd prefixes. char *oper2cmd_prefix[3] = {NULL}; @@ -274,68 +299,79 @@ struct cmd_info **lyd2cmds(const struct lyd_node *change_node) return NULL; } - oper_t op_val; struct lyd_node *next, *startcmd_node; LYD_TREE_DFS_BEGIN(change_node, next) { + LY_DATA_TYPE type; // if this is startcmd node (schema has ipr2cgen:cmd-start), then start building a new command if (is_startcmd_node(next)) { startcmd_node = next; // check if the cmd is not empty (first cmd) if (cmd_line[0] != 0) - add_command(cmd_line, cmds, &cmd_idx, oper2cmd_prefix, startcmd_node); + add_command(cmd_line, startcmd_node, cmds, &cmd_idx); + // prepare for new command op_val = get_operation(next); if (op_val == UNKNOWN_OPR) { - fprintf(stderr, "%s: unknown operation for startcmd node (%s) \n", + fprintf(stderr, "%s: unknown operation for startcmd node \"%s\" \n", __func__, next->schema->name); + free_cmds_info(cmds); return NULL; } strlcpy(cmd_line, oper2cmd_prefix[op_val], sizeof(cmd_line)); - } else if (next->schema->nodetype != LYS_CONTAINER) { - // not a startcmd, get key-value pair and apped to cmd_line - - if (next->schema->nodetype == LYS_LEAF) { - char *key=NULL, *value = NULL; - // if list operation is delete, get the keys only - if (op_val == DELETE_OPR && !lysc_is_key(next->schema)) - goto next_iter; - // if FLAG extension, add schema name to the cmd and go to next iter. - if (get_extension(FLAG_EXT, next, NULL) == EXIT_SUCCESS) { - if (!strcmp("true", lyd_get_value(next))) - value = strdup(next->schema->name); - goto next_iter; - } - // if value only, don't include the schema->name (key) in the command. - if (get_extension(VALUE_ONLY_EXT, next, NULL) == EXIT_SUCCESS) - goto next_iter; - else + } else if (next->schema->nodetype == LYS_LEAF) { + char *key = NULL, *value = NULL; + + // if list operation is delete, get the keys only + if (op_val == DELETE_OPR && !lysc_is_key(next->schema)) + goto next_iter; + + // if FLAG extension, add schema name to the cmd and go to next iter. + if (get_extension(FLAG_EXT, next, NULL) == EXIT_SUCCESS) { + if (!strcmp("true", lyd_get_value(next))) key = strdup(next->schema->name); - // set the value - // special case for identity leaf, where the module prefix might be added (e.g iproute-link:vti) - LY_DATA_TYPE type = ((struct lysc_node_leaf *) next->schema)->type->basetype; - if (type == LY_TYPE_IDENT) - value = strip_yang_iden_prefix(lyd_get_value(next)); - else - value = (char *) lyd_get_value(next); - next_iter: - if (key != NULL) { - strlcat(cmd_line, " ", sizeof(cmd_line)); - strlcat(cmd_line, key, sizeof(cmd_line)); - } - if (value != NULL){ - strlcat(cmd_line, " ", sizeof(cmd_line)); - strlcat(cmd_line, value, sizeof(cmd_line)); - } + goto next_iter; + } + // if value only extension, don't include the schema->name (key) in the command. + if (get_extension(VALUE_ONLY_EXT, next, NULL) == EXIT_SUCCESS) + goto next_iter; + + // if arg_name extension, key will be set to the arg-name value. + if (get_extension(ARG_NAME_EXT, next, &key) == EXIT_SUCCESS) { + if (key == NULL) { + fprintf(stderr, "%s: ipr2cgen:arg-name extension found but failed to " + "get the arg-name value for node \"%s\"\n", + __func__, next->schema->name); + free_cmds_info(cmds); + return NULL; + } + } else + key = strdup(next->schema->name); + + // set the value + // special case for identity leaf, where the module prefix might be added (e.g iproute-link:vti) + type = ((struct lysc_node_leaf *) next->schema)->type->basetype; + if (type == LY_TYPE_IDENT) + value = strip_yang_iden_prefix(lyd_get_value(next)); + else + value = (char *) lyd_get_value(next); + next_iter: + if (key != NULL) { + strlcat(cmd_line, " ", sizeof(cmd_line)); + strlcat(cmd_line, key, sizeof(cmd_line)); + } + if (value != NULL) { + strlcat(cmd_line, " ", sizeof(cmd_line)); + strlcat(cmd_line, value, sizeof(cmd_line)); } } LYD_TREE_DFS_END(change_node, next); } if (cmd_line[0] != 0) - add_command(cmd_line, cmds, &cmd_idx, oper2cmd_prefix, startcmd_node); + add_command(cmd_line,startcmd_node,cmds,&cmd_idx); return cmds; } diff --git a/src/lib/cmdgen.h b/src/lib/cmdgen.h index a35b2e6..bcca8c5 100644 --- a/src/lib/cmdgen.h +++ b/src/lib/cmdgen.h @@ -6,6 +6,11 @@ #define CMDS_ARRAY_SIZE 1024 +/** + * extension to extension name map. + */ +extern char *yang_ext_map[]; + /** * @brief data struct to store cmd operation. * @@ -17,6 +22,26 @@ typedef enum { UNKNOWN_OPR, } oper_t; +/** + * definition of yang's extension_t. + */ +typedef enum { + // list extensions + CMD_START_EXT, + // root container extensions + CMD_ADD_EXT, + CMD_DELETE_EXT, + CMD_UPDATE_EXT, + + // leaf extensions + ARG_NAME_EXT, + FLAG_EXT, + VALUE_ONLY_EXT, + VALUE_ONLY_ON_UPDATE_EXT + + +} extension_t; + /** * @brief data struct to store command information. */ @@ -46,12 +71,19 @@ struct cmd_info** lyd2cmds(const struct lyd_node *change_node); struct cmd_info* lyd2rollback_cmd(const struct lyd_node *change_node); /** - * @brief get the change operation from change lyd_node. - * - * - * @param[in] lyd_node change node. - * @return oper_t + * get the yang operation from the lyd_node (create, replace, delete) + * @param [in]dnode lyd_node + * @return oper_t the time of the operation found on this lyd_node. */ oper_t get_operation(const struct lyd_node *dnode); +/** + * get the extension from lyd_node, + * @param [in] ex_t extension_t to be captured from lyd_node. + * @param [in] dnode lyd_node where to search for provided ext. + * @param [out] value extension value if found. can be null. + * @return EXIT_SUCCESS if ext found, EXIT_FAILURE if not found + */ +int get_extension(extension_t ex_t, const struct lyd_node *dnode, char **value); + #endif// IPROUTE2_SYSREPO_CMDGEN_H \ No newline at end of file diff --git a/tests/cases/test_iplink.sh b/tests/cases/test_ip_link.sh similarity index 100% rename from tests/cases/test_iplink.sh rename to tests/cases/test_ip_link.sh diff --git a/tests/cases/test_ip_nexthop.sh b/tests/cases/test_ip_nexthop.sh new file mode 100755 index 0000000..ec7b229 --- /dev/null +++ b/tests/cases/test_ip_nexthop.sh @@ -0,0 +1,103 @@ +#!/bin/bash + +##################################################################### +# Testbed Script for Testing iproute2-sysrepo "ip nexthop" functionality +##################################################################### +# This script performs a series of tests on the iproute2-sysrepo +# functionality related to IP nexthop manipulation. It verifies the +# creation, deletion, and updating of IP nexthops by iproute2-sysrepo +# using sysrepocfg commands and checks if the operations are +# successful. +# +# Test Steps: +# 1. Test creating nexthop +# 2. Test updating nexthop +# 3. Test deleting nexthop +##################################################################### + +ret=0 +#################################################################### +# Test: Create IP nexthops +#################################################################### +echo "--------------------" +echo "[1] Test nexthop CREATE" +echo "---------------------" + +# Step 1: Add IP nexthops to RUNNING data store +sysrepocfg -d running --edit tests/cases/test_ip_nexthop_data.xml || ret=$? +# Check if sysrepocfg command failed +if [ -n "$ret" ] && [ "$ret" -ne 0 ]; then + echo "TEST-ERROR: failed to create nexthops in sysrepo datastore" + exit "$ret" +fi + +# Step 2: Check if IP 2 is created +if ip nexthop show id 1 >/dev/null 2>&1; then + echo "TEST-INFO: IP nexthop id 1 created successfully (OK)" +else + echo "TEST-ERROR: Failed to create IP nexthop 1 (FAIL)" + exit 1 +fi + +# Step 3: Check if IP testIf1 is created +if ip nexthop show id 2 >/dev/null 2>&1; then + echo "TEST-INFO: IP nexthop id 2 created successfully (OK)" +else + echo "TEST-ERROR: Failed to create IP nexthop 2 (FAIL)" + exit 1 +fi +sleep 0.2 +#################################################################### +# Test: Update IP nexthops +#################################################################### +# NOTE: update require missing extension replace-on-update. +#echo "--------------------" +#echo "[2] Test nexthop UPDATE" +#echo "---------------------" +# +#ip link add name nh_test type dummy +# +## Step 1: update via in sysrepo +#sysrepocfg -S '/iproute2-ip-nexthop:nexthops/nexthop[id="2"]/ipv4/address' --value 192.168.2.2 +# +## Step 2: Check if the IP for IP 2 is updated by iproute2-sysrepo +#current_via=$(ip nexthop show id 2 2>/dev/null | grep -oP '(?<=via )\d+.\d+.\d+.\d+' | head -n 1) +# +#if [ -z "$current_via" ]; then +# echo "TEST-ERROR: Failed to retrieve via for IP nexthop 2" +# exit 1 +#fi +# +#if [ "$current_via" = "192.168.2.2" ]; then +# echo "TEST-INFO: via for IP nexthop 2 updated successfully (OK)" +#else +# echo "TEST-ERROR: Failed to update via for IP nexthop 2 (FAIL)" +# exit 1 +#fi + +#################################################################### +# Test: Delete IP nexthops +#################################################################### +echo "--------------------" +echo "[3] Test nexthop DELETE" +echo "---------------------" + +# Step 1: delete data from sysrepo +sysrepocfg -C startup -d running -m iproute2-ip-nexthop || ret=$? +# Check if sysrepocfg command failed +if [ -n "$ret" ] && [ "$ret" -ne 0 ]; then + echo "TEST-ERROR: failed to delete IP nexthops from sysrepo" + exit "$ret" +fi + +# Step 2: check if interface deleted by iproute2-sysrepo +if ! ip nexthop show id 1 >/dev/null 2>&1 && ! ip nexthop show id 2 >/dev/null 2>&1; then + echo "TEST-INFO: IP nexthops 1 and 2 are deleted successfully (OK)" +else + echo "TEST-ERROR: Failed to delete IP nexthops 2 and 1 (FAIL)" + exit 1 +fi + + +# Exit with return value +exit $ret diff --git a/tests/cases/test_ip_nexthop_data.xml b/tests/cases/test_ip_nexthop_data.xml new file mode 100644 index 0000000..77cfd6b --- /dev/null +++ b/tests/cases/test_ip_nexthop_data.xml @@ -0,0 +1,14 @@ + + + 1 + true + false + + + 2 + +
192.168.1.1
+
+ true +
+
diff --git a/yang/iproute2-ip-link.yang b/yang/iproute2-ip-link.yang index db5b493..cf56e17 100644 --- a/yang/iproute2-ip-link.yang +++ b/yang/iproute2-ip-link.yang @@ -144,6 +144,7 @@ module iproute2-ip-link { description "link name"; } leaf device { + ipr2cgen:arg-name "link"; type string; description "specifies the physical device to act operate on"; } diff --git a/yang/iproute2-ip-nexthop.yang b/yang/iproute2-ip-nexthop.yang index 2301ed8..98ccdd2 100644 --- a/yang/iproute2-ip-nexthop.yang +++ b/yang/iproute2-ip-nexthop.yang @@ -54,12 +54,14 @@ module iproute2-ip-nexthop { container ipv4 { description "ipv4 nexthop"; leaf address { + ipr2cgen:arg-name "via"; description "ipv4 nexthop address"; type inet:ipv4-address; } } container ipv6 { description "ipv6 nexthop"; + ipr2cgen:arg-name "via"; leaf address { description "ipv6 nexthop address"; type inet:ipv6-address;