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