diff options
| author | Renato Westphal <renato@opensourcerouting.org> | 2017-12-07 17:31:48 -0200 |
|---|---|---|
| committer | Renato Westphal <renato@opensourcerouting.org> | 2018-10-27 16:16:12 -0200 |
| commit | 1c2facd12df7bc27758d7ea674b1e57e401fc234 (patch) | |
| tree | b82aeb06586c2c7b380420ddd484964af915aee1 /lib/yang_translator.c | |
| parent | 75082dafb5a929b99d89c8f55e6b2da3f4d90100 (diff) | |
lib: introduce new northbound API
Signed-off-by: Renato Westphal <renato@opensourcerouting.org>
Diffstat (limited to 'lib/yang_translator.c')
| -rw-r--r-- | lib/yang_translator.c | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/lib/yang_translator.c b/lib/yang_translator.c new file mode 100644 index 0000000000..27b92a0e6b --- /dev/null +++ b/lib/yang_translator.c @@ -0,0 +1,545 @@ +/* + * 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" + +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); +static void str_replace(char *o_string, const char *s_string, + const char *r_string); + +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(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 lys_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)); + str_replace(mapping->xpath_from_fmt, "KEY1", "%[^']"); + str_replace(mapping->xpath_from_fmt, "KEY2", "%[^']"); + str_replace(mapping->xpath_from_fmt, "KEY3", "%[^']"); + str_replace(mapping->xpath_from_fmt, "KEY4", "%[^']"); + str_replace(mapping->xpath_to_fmt, "KEY1", "%s"); + str_replace(mapping->xpath_to_fmt, "KEY2", "%s"); + str_replace(mapping->xpath_to_fmt, "KEY3", "%s"); + str_replace(mapping->xpath_to_fmt, "KEY4", "%s"); +} + +struct yang_translator *yang_translator_load(const char *path) +{ + struct yang_translator *translator; + struct yang_tmodule *tmodule; + const char *family; + struct lyd_node *dnode; + struct ly_set *set; + struct listnode *ln; + + /* Load module translator (JSON file). */ + dnode = lyd_parse_path(ly_translator_ctx, path, LYD_JSON, + LYD_OPT_CONFIG); + if (!dnode) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: lyd_parse_path() failed", __func__); + 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); + 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 = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD); + if (!translator->ly_ctx) { + flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); + goto error; + } + ly_ctx_set_searchdir(translator->ly_ctx, YANG_MODELS_PATH); + + /* Load modules and deviations. */ + set = lyd_find_path(dnode, "./module"); + assert(set); + for (size_t i = 0; i < set->number; i++) { + const char *module_name; + + tmodule = + XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule)); + + module_name = yang_dnode_get_string(set->set.d[i], "./name"); + tmodule->module = ly_ctx_load_module(translator->ly_ctx, + module_name, NULL); + if (!tmodule->module) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to load module: %s", __func__, + module_name); + ly_set_free(set); + goto error; + } + + module_name = + yang_dnode_get_string(set->set.d[i], "./deviations"); + tmodule->deviations = ly_ctx_load_module(translator->ly_ctx, + module_name, NULL); + if (!tmodule->deviations) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to load module: %s", __func__, + module_name); + ly_set_free(set); + goto error; + } + lys_set_disabled(tmodule->deviations); + + listnode_add(translator->modules, tmodule); + } + ly_set_free(set); + + /* Calculate the coverage. */ + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + tmodule->nodes_before_deviations = + yang_module_nodes_count(tmodule->module); + + lys_set_enabled(tmodule->deviations); + + tmodule->nodes_after_deviations = + yang_module_nodes_count(tmodule->module); + tmodule->coverage = ((double)tmodule->nodes_after_deviations + / (double)tmodule->nodes_before_deviations) + * 100; + } + + /* Load mappings. */ + set = lyd_find_path(dnode, "./module/mappings"); + assert(set); + for (size_t i = 0; i < set->number; i++) { + const char *xpath_custom, *xpath_native; + const struct lys_node *snode_custom, *snode_native; + + xpath_custom = yang_dnode_get_string(set->set.d[i], "./custom"); + snode_custom = ly_ctx_get_node(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); + goto error; + } + + xpath_native = yang_dnode_get_string(set->set.d[i], "./native"); + snode_native = + ly_ctx_get_node(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); + 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); + + /* 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); + + return NULL; +} + +static void yang_tmodule_delete(struct yang_tmodule *tmodule) +{ + XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule); +} + +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, NULL); + 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 lys_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 = ly_ctx_get_node(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, *next, *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); + + /* Iterate over all nodes from the data tree. */ + LY_TREE_FOR (*dnode, root) { + LY_TREE_DFS_BEGIN (root, next, 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. */ + ly_errno = 0; + if (!lyd_new_path(new, ly_ctx, xpath, + (void *)yang_dnode_get_string( + dnode_iter, NULL), + 0, LYD_PATH_OPT_UPDATE) + && ly_errno) { + flog_err(EC_LIB_LIBYANG, + "%s: lyd_new_path() failed", __func__); + goto error; + } + + next: + LY_TREE_DFS_END(root, next, 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; +} + +static void yang_translator_validate_cb(const struct lys_node *snode_custom, + void *arg1, void *arg2) +{ + struct yang_translator *translator = arg1; + unsigned int *errors = arg2; + struct yang_mapping_node *mapping; + const struct lys_node *snode_native; + const struct lys_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(translator, YANG_TRANSLATE_TO_NATIVE, + xpath); + if (!mapping) { + flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: missing mapping for \"%s\"", __func__, xpath); + *errors += 1; + return; + } + + snode_native = + ly_ctx_get_node(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->base != stype_native->base) { + flog_warn( + EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: YANG types are incompatible (xpath: \"%s\")", + __func__, xpath); + *errors += 1; + return; + } + + /* TODO: check if the value spaces are identical. */ + } +} + +/* + * 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; + unsigned int errors = 0; + + for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { + yang_module_snodes_iterate( + tmodule->module, yang_translator_validate_cb, + YANG_ITER_FILTER_NPCONTAINERS + | YANG_ITER_FILTER_LIST_KEYS + | YANG_ITER_FILTER_INPUT_OUTPUT, + translator, &errors); + } + + if (errors) + flog_warn( + EC_LIB_YANG_TRANSLATOR_LOAD, + "%s: failed to validate \"%s\" module translator: %u error(s)", + __func__, translator->family, errors); + + return errors; +} + +static void yang_module_nodes_count_cb(const struct lys_node *snode, void *arg1, + void *arg2) +{ + unsigned int *total = arg1; + + *total += 1; +} + +/* 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_module_snodes_iterate(module, yang_module_nodes_count_cb, + YANG_ITER_FILTER_NPCONTAINERS + | YANG_ITER_FILTER_LIST_KEYS + | YANG_ITER_FILTER_INPUT_OUTPUT, + &total, NULL); + + return total; +} + +/* TODO: rewrite this function. */ +static void str_replace(char *o_string, const char *s_string, + const char *r_string) +{ + char buffer[BUFSIZ]; + char *ch; + + ch = strstr(o_string, s_string); + if (!ch) + return; + + strncpy(buffer, o_string, ch - o_string); + buffer[ch - o_string] = 0; + + sprintf(buffer + (ch - o_string), "%s%s", r_string, + ch + strlen(s_string)); + + o_string[0] = 0; + strcpy(o_string, buffer); + return str_replace(o_string, s_string, r_string); +} + +void yang_translator_init(void) +{ + ly_translator_ctx = ly_ctx_new(NULL, LY_CTX_DISABLE_SEARCHDIR_CWD); + if (!ly_translator_ctx) { + flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); + exit(1); + } + ly_ctx_set_searchdir(ly_translator_ctx, YANG_MODELS_PATH); + + if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator", + 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, NULL); +} |
