diff options
Diffstat (limited to 'lib/yang_translator.c')
-rw-r--r-- | lib/yang_translator.c | 547 |
1 files changed, 547 insertions, 0 deletions
diff --git a/lib/yang_translator.c b/lib/yang_translator.c new file mode 100644 index 0000000..a2f6e9d --- /dev/null +++ b/lib/yang_translator.c @@ -0,0 +1,547 @@ +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> + +#include "log.h" +#include "lib_errors.h" +#include "hash.h" +#include "yang.h" +#include "yang_translator.h" +#include "frrstr.h" + +DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR, "YANG Translator"); +DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MODULE, "YANG Translator Module"); +DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MAPPING, "YANG Translator Mapping"); + +/* Generate the yang_translators tree. */ +static inline int yang_translator_compare(const struct yang_translator *a, + const struct yang_translator *b) +{ + return strcmp(a->family, b->family); +} +RB_GENERATE(yang_translators, yang_translator, entry, yang_translator_compare) + +struct yang_translators yang_translators = RB_INITIALIZER(&yang_translators); + +/* Separate libyang context for the translator module. */ +static struct ly_ctx *ly_translator_ctx; + +static unsigned int +yang_translator_validate(struct yang_translator *translator); +static unsigned int yang_module_nodes_count(const struct lys_module *module); + +struct yang_mapping_node { + char xpath_from_canonical[XPATH_MAXLEN]; + char xpath_from_fmt[XPATH_MAXLEN]; + char xpath_to_fmt[XPATH_MAXLEN]; +}; + +static bool yang_mapping_hash_cmp(const void *value1, const void *value2) +{ + const struct yang_mapping_node *c1 = value1; + const struct yang_mapping_node *c2 = value2; + + return strmatch(c1->xpath_from_canonical, c2->xpath_from_canonical); +} + +static unsigned int yang_mapping_hash_key(const void *value) +{ + return string_hash_make(value); +} + +static void *yang_mapping_hash_alloc(void *p) +{ + struct yang_mapping_node *new, *key = p; + + new = XCALLOC(MTYPE_YANG_TRANSLATOR_MAPPING, sizeof(*new)); + strlcpy(new->xpath_from_canonical, key->xpath_from_canonical, + sizeof(new->xpath_from_canonical)); + + return new; +} + +static void yang_mapping_hash_free(void *arg) +{ + XFREE(MTYPE_YANG_TRANSLATOR_MAPPING, arg); +} + +static struct yang_mapping_node * +yang_mapping_lookup(const struct yang_translator *translator, int dir, + const char *xpath) +{ + struct yang_mapping_node s; + + strlcpy(s.xpath_from_canonical, xpath, sizeof(s.xpath_from_canonical)); + return hash_lookup(translator->mappings[dir], &s); +} + +static void yang_mapping_add(struct yang_translator *translator, int dir, + const struct lysc_node *snode, + const char *xpath_from_fmt, + const char *xpath_to_fmt) +{ + struct yang_mapping_node *mapping, s; + + yang_snode_get_path(snode, YANG_PATH_DATA, s.xpath_from_canonical, + sizeof(s.xpath_from_canonical)); + mapping = hash_get(translator->mappings[dir], &s, + yang_mapping_hash_alloc); + strlcpy(mapping->xpath_from_fmt, xpath_from_fmt, + sizeof(mapping->xpath_from_fmt)); + strlcpy(mapping->xpath_to_fmt, xpath_to_fmt, + sizeof(mapping->xpath_to_fmt)); + + const char *keys[] = {"KEY1", "KEY2", "KEY3", "KEY4"}; + char *xpfmt; + + for (unsigned int i = 0; i < array_size(keys); i++) { + xpfmt = frrstr_replace(mapping->xpath_from_fmt, keys[i], + "%[^']"); + strlcpy(mapping->xpath_from_fmt, xpfmt, + sizeof(mapping->xpath_from_fmt)); + XFREE(MTYPE_TMP, xpfmt); + } + + for (unsigned int i = 0; i < array_size(keys); i++) { + xpfmt = frrstr_replace(mapping->xpath_to_fmt, keys[i], "%s"); + strlcpy(mapping->xpath_to_fmt, xpfmt, + sizeof(mapping->xpath_to_fmt)); + XFREE(MTYPE_TMP, xpfmt); + } +} + +static void yang_tmodule_delete(struct yang_tmodule *tmodule) +{ + XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule); +} + +struct yang_translator *yang_translator_load(const char *path) +{ + struct yang_translator *translator; + struct yang_tmodule *tmodule = NULL; + const char *family; + struct lyd_node *dnode; + struct ly_set *set; + struct listnode *ln; + LY_ERR err; + + /* Load module translator (JSON file). */ + err = lyd_parse_data_path(ly_translator_ctx, path, LYD_JSON, + LYD_PARSE_NO_STATE, LYD_VALIDATE_NO_STATE, + &dnode); + if (err) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: lyd_parse_path() failed: %d", __func__, err); + return NULL; + } + dnode = yang_dnode_get(dnode, + "/frr-module-translator:frr-module-translator"); + /* + * libyang guarantees the "frr-module-translator" top-level container is + * always present since it contains mandatory child nodes. + */ + assert(dnode); + + family = yang_dnode_get_string(dnode, "./family"); + translator = yang_translator_find(family); + if (translator != NULL) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: module translator \"%s\" is loaded already", + __func__, family); + yang_dnode_free(dnode); + return NULL; + } + + translator = XCALLOC(MTYPE_YANG_TRANSLATOR, sizeof(*translator)); + strlcpy(translator->family, family, sizeof(translator->family)); + translator->modules = list_new(); + for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++) + translator->mappings[i] = hash_create(yang_mapping_hash_key, + yang_mapping_hash_cmp, + "YANG translation table"); + RB_INSERT(yang_translators, &yang_translators, translator); + + /* Initialize the translator libyang context. */ + translator->ly_ctx = yang_ctx_new_setup(false, false); + if (!translator->ly_ctx) { + flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); + goto error; + } + + /* Load modules */ + if (lyd_find_xpath(dnode, "./module", &set) != LY_SUCCESS) + assert(0); /* XXX libyang2: old ly1 code asserted success */ + + for (size_t i = 0; i < set->count; i++) { + const char *module_name; + + tmodule = + XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule)); + + module_name = yang_dnode_get_string(set->dnodes[i], "./name"); + tmodule->module = ly_ctx_load_module(translator->ly_ctx, + module_name, NULL, NULL); + if (!tmodule->module) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to load module: %s", __func__, + module_name); + ly_set_free(set, NULL); + goto error; + } + } + + /* Count nodes in modules. */ + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + tmodule->nodes_before_deviations = + yang_module_nodes_count(tmodule->module); + } + + /* Load the deviations and count nodes again */ + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + const char *module_name = tmodule->module->name; + tmodule->deviations = ly_ctx_load_module( + translator->ly_ctx, module_name, NULL, NULL); + if (!tmodule->deviations) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to load module: %s", __func__, + module_name); + ly_set_free(set, NULL); + goto error; + } + + tmodule->nodes_after_deviations = + yang_module_nodes_count(tmodule->module); + } + ly_set_free(set, NULL); + + /* Calculate the coverage. */ + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + tmodule->coverage = ((double)tmodule->nodes_after_deviations + / (double)tmodule->nodes_before_deviations) + * 100; + } + + /* Load mappings. */ + if (lyd_find_xpath(dnode, "./module/mappings", &set) != LY_SUCCESS) + assert(0); /* XXX libyang2: old ly1 code asserted success */ + for (size_t i = 0; i < set->count; i++) { + const char *xpath_custom, *xpath_native; + const struct lysc_node *snode_custom, *snode_native; + + xpath_custom = + yang_dnode_get_string(set->dnodes[i], "./custom"); + + snode_custom = lys_find_path(translator->ly_ctx, NULL, + xpath_custom, 0); + if (!snode_custom) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: unknown data path: %s", __func__, + xpath_custom); + ly_set_free(set, NULL); + goto error; + } + + xpath_native = + yang_dnode_get_string(set->dnodes[i], "./native"); + snode_native = + lys_find_path(ly_native_ctx, NULL, xpath_native, 0); + if (!snode_native) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: unknown data path: %s", __func__, + xpath_native); + ly_set_free(set, NULL); + goto error; + } + + yang_mapping_add(translator, YANG_TRANSLATE_TO_NATIVE, + snode_custom, xpath_custom, xpath_native); + yang_mapping_add(translator, YANG_TRANSLATE_FROM_NATIVE, + snode_native, xpath_native, xpath_custom); + } + ly_set_free(set, NULL); + + /* Validate mappings. */ + if (yang_translator_validate(translator) != 0) + goto error; + + yang_dnode_free(dnode); + + return translator; + +error: + yang_dnode_free(dnode); + yang_translator_unload(translator); + yang_tmodule_delete(tmodule); + + return NULL; +} + +void yang_translator_unload(struct yang_translator *translator) +{ + for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++) + hash_clean(translator->mappings[i], yang_mapping_hash_free); + translator->modules->del = (void (*)(void *))yang_tmodule_delete; + list_delete(&translator->modules); + ly_ctx_destroy(translator->ly_ctx); + RB_REMOVE(yang_translators, &yang_translators, translator); + XFREE(MTYPE_YANG_TRANSLATOR, translator); +} + +struct yang_translator *yang_translator_find(const char *family) +{ + struct yang_translator s; + + strlcpy(s.family, family, sizeof(s.family)); + return RB_FIND(yang_translators, &yang_translators, &s); +} + +enum yang_translate_result +yang_translate_xpath(const struct yang_translator *translator, int dir, + char *xpath, size_t xpath_len) +{ + struct ly_ctx *ly_ctx; + const struct lysc_node *snode; + struct yang_mapping_node *mapping; + char xpath_canonical[XPATH_MAXLEN]; + char keys[4][LIST_MAXKEYLEN]; + int n; + + if (dir == YANG_TRANSLATE_TO_NATIVE) + ly_ctx = translator->ly_ctx; + else + ly_ctx = ly_native_ctx; + + snode = lys_find_path(ly_ctx, NULL, xpath, 0); + if (!snode) { + flog_warn(EC_LIB_YANG_TRANSLATION_ERROR, + "%s: unknown data path: %s", __func__, xpath); + return YANG_TRANSLATE_FAILURE; + } + + yang_snode_get_path(snode, YANG_PATH_DATA, xpath_canonical, + sizeof(xpath_canonical)); + mapping = yang_mapping_lookup(translator, dir, xpath_canonical); + if (!mapping) + return YANG_TRANSLATE_NOTFOUND; + + n = sscanf(xpath, mapping->xpath_from_fmt, keys[0], keys[1], keys[2], + keys[3]); + if (n < 0) { + flog_warn(EC_LIB_YANG_TRANSLATION_ERROR, + "%s: sscanf() failed: %s", __func__, + safe_strerror(errno)); + return YANG_TRANSLATE_FAILURE; + } + + snprintf(xpath, xpath_len, mapping->xpath_to_fmt, keys[0], keys[1], + keys[2], keys[3]); + + return YANG_TRANSLATE_SUCCESS; +} + +int yang_translate_dnode(const struct yang_translator *translator, int dir, + struct lyd_node **dnode) +{ + struct ly_ctx *ly_ctx; + struct lyd_node *new; + struct lyd_node *root, *dnode_iter; + + /* Create new libyang data node to hold the translated data. */ + if (dir == YANG_TRANSLATE_TO_NATIVE) + ly_ctx = ly_native_ctx; + else + ly_ctx = translator->ly_ctx; + new = yang_dnode_new(ly_ctx, false); + + /* Iterate over all nodes from the data tree. */ + LY_LIST_FOR (*dnode, root) { + LYD_TREE_DFS_BEGIN (root, dnode_iter) { + char xpath[XPATH_MAXLEN]; + enum yang_translate_result ret; + + yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath)); + ret = yang_translate_xpath(translator, dir, xpath, + sizeof(xpath)); + switch (ret) { + case YANG_TRANSLATE_SUCCESS: + break; + case YANG_TRANSLATE_NOTFOUND: + goto next; + case YANG_TRANSLATE_FAILURE: + goto error; + } + + /* Create new node in the tree of translated data. */ + if (lyd_new_path(new, ly_ctx, xpath, + (void *)yang_dnode_get_string( + dnode_iter, NULL), + LYD_NEW_PATH_UPDATE, NULL)) { + flog_err(EC_LIB_LIBYANG, + "%s: lyd_new_path() failed", __func__); + goto error; + } + + next: + LYD_TREE_DFS_END(root, dnode_iter); + } + } + + /* Replace dnode by the new translated dnode. */ + yang_dnode_free(*dnode); + *dnode = new; + + return YANG_TRANSLATE_SUCCESS; + +error: + yang_dnode_free(new); + + return YANG_TRANSLATE_FAILURE; +} + +struct translator_validate_args { + struct yang_translator *translator; + unsigned int errors; +}; + +static int yang_translator_validate_cb(const struct lysc_node *snode_custom, + void *arg) +{ + struct translator_validate_args *args = arg; + struct yang_mapping_node *mapping; + const struct lysc_node *snode_native; + const struct lysc_type *stype_custom, *stype_native; + char xpath[XPATH_MAXLEN]; + + yang_snode_get_path(snode_custom, YANG_PATH_DATA, xpath, sizeof(xpath)); + mapping = yang_mapping_lookup(args->translator, + YANG_TRANSLATE_TO_NATIVE, xpath); + if (!mapping) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: missing mapping for \"%s\"", __func__, xpath); + args->errors += 1; + return YANG_ITER_CONTINUE; + } + + snode_native = + lys_find_path(ly_native_ctx, NULL, mapping->xpath_to_fmt, 0); + assert(snode_native); + + /* Check if the YANG types are compatible. */ + stype_custom = yang_snode_get_type(snode_custom); + stype_native = yang_snode_get_type(snode_native); + if (stype_custom && stype_native) { + if (stype_custom->basetype != stype_native->basetype) { + flog_warn( + EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: YANG types are incompatible (xpath: \"%s\")", + __func__, xpath); + args->errors += 1; + return YANG_ITER_CONTINUE; + } + + /* TODO: check if the value spaces are identical. */ + } + + return YANG_ITER_CONTINUE; +} + +/* + * Check if the modules from the translator have a mapping for all of their + * schema nodes (after loading the deviations). + */ +static unsigned int yang_translator_validate(struct yang_translator *translator) +{ + struct yang_tmodule *tmodule; + struct listnode *ln; + struct translator_validate_args args; + + args.translator = translator; + args.errors = 0; + + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + yang_snodes_iterate(tmodule->module, + yang_translator_validate_cb, + YANG_ITER_FILTER_NPCONTAINERS + | YANG_ITER_FILTER_LIST_KEYS + | YANG_ITER_FILTER_INPUT_OUTPUT, + &args); + } + + if (args.errors) + flog_warn( + EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to validate \"%s\" module translator: %u error(s)", + __func__, translator->family, args.errors); + + return args.errors; +} + +static int yang_module_nodes_count_cb(const struct lysc_node *snode, void *arg) +{ + unsigned int *total = arg; + + *total += 1; + + return YANG_ITER_CONTINUE; +} + +/* Calculate the number of nodes for the given module. */ +static unsigned int yang_module_nodes_count(const struct lys_module *module) +{ + unsigned int total = 0; + + yang_snodes_iterate(module, yang_module_nodes_count_cb, + YANG_ITER_FILTER_NPCONTAINERS + | YANG_ITER_FILTER_LIST_KEYS + | YANG_ITER_FILTER_INPUT_OUTPUT, + &total); + + return total; +} + +void yang_translator_init(void) +{ + ly_translator_ctx = yang_ctx_new_setup(true, false); + if (!ly_translator_ctx) { + flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); + exit(1); + } + + if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator", + NULL, NULL)) { + flog_err( + EC_LIB_YANG_MODULE_LOAD, + "%s: failed to load the \"frr-module-translator\" module", + __func__); + exit(1); + } +} + +void yang_translator_terminate(void) +{ + while (!RB_EMPTY(yang_translators, &yang_translators)) { + struct yang_translator *translator; + + translator = RB_ROOT(yang_translators, &yang_translators); + yang_translator_unload(translator); + } + + ly_ctx_destroy(ly_translator_ctx); +} |