]>
Commit | Line | Data |
---|---|---|
1c2facd1 RW |
1 | /* |
2 | * Copyright (C) 2018 NetDEF, Inc. | |
3 | * Renato Westphal | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the Free | |
7 | * Software Foundation; either version 2 of the License, or (at your option) | |
8 | * any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License along | |
16 | * with this program; see the file COPYING; if not, write to the Free Software | |
17 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
18 | */ | |
19 | ||
20 | #include <zebra.h> | |
21 | ||
22 | #include "log.h" | |
23 | #include "lib_errors.h" | |
24 | #include "hash.h" | |
25 | #include "yang.h" | |
26 | #include "yang_translator.h" | |
f9ce1142 | 27 | #include "frrstr.h" |
1c2facd1 RW |
28 | |
29 | DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR, "YANG Translator") | |
30 | DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MODULE, "YANG Translator Module") | |
31 | DEFINE_MTYPE_STATIC(LIB, YANG_TRANSLATOR_MAPPING, "YANG Translator Mapping") | |
32 | ||
33 | /* Generate the yang_translators tree. */ | |
34 | static inline int yang_translator_compare(const struct yang_translator *a, | |
35 | const struct yang_translator *b) | |
36 | { | |
37 | return strcmp(a->family, b->family); | |
38 | } | |
39 | RB_GENERATE(yang_translators, yang_translator, entry, yang_translator_compare) | |
40 | ||
41 | struct yang_translators yang_translators = RB_INITIALIZER(&yang_translators); | |
42 | ||
43 | /* Separate libyang context for the translator module. */ | |
44 | static struct ly_ctx *ly_translator_ctx; | |
45 | ||
46 | static unsigned int | |
47 | yang_translator_validate(struct yang_translator *translator); | |
48 | static unsigned int yang_module_nodes_count(const struct lys_module *module); | |
1c2facd1 RW |
49 | |
50 | struct yang_mapping_node { | |
51 | char xpath_from_canonical[XPATH_MAXLEN]; | |
52 | char xpath_from_fmt[XPATH_MAXLEN]; | |
53 | char xpath_to_fmt[XPATH_MAXLEN]; | |
54 | }; | |
55 | ||
56 | static bool yang_mapping_hash_cmp(const void *value1, const void *value2) | |
57 | { | |
58 | const struct yang_mapping_node *c1 = value1; | |
59 | const struct yang_mapping_node *c2 = value2; | |
60 | ||
61 | return strmatch(c1->xpath_from_canonical, c2->xpath_from_canonical); | |
62 | } | |
63 | ||
d8b87afe | 64 | static unsigned int yang_mapping_hash_key(const void *value) |
1c2facd1 RW |
65 | { |
66 | return string_hash_make(value); | |
67 | } | |
68 | ||
69 | static void *yang_mapping_hash_alloc(void *p) | |
70 | { | |
71 | struct yang_mapping_node *new, *key = p; | |
72 | ||
73 | new = XCALLOC(MTYPE_YANG_TRANSLATOR_MAPPING, sizeof(*new)); | |
74 | strlcpy(new->xpath_from_canonical, key->xpath_from_canonical, | |
75 | sizeof(new->xpath_from_canonical)); | |
76 | ||
77 | return new; | |
78 | } | |
79 | ||
80 | static void yang_mapping_hash_free(void *arg) | |
81 | { | |
82 | XFREE(MTYPE_YANG_TRANSLATOR_MAPPING, arg); | |
83 | } | |
84 | ||
85 | static struct yang_mapping_node * | |
86 | yang_mapping_lookup(const struct yang_translator *translator, int dir, | |
87 | const char *xpath) | |
88 | { | |
89 | struct yang_mapping_node s; | |
90 | ||
91 | strlcpy(s.xpath_from_canonical, xpath, sizeof(s.xpath_from_canonical)); | |
92 | return hash_lookup(translator->mappings[dir], &s); | |
93 | } | |
94 | ||
95 | static void yang_mapping_add(struct yang_translator *translator, int dir, | |
96 | const struct lys_node *snode, | |
97 | const char *xpath_from_fmt, | |
98 | const char *xpath_to_fmt) | |
99 | { | |
100 | struct yang_mapping_node *mapping, s; | |
101 | ||
102 | yang_snode_get_path(snode, YANG_PATH_DATA, s.xpath_from_canonical, | |
103 | sizeof(s.xpath_from_canonical)); | |
104 | mapping = hash_get(translator->mappings[dir], &s, | |
105 | yang_mapping_hash_alloc); | |
106 | strlcpy(mapping->xpath_from_fmt, xpath_from_fmt, | |
107 | sizeof(mapping->xpath_from_fmt)); | |
108 | strlcpy(mapping->xpath_to_fmt, xpath_to_fmt, | |
109 | sizeof(mapping->xpath_to_fmt)); | |
f9ce1142 QY |
110 | |
111 | const char *keys[] = {"KEY1", "KEY2", "KEY3", "KEY4"}; | |
112 | char *xpfmt; | |
113 | ||
114 | for (unsigned int i = 0; i < array_size(keys); i++) { | |
115 | xpfmt = frrstr_replace(mapping->xpath_from_fmt, keys[i], | |
116 | "%[^']"); | |
117 | strlcpy(mapping->xpath_from_fmt, xpfmt, | |
118 | sizeof(mapping->xpath_from_fmt)); | |
119 | XFREE(MTYPE_TMP, xpfmt); | |
120 | } | |
121 | ||
122 | for (unsigned int i = 0; i < array_size(keys); i++) { | |
123 | xpfmt = frrstr_replace(mapping->xpath_to_fmt, keys[i], "%s"); | |
124 | strlcpy(mapping->xpath_to_fmt, xpfmt, | |
125 | sizeof(mapping->xpath_to_fmt)); | |
126 | XFREE(MTYPE_TMP, xpfmt); | |
127 | } | |
1c2facd1 RW |
128 | } |
129 | ||
130 | struct yang_translator *yang_translator_load(const char *path) | |
131 | { | |
132 | struct yang_translator *translator; | |
133 | struct yang_tmodule *tmodule; | |
134 | const char *family; | |
135 | struct lyd_node *dnode; | |
136 | struct ly_set *set; | |
137 | struct listnode *ln; | |
138 | ||
139 | /* Load module translator (JSON file). */ | |
140 | dnode = lyd_parse_path(ly_translator_ctx, path, LYD_JSON, | |
141 | LYD_OPT_CONFIG); | |
142 | if (!dnode) { | |
143 | flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, | |
144 | "%s: lyd_parse_path() failed", __func__); | |
145 | return NULL; | |
146 | } | |
147 | dnode = yang_dnode_get(dnode, | |
148 | "/frr-module-translator:frr-module-translator"); | |
149 | /* | |
150 | * libyang guarantees the "frr-module-translator" top-level container is | |
151 | * always present since it contains mandatory child nodes. | |
152 | */ | |
153 | assert(dnode); | |
154 | ||
155 | family = yang_dnode_get_string(dnode, "./family"); | |
156 | translator = yang_translator_find(family); | |
157 | if (translator != NULL) { | |
158 | flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, | |
159 | "%s: module translator \"%s\" is loaded already", | |
160 | __func__, family); | |
161 | return NULL; | |
162 | } | |
163 | ||
164 | translator = XCALLOC(MTYPE_YANG_TRANSLATOR, sizeof(*translator)); | |
165 | strlcpy(translator->family, family, sizeof(translator->family)); | |
166 | translator->modules = list_new(); | |
167 | for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++) | |
168 | translator->mappings[i] = hash_create(yang_mapping_hash_key, | |
169 | yang_mapping_hash_cmp, | |
170 | "YANG translation table"); | |
171 | RB_INSERT(yang_translators, &yang_translators, translator); | |
172 | ||
173 | /* Initialize the translator libyang context. */ | |
591f57cf | 174 | translator->ly_ctx = yang_ctx_new_setup(); |
1c2facd1 RW |
175 | if (!translator->ly_ctx) { |
176 | flog_warn(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); | |
177 | goto error; | |
178 | } | |
1c2facd1 RW |
179 | |
180 | /* Load modules and deviations. */ | |
181 | set = lyd_find_path(dnode, "./module"); | |
182 | assert(set); | |
183 | for (size_t i = 0; i < set->number; i++) { | |
184 | const char *module_name; | |
185 | ||
186 | tmodule = | |
187 | XCALLOC(MTYPE_YANG_TRANSLATOR_MODULE, sizeof(*tmodule)); | |
188 | ||
189 | module_name = yang_dnode_get_string(set->set.d[i], "./name"); | |
190 | tmodule->module = ly_ctx_load_module(translator->ly_ctx, | |
191 | module_name, NULL); | |
192 | if (!tmodule->module) { | |
193 | flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, | |
194 | "%s: failed to load module: %s", __func__, | |
195 | module_name); | |
196 | ly_set_free(set); | |
197 | goto error; | |
198 | } | |
199 | ||
200 | module_name = | |
201 | yang_dnode_get_string(set->set.d[i], "./deviations"); | |
202 | tmodule->deviations = ly_ctx_load_module(translator->ly_ctx, | |
203 | module_name, NULL); | |
204 | if (!tmodule->deviations) { | |
205 | flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, | |
206 | "%s: failed to load module: %s", __func__, | |
207 | module_name); | |
208 | ly_set_free(set); | |
209 | goto error; | |
210 | } | |
211 | lys_set_disabled(tmodule->deviations); | |
212 | ||
213 | listnode_add(translator->modules, tmodule); | |
214 | } | |
215 | ly_set_free(set); | |
216 | ||
217 | /* Calculate the coverage. */ | |
218 | for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { | |
219 | tmodule->nodes_before_deviations = | |
220 | yang_module_nodes_count(tmodule->module); | |
221 | ||
222 | lys_set_enabled(tmodule->deviations); | |
223 | ||
224 | tmodule->nodes_after_deviations = | |
225 | yang_module_nodes_count(tmodule->module); | |
226 | tmodule->coverage = ((double)tmodule->nodes_after_deviations | |
227 | / (double)tmodule->nodes_before_deviations) | |
228 | * 100; | |
229 | } | |
230 | ||
231 | /* Load mappings. */ | |
232 | set = lyd_find_path(dnode, "./module/mappings"); | |
233 | assert(set); | |
234 | for (size_t i = 0; i < set->number; i++) { | |
235 | const char *xpath_custom, *xpath_native; | |
236 | const struct lys_node *snode_custom, *snode_native; | |
237 | ||
238 | xpath_custom = yang_dnode_get_string(set->set.d[i], "./custom"); | |
239 | snode_custom = ly_ctx_get_node(translator->ly_ctx, NULL, | |
240 | xpath_custom, 0); | |
241 | if (!snode_custom) { | |
242 | flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, | |
243 | "%s: unknown data path: %s", __func__, | |
244 | xpath_custom); | |
245 | ly_set_free(set); | |
246 | goto error; | |
247 | } | |
248 | ||
249 | xpath_native = yang_dnode_get_string(set->set.d[i], "./native"); | |
250 | snode_native = | |
251 | ly_ctx_get_node(ly_native_ctx, NULL, xpath_native, 0); | |
252 | if (!snode_native) { | |
253 | flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, | |
254 | "%s: unknown data path: %s", __func__, | |
255 | xpath_native); | |
256 | ly_set_free(set); | |
257 | goto error; | |
258 | } | |
259 | ||
260 | yang_mapping_add(translator, YANG_TRANSLATE_TO_NATIVE, | |
261 | snode_custom, xpath_custom, xpath_native); | |
262 | yang_mapping_add(translator, YANG_TRANSLATE_FROM_NATIVE, | |
263 | snode_native, xpath_native, xpath_custom); | |
264 | } | |
265 | ly_set_free(set); | |
266 | ||
267 | /* Validate mappings. */ | |
268 | if (yang_translator_validate(translator) != 0) | |
269 | goto error; | |
270 | ||
271 | yang_dnode_free(dnode); | |
272 | ||
273 | return translator; | |
274 | ||
275 | error: | |
276 | yang_dnode_free(dnode); | |
277 | yang_translator_unload(translator); | |
278 | ||
279 | return NULL; | |
280 | } | |
281 | ||
282 | static void yang_tmodule_delete(struct yang_tmodule *tmodule) | |
283 | { | |
284 | XFREE(MTYPE_YANG_TRANSLATOR_MODULE, tmodule); | |
285 | } | |
286 | ||
287 | void yang_translator_unload(struct yang_translator *translator) | |
288 | { | |
289 | for (size_t i = 0; i < YANG_TRANSLATE_MAX; i++) | |
290 | hash_clean(translator->mappings[i], yang_mapping_hash_free); | |
291 | translator->modules->del = (void (*)(void *))yang_tmodule_delete; | |
292 | list_delete(&translator->modules); | |
293 | ly_ctx_destroy(translator->ly_ctx, NULL); | |
294 | RB_REMOVE(yang_translators, &yang_translators, translator); | |
295 | XFREE(MTYPE_YANG_TRANSLATOR, translator); | |
296 | } | |
297 | ||
298 | struct yang_translator *yang_translator_find(const char *family) | |
299 | { | |
300 | struct yang_translator s; | |
301 | ||
302 | strlcpy(s.family, family, sizeof(s.family)); | |
303 | return RB_FIND(yang_translators, &yang_translators, &s); | |
304 | } | |
305 | ||
306 | enum yang_translate_result | |
307 | yang_translate_xpath(const struct yang_translator *translator, int dir, | |
308 | char *xpath, size_t xpath_len) | |
309 | { | |
310 | struct ly_ctx *ly_ctx; | |
311 | const struct lys_node *snode; | |
312 | struct yang_mapping_node *mapping; | |
313 | char xpath_canonical[XPATH_MAXLEN]; | |
314 | char keys[4][LIST_MAXKEYLEN]; | |
315 | int n; | |
316 | ||
317 | if (dir == YANG_TRANSLATE_TO_NATIVE) | |
318 | ly_ctx = translator->ly_ctx; | |
319 | else | |
320 | ly_ctx = ly_native_ctx; | |
321 | ||
322 | snode = ly_ctx_get_node(ly_ctx, NULL, xpath, 0); | |
323 | if (!snode) { | |
324 | flog_warn(EC_LIB_YANG_TRANSLATION_ERROR, | |
325 | "%s: unknown data path: %s", __func__, xpath); | |
326 | return YANG_TRANSLATE_FAILURE; | |
327 | } | |
328 | ||
329 | yang_snode_get_path(snode, YANG_PATH_DATA, xpath_canonical, | |
330 | sizeof(xpath_canonical)); | |
331 | mapping = yang_mapping_lookup(translator, dir, xpath_canonical); | |
332 | if (!mapping) | |
333 | return YANG_TRANSLATE_NOTFOUND; | |
334 | ||
335 | n = sscanf(xpath, mapping->xpath_from_fmt, keys[0], keys[1], keys[2], | |
336 | keys[3]); | |
337 | if (n < 0) { | |
338 | flog_warn(EC_LIB_YANG_TRANSLATION_ERROR, | |
339 | "%s: sscanf() failed: %s", __func__, | |
340 | safe_strerror(errno)); | |
341 | return YANG_TRANSLATE_FAILURE; | |
342 | } | |
343 | ||
344 | snprintf(xpath, xpath_len, mapping->xpath_to_fmt, keys[0], keys[1], | |
345 | keys[2], keys[3]); | |
346 | ||
347 | return YANG_TRANSLATE_SUCCESS; | |
348 | } | |
349 | ||
350 | int yang_translate_dnode(const struct yang_translator *translator, int dir, | |
351 | struct lyd_node **dnode) | |
352 | { | |
353 | struct ly_ctx *ly_ctx; | |
354 | struct lyd_node *new; | |
355 | struct lyd_node *root, *next, *dnode_iter; | |
356 | ||
357 | /* Create new libyang data node to hold the translated data. */ | |
358 | if (dir == YANG_TRANSLATE_TO_NATIVE) | |
359 | ly_ctx = ly_native_ctx; | |
360 | else | |
361 | ly_ctx = translator->ly_ctx; | |
5e02643a | 362 | new = yang_dnode_new(ly_ctx, false); |
1c2facd1 RW |
363 | |
364 | /* Iterate over all nodes from the data tree. */ | |
365 | LY_TREE_FOR (*dnode, root) { | |
366 | LY_TREE_DFS_BEGIN (root, next, dnode_iter) { | |
367 | char xpath[XPATH_MAXLEN]; | |
368 | enum yang_translate_result ret; | |
369 | ||
370 | yang_dnode_get_path(dnode_iter, xpath, sizeof(xpath)); | |
371 | ret = yang_translate_xpath(translator, dir, xpath, | |
372 | sizeof(xpath)); | |
373 | switch (ret) { | |
374 | case YANG_TRANSLATE_SUCCESS: | |
375 | break; | |
376 | case YANG_TRANSLATE_NOTFOUND: | |
377 | goto next; | |
378 | case YANG_TRANSLATE_FAILURE: | |
379 | goto error; | |
380 | } | |
381 | ||
382 | /* Create new node in the tree of translated data. */ | |
383 | ly_errno = 0; | |
384 | if (!lyd_new_path(new, ly_ctx, xpath, | |
385 | (void *)yang_dnode_get_string( | |
386 | dnode_iter, NULL), | |
387 | 0, LYD_PATH_OPT_UPDATE) | |
388 | && ly_errno) { | |
389 | flog_err(EC_LIB_LIBYANG, | |
390 | "%s: lyd_new_path() failed", __func__); | |
391 | goto error; | |
392 | } | |
393 | ||
394 | next: | |
395 | LY_TREE_DFS_END(root, next, dnode_iter); | |
396 | } | |
397 | } | |
398 | ||
399 | /* Replace dnode by the new translated dnode. */ | |
400 | yang_dnode_free(*dnode); | |
401 | *dnode = new; | |
402 | ||
403 | return YANG_TRANSLATE_SUCCESS; | |
404 | ||
405 | error: | |
406 | yang_dnode_free(new); | |
407 | ||
408 | return YANG_TRANSLATE_FAILURE; | |
409 | } | |
410 | ||
e0ccfad2 RW |
411 | struct translator_validate_args { |
412 | struct yang_translator *translator; | |
413 | unsigned int errors; | |
414 | }; | |
415 | ||
416 | static int yang_translator_validate_cb(const struct lys_node *snode_custom, | |
417 | void *arg) | |
1c2facd1 | 418 | { |
e0ccfad2 | 419 | struct translator_validate_args *args = arg; |
1c2facd1 RW |
420 | struct yang_mapping_node *mapping; |
421 | const struct lys_node *snode_native; | |
422 | const struct lys_type *stype_custom, *stype_native; | |
423 | char xpath[XPATH_MAXLEN]; | |
424 | ||
425 | yang_snode_get_path(snode_custom, YANG_PATH_DATA, xpath, sizeof(xpath)); | |
e0ccfad2 RW |
426 | mapping = yang_mapping_lookup(args->translator, |
427 | YANG_TRANSLATE_TO_NATIVE, xpath); | |
1c2facd1 RW |
428 | if (!mapping) { |
429 | flog_warn(EC_LIB_YANG_TRANSLATOR_LOAD, | |
430 | "%s: missing mapping for \"%s\"", __func__, xpath); | |
e0ccfad2 RW |
431 | args->errors += 1; |
432 | return YANG_ITER_CONTINUE; | |
1c2facd1 RW |
433 | } |
434 | ||
435 | snode_native = | |
436 | ly_ctx_get_node(ly_native_ctx, NULL, mapping->xpath_to_fmt, 0); | |
437 | assert(snode_native); | |
438 | ||
439 | /* Check if the YANG types are compatible. */ | |
440 | stype_custom = yang_snode_get_type(snode_custom); | |
441 | stype_native = yang_snode_get_type(snode_native); | |
442 | if (stype_custom && stype_native) { | |
443 | if (stype_custom->base != stype_native->base) { | |
444 | flog_warn( | |
445 | EC_LIB_YANG_TRANSLATOR_LOAD, | |
446 | "%s: YANG types are incompatible (xpath: \"%s\")", | |
447 | __func__, xpath); | |
e0ccfad2 RW |
448 | args->errors += 1; |
449 | return YANG_ITER_CONTINUE; | |
1c2facd1 RW |
450 | } |
451 | ||
452 | /* TODO: check if the value spaces are identical. */ | |
453 | } | |
e0ccfad2 RW |
454 | |
455 | return YANG_ITER_CONTINUE; | |
1c2facd1 RW |
456 | } |
457 | ||
458 | /* | |
459 | * Check if the modules from the translator have a mapping for all of their | |
460 | * schema nodes (after loading the deviations). | |
461 | */ | |
462 | static unsigned int yang_translator_validate(struct yang_translator *translator) | |
463 | { | |
464 | struct yang_tmodule *tmodule; | |
465 | struct listnode *ln; | |
e0ccfad2 RW |
466 | struct translator_validate_args args; |
467 | ||
468 | args.translator = translator; | |
469 | args.errors = 0; | |
1c2facd1 RW |
470 | |
471 | for (ALL_LIST_ELEMENTS_RO(translator->modules, ln, tmodule)) { | |
e0ccfad2 | 472 | yang_snodes_iterate_module( |
1c2facd1 RW |
473 | tmodule->module, yang_translator_validate_cb, |
474 | YANG_ITER_FILTER_NPCONTAINERS | |
475 | | YANG_ITER_FILTER_LIST_KEYS | |
476 | | YANG_ITER_FILTER_INPUT_OUTPUT, | |
e0ccfad2 | 477 | &args); |
1c2facd1 RW |
478 | } |
479 | ||
e0ccfad2 | 480 | if (args.errors) |
1c2facd1 RW |
481 | flog_warn( |
482 | EC_LIB_YANG_TRANSLATOR_LOAD, | |
483 | "%s: failed to validate \"%s\" module translator: %u error(s)", | |
e0ccfad2 | 484 | __func__, translator->family, args.errors); |
1c2facd1 | 485 | |
e0ccfad2 | 486 | return args.errors; |
1c2facd1 RW |
487 | } |
488 | ||
e0ccfad2 | 489 | static int yang_module_nodes_count_cb(const struct lys_node *snode, void *arg) |
1c2facd1 | 490 | { |
e0ccfad2 | 491 | unsigned int *total = arg; |
1c2facd1 RW |
492 | |
493 | *total += 1; | |
e0ccfad2 RW |
494 | |
495 | return YANG_ITER_CONTINUE; | |
1c2facd1 RW |
496 | } |
497 | ||
498 | /* Calculate the number of nodes for the given module. */ | |
499 | static unsigned int yang_module_nodes_count(const struct lys_module *module) | |
500 | { | |
501 | unsigned int total = 0; | |
502 | ||
e0ccfad2 | 503 | yang_snodes_iterate_module(module, yang_module_nodes_count_cb, |
1c2facd1 RW |
504 | YANG_ITER_FILTER_NPCONTAINERS |
505 | | YANG_ITER_FILTER_LIST_KEYS | |
506 | | YANG_ITER_FILTER_INPUT_OUTPUT, | |
e0ccfad2 | 507 | &total); |
1c2facd1 RW |
508 | |
509 | return total; | |
510 | } | |
511 | ||
1c2facd1 RW |
512 | void yang_translator_init(void) |
513 | { | |
591f57cf | 514 | ly_translator_ctx = yang_ctx_new_setup(); |
1c2facd1 RW |
515 | if (!ly_translator_ctx) { |
516 | flog_err(EC_LIB_LIBYANG, "%s: ly_ctx_new() failed", __func__); | |
517 | exit(1); | |
518 | } | |
1c2facd1 RW |
519 | |
520 | if (!ly_ctx_load_module(ly_translator_ctx, "frr-module-translator", | |
521 | NULL)) { | |
522 | flog_err( | |
523 | EC_LIB_YANG_MODULE_LOAD, | |
524 | "%s: failed to load the \"frr-module-translator\" module", | |
525 | __func__); | |
526 | exit(1); | |
527 | } | |
528 | } | |
529 | ||
530 | void yang_translator_terminate(void) | |
531 | { | |
532 | while (!RB_EMPTY(yang_translators, &yang_translators)) { | |
533 | struct yang_translator *translator; | |
534 | ||
535 | translator = RB_ROOT(yang_translators, &yang_translators); | |
536 | yang_translator_unload(translator); | |
537 | } | |
538 | ||
539 | ly_ctx_destroy(ly_translator_ctx, NULL); | |
540 | } |