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