]>
Commit | Line | Data |
---|---|---|
a7ca2199 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 "command.h" | |
25 | #include "memory.h" | |
26 | #include "libfrr.h" | |
27 | #include "version.h" | |
28 | #include "northbound.h" | |
29 | ||
30 | #include <sysrepo.h> | |
31 | #include <sysrepo/values.h> | |
32 | #include <sysrepo/xpath.h> | |
33 | ||
34 | DEFINE_MTYPE_STATIC(LIB, SYSREPO, "Sysrepo module") | |
35 | ||
36 | static struct thread_master *master; | |
37 | static struct list *sysrepo_threads; | |
38 | static sr_session_ctx_t *session; | |
39 | static sr_conn_ctx_t *connection; | |
40 | ||
41 | static int frr_sr_read_cb(struct thread *thread); | |
42 | static int frr_sr_write_cb(struct thread *thread); | |
43 | static int frr_sr_finish(void); | |
44 | ||
45 | /* Convert FRR YANG data value to sysrepo YANG data value. */ | |
46 | static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data) | |
47 | { | |
48 | const struct lys_node *snode; | |
49 | struct lys_node_container *scontainer; | |
50 | struct lys_node_leaf *sleaf; | |
51 | struct lys_node_leaflist *sleaflist; | |
52 | LY_DATA_TYPE type; | |
53 | ||
54 | sr_val_set_xpath(sr_data, frr_data->xpath); | |
55 | ||
56 | snode = frr_data->snode; | |
57 | switch (snode->nodetype) { | |
58 | case LYS_CONTAINER: | |
59 | scontainer = (struct lys_node_container *)snode; | |
60 | if (!scontainer->presence) | |
61 | return -1; | |
62 | sr_data->type = SR_CONTAINER_PRESENCE_T; | |
63 | return 0; | |
64 | case LYS_LIST: | |
65 | sr_data->type = SR_LIST_T; | |
66 | return 0; | |
67 | case LYS_LEAF: | |
68 | sleaf = (struct lys_node_leaf *)snode; | |
69 | type = sleaf->type.base; | |
70 | break; | |
71 | case LYS_LEAFLIST: | |
72 | sleaflist = (struct lys_node_leaflist *)snode; | |
73 | type = sleaflist->type.base; | |
74 | break; | |
75 | default: | |
76 | return -1; | |
77 | } | |
78 | ||
79 | switch (type) { | |
80 | case LY_TYPE_BINARY: | |
81 | sr_val_set_str_data(sr_data, SR_BINARY_T, frr_data->value); | |
82 | break; | |
83 | case LY_TYPE_BITS: | |
84 | sr_val_set_str_data(sr_data, SR_BITS_T, frr_data->value); | |
85 | break; | |
86 | case LY_TYPE_BOOL: | |
87 | sr_data->type = SR_BOOL_T; | |
88 | sr_data->data.bool_val = yang_str2bool(frr_data->value); | |
89 | break; | |
90 | case LY_TYPE_DEC64: | |
91 | sr_data->type = SR_DECIMAL64_T; | |
92 | sr_data->data.decimal64_val = | |
93 | yang_str2dec64(frr_data->xpath, frr_data->value); | |
94 | break; | |
95 | case LY_TYPE_EMPTY: | |
96 | sr_data->type = SR_LEAF_EMPTY_T; | |
97 | break; | |
98 | case LY_TYPE_ENUM: | |
99 | sr_val_set_str_data(sr_data, SR_ENUM_T, frr_data->value); | |
100 | break; | |
101 | case LY_TYPE_IDENT: | |
102 | sr_val_set_str_data(sr_data, SR_IDENTITYREF_T, frr_data->value); | |
103 | break; | |
104 | case LY_TYPE_INST: | |
105 | sr_val_set_str_data(sr_data, SR_INSTANCEID_T, frr_data->value); | |
106 | break; | |
107 | case LY_TYPE_INT8: | |
108 | sr_data->type = SR_INT8_T; | |
109 | sr_data->data.int8_val = yang_str2int8(frr_data->value); | |
110 | break; | |
111 | case LY_TYPE_INT16: | |
112 | sr_data->type = SR_INT16_T; | |
113 | sr_data->data.int16_val = yang_str2int16(frr_data->value); | |
114 | break; | |
115 | case LY_TYPE_INT32: | |
116 | sr_data->type = SR_INT32_T; | |
117 | sr_data->data.int32_val = yang_str2int32(frr_data->value); | |
118 | break; | |
119 | case LY_TYPE_INT64: | |
120 | sr_data->type = SR_INT64_T; | |
121 | sr_data->data.int64_val = yang_str2int64(frr_data->value); | |
122 | break; | |
123 | case LY_TYPE_STRING: | |
124 | sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value); | |
125 | break; | |
126 | case LY_TYPE_UINT8: | |
127 | sr_data->type = SR_UINT8_T; | |
128 | sr_data->data.uint8_val = yang_str2uint8(frr_data->value); | |
129 | break; | |
130 | case LY_TYPE_UINT16: | |
131 | sr_data->type = SR_UINT16_T; | |
132 | sr_data->data.uint16_val = yang_str2uint16(frr_data->value); | |
133 | break; | |
134 | case LY_TYPE_UINT32: | |
135 | sr_data->type = SR_UINT32_T; | |
136 | sr_data->data.uint32_val = yang_str2uint32(frr_data->value); | |
137 | break; | |
138 | case LY_TYPE_UINT64: | |
139 | sr_data->type = SR_UINT64_T; | |
140 | sr_data->data.uint64_val = yang_str2uint64(frr_data->value); | |
141 | break; | |
142 | default: | |
143 | return -1; | |
144 | } | |
145 | ||
146 | return 0; | |
147 | } | |
148 | ||
149 | static int frr_sr_process_change(struct nb_config *candidate, | |
150 | sr_change_oper_t sr_op, sr_val_t *sr_old_val, | |
151 | sr_val_t *sr_new_val) | |
152 | { | |
153 | struct nb_node *nb_node; | |
154 | enum nb_operation nb_op; | |
155 | sr_val_t *sr_data; | |
156 | const char *xpath; | |
157 | char value_str[YANG_VALUE_MAXLEN]; | |
158 | struct yang_data *data; | |
159 | int ret; | |
160 | ||
161 | sr_data = sr_new_val ? sr_new_val : sr_old_val; | |
162 | assert(sr_data); | |
163 | ||
164 | xpath = sr_data->xpath; | |
165 | ||
166 | /* Non-presence container - nothing to do. */ | |
167 | if (sr_data->type == SR_CONTAINER_T) | |
168 | return NB_OK; | |
169 | ||
170 | nb_node = nb_node_find(xpath); | |
171 | if (!nb_node) { | |
172 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
173 | "%s: unknown data path: %s", __func__, xpath); | |
174 | return NB_ERR; | |
175 | } | |
176 | ||
177 | /* Map operation values. */ | |
178 | switch (sr_op) { | |
179 | case SR_OP_CREATED: | |
180 | case SR_OP_MODIFIED: | |
181 | if (nb_operation_is_valid(NB_OP_CREATE, nb_node->snode)) | |
182 | nb_op = NB_OP_CREATE; | |
183 | else if (nb_operation_is_valid(NB_OP_MODIFY, nb_node->snode)) { | |
184 | nb_op = NB_OP_MODIFY; | |
185 | } else | |
186 | /* Ignore list keys modifications. */ | |
187 | return NB_OK; | |
188 | break; | |
189 | case SR_OP_DELETED: | |
190 | /* | |
191 | * When a list is deleted or one of its keys is changed, we are | |
192 | * notified about the removal of all of its leafs, even the ones | |
193 | * that are non-optional. We need to ignore these notifications. | |
194 | */ | |
195 | if (!nb_operation_is_valid(NB_OP_DELETE, nb_node->snode)) | |
196 | return NB_OK; | |
197 | ||
198 | nb_op = NB_OP_DELETE; | |
199 | break; | |
200 | case SR_OP_MOVED: | |
201 | nb_op = NB_OP_MOVE; | |
202 | break; | |
203 | default: | |
204 | flog_err(EC_LIB_DEVELOPMENT, | |
205 | "%s: unexpected operation %u [xpath %s]", __func__, | |
206 | sr_op, xpath); | |
207 | return NB_ERR; | |
208 | } | |
209 | ||
210 | sr_val_to_buff(sr_data, value_str, sizeof(value_str)); | |
211 | data = yang_data_new(xpath, value_str); | |
212 | ||
213 | ret = nb_candidate_edit(candidate, nb_node, nb_op, xpath, NULL, data); | |
214 | yang_data_free(data); | |
215 | if (ret != NB_OK) { | |
216 | flog_warn( | |
217 | EC_LIB_NB_CANDIDATE_EDIT_ERROR, | |
218 | "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", | |
219 | __func__, nb_operation_name(nb_op), xpath); | |
220 | return NB_ERR; | |
221 | } | |
222 | ||
223 | return NB_OK; | |
224 | } | |
225 | ||
226 | /* Callback for changes in the running configuration. */ | |
227 | static int frr_sr_config_change_cb(sr_session_ctx_t *session, | |
228 | const char *module_name, | |
229 | sr_notif_event_t sr_ev, void *private_ctx) | |
230 | { | |
231 | sr_change_iter_t *it; | |
232 | int ret; | |
233 | sr_change_oper_t sr_op; | |
234 | sr_val_t *sr_old_val, *sr_new_val; | |
235 | char xpath[XPATH_MAXLEN]; | |
236 | struct nb_config *candidate; | |
237 | ||
238 | /* | |
239 | * Ignore SR_EV_ABORT and SR_EV_APPLY. We'll leverage the northbound | |
240 | * layer itself to abort or apply the configuration changes when a | |
241 | * transaction is created. | |
242 | */ | |
243 | if (sr_ev != SR_EV_ENABLED && sr_ev != SR_EV_VERIFY) | |
244 | return SR_ERR_OK; | |
245 | ||
246 | snprintf(xpath, sizeof(xpath), "/%s:*", module_name); | |
247 | ret = sr_get_changes_iter(session, xpath, &it); | |
248 | if (ret != SR_ERR_OK) { | |
249 | flog_err(EC_LIB_LIBSYSREPO, | |
250 | "%s: sr_get_changes_iter() failed for xpath %s", | |
251 | __func__, xpath); | |
252 | return ret; | |
253 | } | |
254 | ||
255 | candidate = nb_config_dup(running_config); | |
256 | ||
257 | while ((ret = sr_get_change_next(session, it, &sr_op, &sr_old_val, | |
258 | &sr_new_val)) | |
259 | == SR_ERR_OK) { | |
260 | ret = frr_sr_process_change(candidate, sr_op, sr_old_val, | |
261 | sr_new_val); | |
262 | sr_free_val(sr_old_val); | |
263 | sr_free_val(sr_new_val); | |
264 | if (ret != NB_OK) | |
265 | break; | |
266 | } | |
267 | ||
268 | sr_free_change_iter(it); | |
269 | if (ret != NB_OK && ret != SR_ERR_NOT_FOUND) { | |
270 | nb_config_free(candidate); | |
271 | return SR_ERR_INTERNAL; | |
272 | } | |
273 | ||
274 | /* Commit changes. */ | |
275 | ret = nb_candidate_commit(candidate, NB_CLIENT_SYSREPO, true, NULL, | |
276 | NULL); | |
277 | nb_config_free(candidate); | |
278 | ||
279 | /* Map northbound return code to sysrepo return code. */ | |
280 | switch (ret) { | |
281 | case NB_OK: | |
282 | case NB_ERR_NO_CHANGES: | |
283 | return SR_ERR_OK; | |
284 | case NB_ERR_LOCKED: | |
285 | return SR_ERR_LOCKED; | |
286 | case NB_ERR_RESOURCE: | |
287 | return SR_ERR_NOMEM; | |
288 | default: | |
289 | return SR_ERR_VALIDATION_FAILED; | |
290 | } | |
291 | } | |
292 | ||
293 | static void frr_sr_state_get_elem(struct list *elements, | |
294 | struct nb_node *nb_node, | |
295 | const void *list_entry, const char *xpath) | |
296 | { | |
297 | struct yang_data *data; | |
298 | ||
299 | data = nb_node->cbs.get_elem(xpath, list_entry); | |
300 | if (data) | |
301 | listnode_add(elements, data); | |
302 | } | |
303 | ||
304 | static void frr_sr_state_cb_container(struct list *elements, const char *xpath, | |
305 | const struct lys_node *snode) | |
306 | { | |
307 | struct lys_node *child; | |
308 | ||
309 | LY_TREE_FOR (snode->child, child) { | |
310 | struct nb_node *nb_node = child->priv; | |
311 | char xpath_child[XPATH_MAXLEN]; | |
312 | ||
313 | if (!nb_operation_is_valid(NB_OP_GET_ELEM, child)) | |
314 | continue; | |
315 | ||
316 | snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath, | |
317 | child->name); | |
318 | ||
319 | frr_sr_state_get_elem(elements, nb_node, NULL, xpath_child); | |
320 | } | |
321 | } | |
322 | ||
323 | static void frr_sr_state_cb_list_entry(struct list *elements, | |
324 | const char *xpath_list, | |
325 | const void *list_entry, | |
326 | struct lys_node *child) | |
327 | { | |
328 | struct nb_node *nb_node = child->priv; | |
329 | struct lys_node_leaf *sleaf; | |
330 | char xpath_child[XPATH_MAXLEN]; | |
331 | ||
332 | /* Sysrepo doesn't want to know about list keys. */ | |
333 | switch (child->nodetype) { | |
334 | case LYS_LEAF: | |
335 | sleaf = (struct lys_node_leaf *)child; | |
336 | if (lys_is_key(sleaf, NULL)) | |
337 | return; | |
338 | break; | |
339 | case LYS_LEAFLIST: | |
340 | break; | |
341 | default: | |
342 | return; | |
343 | } | |
344 | ||
345 | if (!nb_operation_is_valid(NB_OP_GET_ELEM, child)) | |
346 | return; | |
347 | ||
348 | snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath_list, | |
349 | child->name); | |
350 | ||
351 | frr_sr_state_get_elem(elements, nb_node, list_entry, xpath_child); | |
352 | } | |
353 | ||
354 | static void frr_sr_state_cb_list(struct list *elements, const char *xpath, | |
355 | const struct lys_node *snode) | |
356 | { | |
357 | struct nb_node *nb_node = snode->priv; | |
358 | struct lys_node_list *slist = (struct lys_node_list *)snode; | |
359 | const void *next; | |
360 | ||
361 | for (next = nb_node->cbs.get_next(xpath, NULL); next; | |
362 | next = nb_node->cbs.get_next(xpath, next)) { | |
363 | struct yang_list_keys keys; | |
364 | const void *list_entry; | |
365 | char xpath_list[XPATH_MAXLEN]; | |
366 | struct lys_node *child; | |
367 | ||
368 | /* Get the list keys. */ | |
369 | if (nb_node->cbs.get_keys(next, &keys) != NB_OK) { | |
370 | flog_warn(EC_LIB_NB_CB_STATE, | |
371 | "%s: failed to get list keys", __func__); | |
372 | continue; | |
373 | } | |
374 | ||
375 | /* Get list item. */ | |
376 | list_entry = nb_node->cbs.lookup_entry(&keys); | |
377 | if (!list_entry) { | |
378 | flog_warn(EC_LIB_NB_CB_STATE, | |
379 | "%s: failed to lookup list entry", __func__); | |
380 | continue; | |
381 | } | |
382 | ||
383 | /* Append list keys to the XPath. */ | |
384 | strlcpy(xpath_list, xpath, sizeof(xpath_list)); | |
385 | for (unsigned int i = 0; i < keys.num; i++) { | |
386 | snprintf(xpath_list + strlen(xpath_list), | |
387 | sizeof(xpath_list) - strlen(xpath_list), | |
388 | "[%s='%s']", slist->keys[i]->name, | |
389 | keys.key[i].value); | |
390 | } | |
391 | ||
392 | /* Loop through list entries. */ | |
393 | LY_TREE_FOR (snode->child, child) { | |
394 | frr_sr_state_cb_list_entry(elements, xpath_list, | |
395 | list_entry, child); | |
396 | } | |
397 | } | |
398 | } | |
399 | ||
400 | /* Callback for state retrieval. */ | |
401 | static int frr_sr_state_cb(const char *xpath, sr_val_t **values, | |
402 | size_t *values_cnt, uint64_t request_id, | |
403 | void *private_ctx) | |
404 | { | |
405 | struct list *elements; | |
406 | struct yang_data *data; | |
407 | const struct lys_node *snode; | |
408 | struct listnode *node; | |
409 | sr_val_t *v; | |
410 | int ret, count, i = 0; | |
411 | ||
412 | /* Find schema node. */ | |
413 | snode = ly_ctx_get_node(ly_native_ctx, NULL, xpath, 0); | |
414 | ||
415 | elements = yang_data_list_new(); | |
416 | ||
417 | switch (snode->nodetype) { | |
418 | case LYS_CONTAINER: | |
419 | frr_sr_state_cb_container(elements, xpath, snode); | |
420 | break; | |
421 | case LYS_LIST: | |
422 | frr_sr_state_cb_list(elements, xpath, snode); | |
423 | break; | |
424 | default: | |
425 | break; | |
426 | } | |
427 | if (list_isempty(elements)) | |
428 | goto exit; | |
429 | ||
430 | count = listcount(elements); | |
431 | ret = sr_new_values(count, &v); | |
432 | if (ret != SR_ERR_OK) { | |
433 | flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", __func__, | |
434 | sr_strerror(ret)); | |
435 | goto exit; | |
436 | } | |
437 | ||
438 | for (ALL_LIST_ELEMENTS_RO(elements, node, data)) { | |
439 | if (yang_data_frr2sr(data, &v[i++]) != 0) { | |
440 | flog_err(EC_LIB_SYSREPO_DATA_CONVERT, | |
441 | "%s: failed to convert data to sysrepo format", | |
442 | __func__); | |
443 | } | |
444 | } | |
445 | ||
446 | *values = v; | |
447 | *values_cnt = count; | |
448 | ||
449 | list_delete(&elements); | |
450 | ||
451 | return SR_ERR_OK; | |
452 | ||
453 | exit: | |
454 | list_delete(&elements); | |
455 | *values = NULL; | |
456 | values_cnt = 0; | |
457 | ||
458 | return SR_ERR_OK; | |
459 | } | |
460 | ||
461 | static int frr_sr_config_rpc_cb(const char *xpath, const sr_val_t *sr_input, | |
462 | const size_t input_cnt, sr_val_t **sr_output, | |
463 | size_t *sr_output_cnt, void *private_ctx) | |
464 | { | |
465 | struct nb_node *nb_node; | |
466 | struct list *input; | |
467 | struct list *output; | |
468 | struct yang_data *data; | |
469 | size_t cb_output_cnt; | |
470 | int ret = SR_ERR_OK; | |
471 | ||
472 | nb_node = nb_node_find(xpath); | |
473 | if (!nb_node) { | |
474 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
475 | "%s: unknown data path: %s", __func__, xpath); | |
476 | return SR_ERR_INTERNAL; | |
477 | } | |
478 | ||
479 | input = yang_data_list_new(); | |
480 | output = yang_data_list_new(); | |
481 | ||
482 | /* Process input. */ | |
483 | for (size_t i = 0; i < input_cnt; i++) { | |
484 | char value_str[YANG_VALUE_MAXLEN]; | |
485 | ||
486 | sr_val_to_buff(&sr_input[i], value_str, sizeof(value_str)); | |
487 | ||
488 | data = yang_data_new(xpath, value_str); | |
489 | listnode_add(input, data); | |
490 | } | |
491 | ||
492 | /* Execute callback registered for this XPath. */ | |
493 | if (nb_node->cbs.rpc(xpath, input, output) != NB_OK) { | |
494 | flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", | |
495 | __func__, xpath); | |
496 | ret = SR_ERR_OPERATION_FAILED; | |
497 | goto exit; | |
498 | } | |
499 | ||
500 | /* Process output. */ | |
501 | if (listcount(output) > 0) { | |
502 | sr_val_t *values = NULL; | |
503 | struct listnode *node; | |
504 | int i = 0; | |
505 | ||
506 | cb_output_cnt = listcount(output); | |
507 | ret = sr_new_values(cb_output_cnt, &values); | |
508 | if (ret != SR_ERR_OK) { | |
509 | flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", | |
510 | __func__, sr_strerror(ret)); | |
511 | goto exit; | |
512 | } | |
513 | ||
514 | for (ALL_LIST_ELEMENTS_RO(output, node, data)) { | |
515 | if (yang_data_frr2sr(data, &values[i++]) != 0) { | |
516 | flog_err( | |
517 | EC_LIB_SYSREPO_DATA_CONVERT, | |
518 | "%s: failed to convert data to Sysrepo format", | |
519 | __func__); | |
520 | ret = SR_ERR_INTERNAL; | |
521 | sr_free_values(values, cb_output_cnt); | |
522 | goto exit; | |
523 | } | |
524 | } | |
525 | ||
526 | *sr_output = values; | |
527 | *sr_output_cnt = cb_output_cnt; | |
528 | } | |
529 | ||
530 | exit: | |
531 | /* Release memory. */ | |
532 | list_delete(&input); | |
533 | list_delete(&output); | |
534 | ||
535 | return ret; | |
536 | } | |
537 | ||
538 | static int frr_sr_notification_send(const char *xpath, struct list *arguments) | |
539 | { | |
540 | sr_val_t *values = NULL; | |
541 | size_t values_cnt = 0; | |
542 | int ret; | |
543 | ||
544 | if (arguments && listcount(arguments) > 0) { | |
545 | struct yang_data *data; | |
546 | struct listnode *node; | |
547 | int i = 0; | |
548 | ||
549 | values_cnt = listcount(arguments); | |
550 | ret = sr_new_values(values_cnt, &values); | |
551 | if (ret != SR_ERR_OK) { | |
552 | flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", | |
553 | __func__, sr_strerror(ret)); | |
554 | return NB_ERR; | |
555 | } | |
556 | ||
557 | for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) { | |
558 | if (yang_data_frr2sr(data, &values[i++]) != 0) { | |
559 | flog_err( | |
560 | EC_LIB_SYSREPO_DATA_CONVERT, | |
561 | "%s: failed to convert data to sysrepo format", | |
562 | __func__); | |
563 | sr_free_values(values, values_cnt); | |
564 | return NB_ERR; | |
565 | } | |
566 | } | |
567 | } | |
568 | ||
569 | ret = sr_event_notif_send(session, xpath, values, values_cnt, | |
570 | SR_EV_NOTIF_DEFAULT); | |
571 | if (ret != SR_ERR_OK) { | |
572 | flog_err(EC_LIB_LIBSYSREPO, | |
573 | "%s: sr_event_notif_send() failed for xpath %s", | |
574 | __func__, xpath); | |
575 | return NB_ERR; | |
576 | } | |
577 | ||
578 | return NB_OK; | |
579 | } | |
580 | ||
581 | /* Code to integrate the sysrepo client into FRR main event loop. */ | |
582 | struct sysrepo_thread { | |
583 | struct thread *thread; | |
584 | sr_fd_event_t event; | |
585 | int fd; | |
586 | }; | |
587 | ||
588 | static struct sysrepo_thread *frr_sr_fd_lookup(sr_fd_event_t event, int fd) | |
589 | { | |
590 | struct sysrepo_thread *sr_thread; | |
591 | struct listnode *node; | |
592 | ||
593 | for (ALL_LIST_ELEMENTS_RO(sysrepo_threads, node, sr_thread)) { | |
594 | if (sr_thread->event == event && sr_thread->fd == fd) | |
595 | return sr_thread; | |
596 | } | |
597 | ||
598 | return NULL; | |
599 | } | |
600 | ||
601 | static void frr_sr_fd_add(int event, int fd) | |
602 | { | |
603 | struct sysrepo_thread *sr_thread; | |
604 | ||
605 | if (frr_sr_fd_lookup(event, fd) != NULL) | |
606 | return; | |
607 | ||
608 | sr_thread = XCALLOC(MTYPE_SYSREPO, sizeof(*sr_thread)); | |
609 | sr_thread->event = event; | |
610 | sr_thread->fd = fd; | |
611 | listnode_add(sysrepo_threads, sr_thread); | |
612 | ||
613 | switch (event) { | |
614 | case SR_FD_INPUT_READY: | |
615 | thread_add_read(master, frr_sr_read_cb, NULL, fd, | |
616 | &sr_thread->thread); | |
617 | break; | |
618 | case SR_FD_OUTPUT_READY: | |
619 | thread_add_write(master, frr_sr_write_cb, NULL, fd, | |
620 | &sr_thread->thread); | |
621 | break; | |
622 | default: | |
623 | return; | |
624 | } | |
625 | } | |
626 | ||
627 | static void frr_sr_fd_free(struct sysrepo_thread *sr_thread) | |
628 | { | |
629 | THREAD_OFF(sr_thread->thread); | |
630 | XFREE(MTYPE_SYSREPO, sr_thread); | |
631 | } | |
632 | ||
633 | static void frr_sr_fd_del(int event, int fd) | |
634 | { | |
635 | struct sysrepo_thread *sr_thread; | |
636 | ||
637 | sr_thread = frr_sr_fd_lookup(event, fd); | |
638 | if (!sr_thread) | |
639 | return; | |
640 | ||
641 | listnode_delete(sysrepo_threads, sr_thread); | |
642 | frr_sr_fd_free(sr_thread); | |
643 | } | |
644 | ||
645 | static void frr_sr_fd_update(sr_fd_change_t *fd_change_set, | |
646 | size_t fd_change_set_cnt) | |
647 | { | |
648 | for (size_t i = 0; i < fd_change_set_cnt; i++) { | |
649 | int fd = fd_change_set[i].fd; | |
650 | int event = fd_change_set[i].events; | |
651 | ||
652 | if (event != SR_FD_INPUT_READY && event != SR_FD_OUTPUT_READY) | |
653 | continue; | |
654 | ||
655 | switch (fd_change_set[i].action) { | |
656 | case SR_FD_START_WATCHING: | |
657 | frr_sr_fd_add(event, fd); | |
658 | break; | |
659 | case SR_FD_STOP_WATCHING: | |
660 | frr_sr_fd_del(event, fd); | |
661 | break; | |
662 | default: | |
663 | break; | |
664 | } | |
665 | } | |
666 | } | |
667 | ||
668 | static int frr_sr_read_cb(struct thread *thread) | |
669 | { | |
670 | int fd = THREAD_FD(thread); | |
671 | sr_fd_change_t *fd_change_set = NULL; | |
672 | size_t fd_change_set_cnt = 0; | |
673 | int ret; | |
674 | ||
675 | ret = sr_fd_event_process(fd, SR_FD_INPUT_READY, &fd_change_set, | |
676 | &fd_change_set_cnt); | |
677 | if (ret != SR_ERR_OK) { | |
678 | flog_err(EC_LIB_LIBSYSREPO, "%s: sr_fd_event_process(): %s", | |
679 | __func__, sr_strerror(ret)); | |
680 | return -1; | |
681 | } | |
682 | ||
683 | thread = NULL; | |
684 | thread_add_read(master, frr_sr_read_cb, NULL, fd, &thread); | |
685 | ||
686 | frr_sr_fd_update(fd_change_set, fd_change_set_cnt); | |
687 | free(fd_change_set); | |
688 | ||
689 | return 0; | |
690 | } | |
691 | ||
692 | static int frr_sr_write_cb(struct thread *thread) | |
693 | { | |
694 | int fd = THREAD_FD(thread); | |
695 | sr_fd_change_t *fd_change_set = NULL; | |
696 | size_t fd_change_set_cnt = 0; | |
697 | int ret; | |
698 | ||
699 | ret = sr_fd_event_process(fd, SR_FD_OUTPUT_READY, &fd_change_set, | |
700 | &fd_change_set_cnt); | |
701 | if (ret != SR_ERR_OK) { | |
702 | flog_err(EC_LIB_LIBSYSREPO, "%s: sr_fd_event_process(): %s", | |
703 | __func__, sr_strerror(ret)); | |
704 | return -1; | |
705 | } | |
706 | ||
707 | frr_sr_fd_update(fd_change_set, fd_change_set_cnt); | |
708 | free(fd_change_set); | |
709 | ||
710 | return 0; | |
711 | } | |
712 | ||
713 | static void frr_sr_subscribe_config(struct yang_module *module) | |
714 | { | |
715 | int ret; | |
716 | ||
717 | ret = sr_module_change_subscribe( | |
718 | session, module->name, frr_sr_config_change_cb, NULL, 0, | |
719 | SR_SUBSCR_DEFAULT | SR_SUBSCR_EV_ENABLED, | |
720 | &module->sr_subscription); | |
721 | if (ret != SR_ERR_OK) | |
722 | flog_err(EC_LIB_LIBSYSREPO, "sr_module_change_subscribe(): %s", | |
723 | sr_strerror(ret)); | |
724 | } | |
725 | ||
726 | static void frr_sr_subscribe_state(const struct lys_node *snode, void *arg1, | |
727 | void *arg2) | |
728 | { | |
729 | struct yang_module *module = arg1; | |
730 | struct nb_node *nb_node; | |
731 | int ret; | |
732 | ||
733 | if (!(snode->flags & LYS_CONFIG_R)) | |
734 | return; | |
735 | /* We only need to subscribe to the root of the state subtrees. */ | |
736 | if (snode->parent && (snode->parent->flags & LYS_CONFIG_R)) | |
737 | return; | |
738 | ||
739 | nb_node = snode->priv; | |
740 | if (debug_northbound) | |
741 | zlog_debug("%s: providing data to '%s'", __func__, | |
742 | nb_node->xpath); | |
743 | ||
744 | ret = sr_dp_get_items_subscribe( | |
745 | session, nb_node->xpath, frr_sr_state_cb, NULL, | |
746 | SR_SUBSCR_CTX_REUSE, &module->sr_subscription); | |
747 | if (ret != SR_ERR_OK) | |
748 | flog_err(EC_LIB_LIBSYSREPO, "sr_dp_get_items_subscribe(): %s", | |
749 | sr_strerror(ret)); | |
750 | } | |
751 | ||
752 | static void frr_sr_subscribe_rpc(const struct lys_node *snode, void *arg1, | |
753 | void *arg2) | |
754 | { | |
755 | struct yang_module *module = arg1; | |
756 | struct nb_node *nb_node; | |
757 | int ret; | |
758 | ||
759 | if (snode->nodetype != LYS_RPC) | |
760 | return; | |
761 | ||
762 | nb_node = snode->priv; | |
763 | if (debug_northbound) | |
764 | zlog_debug("%s: providing RPC to '%s'", __func__, | |
765 | nb_node->xpath); | |
766 | ||
767 | ret = sr_rpc_subscribe(session, nb_node->xpath, frr_sr_config_rpc_cb, | |
768 | NULL, SR_SUBSCR_CTX_REUSE, | |
769 | &module->sr_subscription); | |
770 | if (ret != SR_ERR_OK) | |
771 | flog_err(EC_LIB_LIBSYSREPO, "sr_rpc_subscribe(): %s", | |
772 | sr_strerror(ret)); | |
773 | } | |
774 | ||
775 | static void frr_sr_subscribe_action(const struct lys_node *snode, void *arg1, | |
776 | void *arg2) | |
777 | { | |
778 | struct yang_module *module = arg1; | |
779 | struct nb_node *nb_node; | |
780 | int ret; | |
781 | ||
782 | if (snode->nodetype != LYS_ACTION) | |
783 | return; | |
784 | ||
785 | nb_node = snode->priv; | |
786 | if (debug_northbound) | |
787 | zlog_debug("%s: providing action to '%s'", __func__, | |
788 | nb_node->xpath); | |
789 | ||
790 | ret = sr_action_subscribe(session, nb_node->xpath, frr_sr_config_rpc_cb, | |
791 | NULL, SR_SUBSCR_CTX_REUSE, | |
792 | &module->sr_subscription); | |
793 | if (ret != SR_ERR_OK) | |
794 | flog_err(EC_LIB_LIBSYSREPO, "sr_action_subscribe(): %s", | |
795 | sr_strerror(ret)); | |
796 | } | |
797 | ||
798 | /* FRR's Sysrepo initialization. */ | |
799 | static int frr_sr_init(const char *program_name) | |
800 | { | |
801 | struct yang_module *module; | |
802 | int sysrepo_fd, ret; | |
803 | ||
804 | sysrepo_threads = list_new(); | |
805 | ||
806 | ret = sr_fd_watcher_init(&sysrepo_fd, NULL); | |
807 | if (ret != SR_ERR_OK) { | |
808 | flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_fd_watcher_init(): %s", | |
809 | __func__, sr_strerror(ret)); | |
810 | goto cleanup; | |
811 | } | |
812 | ||
813 | /* Connect to Sysrepo. */ | |
814 | ret = sr_connect(program_name, SR_CONN_DEFAULT, &connection); | |
815 | if (ret != SR_ERR_OK) { | |
816 | flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_connect(): %s", __func__, | |
817 | sr_strerror(ret)); | |
818 | goto cleanup; | |
819 | } | |
820 | ||
821 | /* Start session. */ | |
822 | ret = sr_session_start(connection, SR_DS_RUNNING, SR_SESS_DEFAULT, | |
823 | &session); | |
824 | if (ret != SR_ERR_OK) { | |
825 | flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_session_start(): %s", | |
826 | __func__, sr_strerror(ret)); | |
827 | goto cleanup; | |
828 | } | |
829 | ||
830 | /* Perform subscriptions. */ | |
831 | RB_FOREACH (module, yang_modules, &yang_modules) { | |
832 | frr_sr_subscribe_config(module); | |
833 | yang_module_snodes_iterate(module->info, frr_sr_subscribe_state, | |
834 | 0, module, NULL); | |
835 | yang_module_snodes_iterate(module->info, frr_sr_subscribe_rpc, | |
836 | 0, module, NULL); | |
837 | yang_module_snodes_iterate( | |
838 | module->info, frr_sr_subscribe_action, 0, module, NULL); | |
839 | } | |
840 | ||
841 | hook_register(nb_notification_send, frr_sr_notification_send); | |
842 | ||
843 | frr_sr_fd_add(SR_FD_INPUT_READY, sysrepo_fd); | |
844 | ||
845 | return 0; | |
846 | ||
847 | cleanup: | |
848 | frr_sr_finish(); | |
849 | ||
850 | return -1; | |
851 | } | |
852 | ||
853 | static int frr_sr_finish(void) | |
854 | { | |
855 | struct yang_module *module; | |
856 | ||
857 | RB_FOREACH (module, yang_modules, &yang_modules) { | |
858 | if (!module->sr_subscription) | |
859 | continue; | |
860 | sr_unsubscribe(session, module->sr_subscription); | |
861 | } | |
862 | ||
863 | if (session) | |
864 | sr_session_stop(session); | |
865 | if (connection) | |
866 | sr_disconnect(connection); | |
867 | ||
868 | sysrepo_threads->del = (void (*)(void *))frr_sr_fd_free; | |
869 | list_delete(&sysrepo_threads); | |
870 | sr_fd_watcher_cleanup(); | |
871 | ||
872 | return 0; | |
873 | } | |
874 | ||
875 | static int frr_sr_module_late_init(struct thread_master *tm) | |
876 | { | |
877 | master = tm; | |
878 | ||
879 | if (frr_sr_init(frr_get_progname()) < 0) { | |
880 | flog_err(EC_LIB_SYSREPO_INIT, | |
881 | "failed to initialize the Sysrepo module"); | |
882 | return -1; | |
883 | } | |
884 | ||
885 | hook_register(frr_fini, frr_sr_finish); | |
886 | ||
887 | return 0; | |
888 | } | |
889 | ||
890 | static int frr_sr_module_init(void) | |
891 | { | |
892 | hook_register(frr_late_init, frr_sr_module_late_init); | |
893 | ||
894 | return 0; | |
895 | } | |
896 | ||
897 | FRR_MODULE_SETUP(.name = "frr_sysrepo", .version = FRR_VERSION, | |
898 | .description = "FRR sysrepo integration module", | |
899 | .init = frr_sr_module_init, ) |