]>
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" | |
9eb2c0a1 | 25 | #include "debug.h" |
a7ca2199 RW |
26 | #include "memory.h" |
27 | #include "libfrr.h" | |
09781197 | 28 | #include "lib/version.h" |
a7ca2199 RW |
29 | #include "northbound.h" |
30 | ||
31 | #include <sysrepo.h> | |
32 | #include <sysrepo/values.h> | |
33 | #include <sysrepo/xpath.h> | |
34 | ||
bf8d3d6a | 35 | DEFINE_MTYPE_STATIC(LIB, SYSREPO, "Sysrepo module"); |
a7ca2199 | 36 | |
9eb2c0a1 RW |
37 | static struct debug nb_dbg_client_sysrepo = {0, "Northbound client: Sysrepo"}; |
38 | ||
a7ca2199 | 39 | static struct thread_master *master; |
a7ca2199 RW |
40 | static sr_session_ctx_t *session; |
41 | static sr_conn_ctx_t *connection; | |
88a7d121 | 42 | static struct nb_transaction *transaction; |
a7ca2199 RW |
43 | |
44 | static int frr_sr_read_cb(struct thread *thread); | |
a7ca2199 RW |
45 | static int frr_sr_finish(void); |
46 | ||
47 | /* Convert FRR YANG data value to sysrepo YANG data value. */ | |
48 | static int yang_data_frr2sr(struct yang_data *frr_data, sr_val_t *sr_data) | |
49 | { | |
80243aef | 50 | struct nb_node *nb_node; |
3bb513c3 CH |
51 | const struct lysc_node *snode; |
52 | struct lysc_node_container *scontainer; | |
53 | struct lysc_node_leaf *sleaf; | |
54 | struct lysc_node_leaflist *sleaflist; | |
a7ca2199 RW |
55 | LY_DATA_TYPE type; |
56 | ||
57 | sr_val_set_xpath(sr_data, frr_data->xpath); | |
58 | ||
80243aef RW |
59 | nb_node = nb_node_find(frr_data->xpath); |
60 | if (!nb_node) { | |
61 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
62 | "%s: unknown data path: %s", __func__, | |
63 | frr_data->xpath); | |
64 | return -1; | |
65 | } | |
66 | ||
67 | snode = nb_node->snode; | |
a7ca2199 RW |
68 | switch (snode->nodetype) { |
69 | case LYS_CONTAINER: | |
3bb513c3 CH |
70 | scontainer = (struct lysc_node_container *)snode; |
71 | if (!CHECK_FLAG(scontainer->flags, LYS_PRESENCE)) | |
a7ca2199 RW |
72 | return -1; |
73 | sr_data->type = SR_CONTAINER_PRESENCE_T; | |
74 | return 0; | |
75 | case LYS_LIST: | |
76 | sr_data->type = SR_LIST_T; | |
77 | return 0; | |
78 | case LYS_LEAF: | |
3bb513c3 | 79 | sleaf = (struct lysc_node_leaf *)snode; |
a7ca2199 RW |
80 | type = sleaf->type.base; |
81 | break; | |
82 | case LYS_LEAFLIST: | |
3bb513c3 | 83 | sleaflist = (struct lysc_node_leaflist *)snode; |
a7ca2199 RW |
84 | type = sleaflist->type.base; |
85 | break; | |
86 | default: | |
87 | return -1; | |
88 | } | |
89 | ||
90 | switch (type) { | |
91 | case LY_TYPE_BINARY: | |
92 | sr_val_set_str_data(sr_data, SR_BINARY_T, frr_data->value); | |
93 | break; | |
94 | case LY_TYPE_BITS: | |
95 | sr_val_set_str_data(sr_data, SR_BITS_T, frr_data->value); | |
96 | break; | |
97 | case LY_TYPE_BOOL: | |
98 | sr_data->type = SR_BOOL_T; | |
99 | sr_data->data.bool_val = yang_str2bool(frr_data->value); | |
100 | break; | |
101 | case LY_TYPE_DEC64: | |
102 | sr_data->type = SR_DECIMAL64_T; | |
103 | sr_data->data.decimal64_val = | |
104 | yang_str2dec64(frr_data->xpath, frr_data->value); | |
105 | break; | |
106 | case LY_TYPE_EMPTY: | |
107 | sr_data->type = SR_LEAF_EMPTY_T; | |
108 | break; | |
109 | case LY_TYPE_ENUM: | |
110 | sr_val_set_str_data(sr_data, SR_ENUM_T, frr_data->value); | |
111 | break; | |
112 | case LY_TYPE_IDENT: | |
113 | sr_val_set_str_data(sr_data, SR_IDENTITYREF_T, frr_data->value); | |
114 | break; | |
115 | case LY_TYPE_INST: | |
116 | sr_val_set_str_data(sr_data, SR_INSTANCEID_T, frr_data->value); | |
117 | break; | |
118 | case LY_TYPE_INT8: | |
119 | sr_data->type = SR_INT8_T; | |
120 | sr_data->data.int8_val = yang_str2int8(frr_data->value); | |
121 | break; | |
122 | case LY_TYPE_INT16: | |
123 | sr_data->type = SR_INT16_T; | |
124 | sr_data->data.int16_val = yang_str2int16(frr_data->value); | |
125 | break; | |
126 | case LY_TYPE_INT32: | |
127 | sr_data->type = SR_INT32_T; | |
128 | sr_data->data.int32_val = yang_str2int32(frr_data->value); | |
129 | break; | |
130 | case LY_TYPE_INT64: | |
131 | sr_data->type = SR_INT64_T; | |
132 | sr_data->data.int64_val = yang_str2int64(frr_data->value); | |
133 | break; | |
134 | case LY_TYPE_STRING: | |
135 | sr_val_set_str_data(sr_data, SR_STRING_T, frr_data->value); | |
136 | break; | |
137 | case LY_TYPE_UINT8: | |
138 | sr_data->type = SR_UINT8_T; | |
139 | sr_data->data.uint8_val = yang_str2uint8(frr_data->value); | |
140 | break; | |
141 | case LY_TYPE_UINT16: | |
142 | sr_data->type = SR_UINT16_T; | |
143 | sr_data->data.uint16_val = yang_str2uint16(frr_data->value); | |
144 | break; | |
145 | case LY_TYPE_UINT32: | |
146 | sr_data->type = SR_UINT32_T; | |
147 | sr_data->data.uint32_val = yang_str2uint32(frr_data->value); | |
148 | break; | |
149 | case LY_TYPE_UINT64: | |
150 | sr_data->type = SR_UINT64_T; | |
151 | sr_data->data.uint64_val = yang_str2uint64(frr_data->value); | |
152 | break; | |
153 | default: | |
154 | return -1; | |
155 | } | |
156 | ||
157 | return 0; | |
158 | } | |
159 | ||
160 | static int frr_sr_process_change(struct nb_config *candidate, | |
161 | sr_change_oper_t sr_op, sr_val_t *sr_old_val, | |
162 | sr_val_t *sr_new_val) | |
163 | { | |
164 | struct nb_node *nb_node; | |
165 | enum nb_operation nb_op; | |
166 | sr_val_t *sr_data; | |
167 | const char *xpath; | |
168 | char value_str[YANG_VALUE_MAXLEN]; | |
169 | struct yang_data *data; | |
170 | int ret; | |
171 | ||
172 | sr_data = sr_new_val ? sr_new_val : sr_old_val; | |
173 | assert(sr_data); | |
174 | ||
175 | xpath = sr_data->xpath; | |
176 | ||
bbeaa033 RW |
177 | DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: processing change [xpath %s]", |
178 | xpath); | |
179 | ||
a7ca2199 RW |
180 | /* Non-presence container - nothing to do. */ |
181 | if (sr_data->type == SR_CONTAINER_T) | |
182 | return NB_OK; | |
183 | ||
184 | nb_node = nb_node_find(xpath); | |
185 | if (!nb_node) { | |
186 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
187 | "%s: unknown data path: %s", __func__, xpath); | |
188 | return NB_ERR; | |
189 | } | |
190 | ||
191 | /* Map operation values. */ | |
192 | switch (sr_op) { | |
193 | case SR_OP_CREATED: | |
194 | case SR_OP_MODIFIED: | |
195 | if (nb_operation_is_valid(NB_OP_CREATE, nb_node->snode)) | |
196 | nb_op = NB_OP_CREATE; | |
197 | else if (nb_operation_is_valid(NB_OP_MODIFY, nb_node->snode)) { | |
198 | nb_op = NB_OP_MODIFY; | |
199 | } else | |
200 | /* Ignore list keys modifications. */ | |
201 | return NB_OK; | |
202 | break; | |
203 | case SR_OP_DELETED: | |
204 | /* | |
205 | * When a list is deleted or one of its keys is changed, we are | |
206 | * notified about the removal of all of its leafs, even the ones | |
207 | * that are non-optional. We need to ignore these notifications. | |
208 | */ | |
95ce849b | 209 | if (!nb_operation_is_valid(NB_OP_DESTROY, nb_node->snode)) |
a7ca2199 RW |
210 | return NB_OK; |
211 | ||
95ce849b | 212 | nb_op = NB_OP_DESTROY; |
a7ca2199 RW |
213 | break; |
214 | case SR_OP_MOVED: | |
215 | nb_op = NB_OP_MOVE; | |
216 | break; | |
217 | default: | |
218 | flog_err(EC_LIB_DEVELOPMENT, | |
219 | "%s: unexpected operation %u [xpath %s]", __func__, | |
220 | sr_op, xpath); | |
221 | return NB_ERR; | |
222 | } | |
223 | ||
224 | sr_val_to_buff(sr_data, value_str, sizeof(value_str)); | |
225 | data = yang_data_new(xpath, value_str); | |
226 | ||
227 | ret = nb_candidate_edit(candidate, nb_node, nb_op, xpath, NULL, data); | |
228 | yang_data_free(data); | |
7dac19f7 | 229 | if (ret != NB_OK && ret != NB_ERR_NOT_FOUND) { |
a7ca2199 RW |
230 | flog_warn( |
231 | EC_LIB_NB_CANDIDATE_EDIT_ERROR, | |
232 | "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", | |
233 | __func__, nb_operation_name(nb_op), xpath); | |
234 | return NB_ERR; | |
235 | } | |
236 | ||
237 | return NB_OK; | |
238 | } | |
239 | ||
24ed137c | 240 | static int frr_sr_config_change_cb_prepare(sr_session_ctx_t *session, |
bbeaa033 | 241 | const char *module_name) |
a7ca2199 RW |
242 | { |
243 | sr_change_iter_t *it; | |
244 | int ret; | |
245 | sr_change_oper_t sr_op; | |
246 | sr_val_t *sr_old_val, *sr_new_val; | |
13d6b9c1 | 247 | struct nb_context context = {}; |
a7ca2199 | 248 | struct nb_config *candidate; |
df5eda3d | 249 | char errmsg[BUFSIZ] = {0}; |
a7ca2199 | 250 | |
24ed137c | 251 | ret = sr_get_changes_iter(session, "//*", &it); |
a7ca2199 RW |
252 | if (ret != SR_ERR_OK) { |
253 | flog_err(EC_LIB_LIBSYSREPO, | |
24ed137c RW |
254 | "%s: sr_get_changes_iter() failed for \"%s\"", |
255 | __func__, module_name); | |
a7ca2199 RW |
256 | return ret; |
257 | } | |
258 | ||
8685be73 | 259 | candidate = nb_config_dup(running_config); |
a7ca2199 RW |
260 | |
261 | while ((ret = sr_get_change_next(session, it, &sr_op, &sr_old_val, | |
262 | &sr_new_val)) | |
263 | == SR_ERR_OK) { | |
264 | ret = frr_sr_process_change(candidate, sr_op, sr_old_val, | |
265 | sr_new_val); | |
266 | sr_free_val(sr_old_val); | |
267 | sr_free_val(sr_new_val); | |
268 | if (ret != NB_OK) | |
269 | break; | |
270 | } | |
271 | ||
272 | sr_free_change_iter(it); | |
273 | if (ret != NB_OK && ret != SR_ERR_NOT_FOUND) { | |
274 | nb_config_free(candidate); | |
275 | return SR_ERR_INTERNAL; | |
276 | } | |
277 | ||
88a7d121 | 278 | transaction = NULL; |
13d6b9c1 | 279 | context.client = NB_CLIENT_SYSREPO; |
bbeaa033 RW |
280 | /* |
281 | * Validate the configuration changes and allocate all resources | |
282 | * required to apply them. | |
283 | */ | |
284 | ret = nb_candidate_commit_prepare(&context, candidate, NULL, | |
285 | &transaction, errmsg, sizeof(errmsg)); | |
286 | if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) | |
287 | flog_warn( | |
288 | EC_LIB_LIBSYSREPO, | |
289 | "%s: failed to prepare configuration transaction: %s (%s)", | |
290 | __func__, nb_err_name(ret), errmsg); | |
a7ca2199 | 291 | |
24ed137c RW |
292 | if (!transaction) |
293 | nb_config_free(candidate); | |
294 | ||
a7ca2199 RW |
295 | /* Map northbound return code to sysrepo return code. */ |
296 | switch (ret) { | |
297 | case NB_OK: | |
88a7d121 | 298 | return SR_ERR_OK; |
a7ca2199 RW |
299 | case NB_ERR_NO_CHANGES: |
300 | return SR_ERR_OK; | |
301 | case NB_ERR_LOCKED: | |
302 | return SR_ERR_LOCKED; | |
303 | case NB_ERR_RESOURCE: | |
304 | return SR_ERR_NOMEM; | |
305 | default: | |
306 | return SR_ERR_VALIDATION_FAILED; | |
307 | } | |
308 | } | |
309 | ||
88a7d121 RW |
310 | static int frr_sr_config_change_cb_apply(sr_session_ctx_t *session, |
311 | const char *module_name) | |
312 | { | |
313 | /* Apply the transaction. */ | |
314 | if (transaction) { | |
315 | struct nb_config *candidate = transaction->config; | |
0fe5b904 | 316 | char errmsg[BUFSIZ] = {0}; |
88a7d121 | 317 | |
0fe5b904 RW |
318 | nb_candidate_commit_apply(transaction, true, NULL, errmsg, |
319 | sizeof(errmsg)); | |
88a7d121 RW |
320 | nb_config_free(candidate); |
321 | } | |
322 | ||
323 | return SR_ERR_OK; | |
324 | } | |
325 | ||
326 | static int frr_sr_config_change_cb_abort(sr_session_ctx_t *session, | |
327 | const char *module_name) | |
328 | { | |
329 | /* Abort the transaction. */ | |
330 | if (transaction) { | |
331 | struct nb_config *candidate = transaction->config; | |
0fe5b904 | 332 | char errmsg[BUFSIZ] = {0}; |
88a7d121 | 333 | |
0fe5b904 | 334 | nb_candidate_commit_abort(transaction, errmsg, sizeof(errmsg)); |
88a7d121 RW |
335 | nb_config_free(candidate); |
336 | } | |
337 | ||
338 | return SR_ERR_OK; | |
339 | } | |
340 | ||
341 | /* Callback for changes in the running configuration. */ | |
342 | static int frr_sr_config_change_cb(sr_session_ctx_t *session, | |
24ed137c RW |
343 | const char *module_name, const char *xpath, |
344 | sr_event_t sr_ev, uint32_t request_id, | |
345 | void *private_data) | |
88a7d121 RW |
346 | { |
347 | switch (sr_ev) { | |
348 | case SR_EV_ENABLED: | |
24ed137c | 349 | case SR_EV_CHANGE: |
bbeaa033 | 350 | return frr_sr_config_change_cb_prepare(session, module_name); |
24ed137c | 351 | case SR_EV_DONE: |
88a7d121 RW |
352 | return frr_sr_config_change_cb_apply(session, module_name); |
353 | case SR_EV_ABORT: | |
354 | return frr_sr_config_change_cb_abort(session, module_name); | |
355 | default: | |
24ed137c | 356 | flog_err(EC_LIB_LIBSYSREPO, "%s: unexpected sysrepo event: %u", |
88a7d121 RW |
357 | __func__, sr_ev); |
358 | return SR_ERR_INTERNAL; | |
359 | } | |
360 | } | |
361 | ||
3bb513c3 | 362 | static int frr_sr_state_data_iter_cb(const struct lysc_node *snode, |
1a4bc045 RW |
363 | struct yang_translator *translator, |
364 | struct yang_data *data, void *arg) | |
a7ca2199 | 365 | { |
24ed137c RW |
366 | struct lyd_node *dnode = arg; |
367 | ||
368 | ly_errno = 0; | |
369 | dnode = lyd_new_path(dnode, ly_native_ctx, data->xpath, data->value, 0, | |
370 | LYD_PATH_OPT_UPDATE); | |
371 | if (!dnode && ly_errno) { | |
372 | flog_warn(EC_LIB_LIBYANG, "%s: lyd_new_path() failed", | |
373 | __func__); | |
374 | yang_data_free(data); | |
375 | return NB_ERR; | |
376 | } | |
a7ca2199 | 377 | |
24ed137c | 378 | yang_data_free(data); |
1a4bc045 | 379 | return NB_OK; |
a7ca2199 RW |
380 | } |
381 | ||
382 | /* Callback for state retrieval. */ | |
24ed137c RW |
383 | static int frr_sr_state_cb(sr_session_ctx_t *session, const char *module_name, |
384 | const char *xpath, const char *request_xpath, | |
385 | uint32_t request_id, struct lyd_node **parent, | |
386 | void *private_ctx) | |
a7ca2199 | 387 | { |
24ed137c | 388 | struct lyd_node *dnode; |
a7ca2199 | 389 | |
24ed137c RW |
390 | dnode = *parent; |
391 | if (nb_oper_data_iterate(request_xpath, NULL, 0, | |
392 | frr_sr_state_data_iter_cb, dnode) | |
1a4bc045 RW |
393 | != NB_OK) { |
394 | flog_warn(EC_LIB_NB_OPERATIONAL_DATA, | |
395 | "%s: failed to obtain operational data [xpath %s]", | |
396 | __func__, xpath); | |
24ed137c | 397 | return SR_ERR_INTERNAL; |
a7ca2199 RW |
398 | } |
399 | ||
24ed137c | 400 | *parent = dnode; |
a7ca2199 RW |
401 | |
402 | return SR_ERR_OK; | |
403 | } | |
404 | ||
24ed137c RW |
405 | static int frr_sr_config_rpc_cb(sr_session_ctx_t *session, const char *xpath, |
406 | const sr_val_t *sr_input, | |
407 | const size_t input_cnt, sr_event_t sr_ev, | |
408 | uint32_t request_id, sr_val_t **sr_output, | |
a7ca2199 RW |
409 | size_t *sr_output_cnt, void *private_ctx) |
410 | { | |
411 | struct nb_node *nb_node; | |
412 | struct list *input; | |
413 | struct list *output; | |
414 | struct yang_data *data; | |
415 | size_t cb_output_cnt; | |
416 | int ret = SR_ERR_OK; | |
f63f5f19 | 417 | char errmsg[BUFSIZ] = {0}; |
a7ca2199 RW |
418 | |
419 | nb_node = nb_node_find(xpath); | |
420 | if (!nb_node) { | |
421 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
422 | "%s: unknown data path: %s", __func__, xpath); | |
423 | return SR_ERR_INTERNAL; | |
424 | } | |
425 | ||
426 | input = yang_data_list_new(); | |
427 | output = yang_data_list_new(); | |
428 | ||
429 | /* Process input. */ | |
430 | for (size_t i = 0; i < input_cnt; i++) { | |
431 | char value_str[YANG_VALUE_MAXLEN]; | |
432 | ||
433 | sr_val_to_buff(&sr_input[i], value_str, sizeof(value_str)); | |
434 | ||
435 | data = yang_data_new(xpath, value_str); | |
436 | listnode_add(input, data); | |
437 | } | |
438 | ||
439 | /* Execute callback registered for this XPath. */ | |
f63f5f19 CS |
440 | if (nb_callback_rpc(nb_node, xpath, input, output, errmsg, |
441 | sizeof(errmsg)) | |
442 | != NB_OK) { | |
a7ca2199 RW |
443 | flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", |
444 | __func__, xpath); | |
445 | ret = SR_ERR_OPERATION_FAILED; | |
446 | goto exit; | |
447 | } | |
448 | ||
449 | /* Process output. */ | |
450 | if (listcount(output) > 0) { | |
451 | sr_val_t *values = NULL; | |
452 | struct listnode *node; | |
453 | int i = 0; | |
454 | ||
455 | cb_output_cnt = listcount(output); | |
456 | ret = sr_new_values(cb_output_cnt, &values); | |
457 | if (ret != SR_ERR_OK) { | |
458 | flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", | |
459 | __func__, sr_strerror(ret)); | |
460 | goto exit; | |
461 | } | |
462 | ||
463 | for (ALL_LIST_ELEMENTS_RO(output, node, data)) { | |
464 | if (yang_data_frr2sr(data, &values[i++]) != 0) { | |
465 | flog_err( | |
466 | EC_LIB_SYSREPO_DATA_CONVERT, | |
467 | "%s: failed to convert data to Sysrepo format", | |
468 | __func__); | |
469 | ret = SR_ERR_INTERNAL; | |
470 | sr_free_values(values, cb_output_cnt); | |
471 | goto exit; | |
472 | } | |
473 | } | |
474 | ||
475 | *sr_output = values; | |
476 | *sr_output_cnt = cb_output_cnt; | |
477 | } | |
478 | ||
479 | exit: | |
480 | /* Release memory. */ | |
481 | list_delete(&input); | |
482 | list_delete(&output); | |
483 | ||
484 | return ret; | |
485 | } | |
486 | ||
487 | static int frr_sr_notification_send(const char *xpath, struct list *arguments) | |
488 | { | |
489 | sr_val_t *values = NULL; | |
490 | size_t values_cnt = 0; | |
491 | int ret; | |
492 | ||
493 | if (arguments && listcount(arguments) > 0) { | |
494 | struct yang_data *data; | |
495 | struct listnode *node; | |
496 | int i = 0; | |
497 | ||
498 | values_cnt = listcount(arguments); | |
499 | ret = sr_new_values(values_cnt, &values); | |
500 | if (ret != SR_ERR_OK) { | |
501 | flog_err(EC_LIB_LIBSYSREPO, "%s: sr_new_values(): %s", | |
502 | __func__, sr_strerror(ret)); | |
503 | return NB_ERR; | |
504 | } | |
505 | ||
506 | for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) { | |
507 | if (yang_data_frr2sr(data, &values[i++]) != 0) { | |
508 | flog_err( | |
509 | EC_LIB_SYSREPO_DATA_CONVERT, | |
510 | "%s: failed to convert data to sysrepo format", | |
511 | __func__); | |
512 | sr_free_values(values, values_cnt); | |
513 | return NB_ERR; | |
514 | } | |
515 | } | |
516 | } | |
517 | ||
24ed137c | 518 | ret = sr_event_notif_send(session, xpath, values, values_cnt); |
a7ca2199 RW |
519 | if (ret != SR_ERR_OK) { |
520 | flog_err(EC_LIB_LIBSYSREPO, | |
521 | "%s: sr_event_notif_send() failed for xpath %s", | |
522 | __func__, xpath); | |
523 | return NB_ERR; | |
524 | } | |
525 | ||
526 | return NB_OK; | |
527 | } | |
528 | ||
a7ca2199 RW |
529 | static int frr_sr_read_cb(struct thread *thread) |
530 | { | |
7640e3c6 | 531 | struct yang_module *module = THREAD_ARG(thread); |
a7ca2199 | 532 | int fd = THREAD_FD(thread); |
a7ca2199 RW |
533 | int ret; |
534 | ||
7640e3c6 | 535 | ret = sr_process_events(module->sr_subscription, session, NULL); |
a7ca2199 RW |
536 | if (ret != SR_ERR_OK) { |
537 | flog_err(EC_LIB_LIBSYSREPO, "%s: sr_fd_event_process(): %s", | |
538 | __func__, sr_strerror(ret)); | |
539 | return -1; | |
540 | } | |
541 | ||
7640e3c6 | 542 | thread_add_read(master, frr_sr_read_cb, module, fd, &module->sr_thread); |
a7ca2199 RW |
543 | |
544 | return 0; | |
545 | } | |
546 | ||
547 | static void frr_sr_subscribe_config(struct yang_module *module) | |
548 | { | |
549 | int ret; | |
550 | ||
bbeaa033 RW |
551 | DEBUGD(&nb_dbg_client_sysrepo, |
552 | "sysrepo: subscribing for configuration changes made in the '%s' module", | |
553 | module->name); | |
554 | ||
a7ca2199 | 555 | ret = sr_module_change_subscribe( |
24ed137c RW |
556 | session, module->name, NULL, frr_sr_config_change_cb, NULL, 0, |
557 | SR_SUBSCR_DEFAULT | SR_SUBSCR_ENABLED | SR_SUBSCR_NO_THREAD, | |
a7ca2199 RW |
558 | &module->sr_subscription); |
559 | if (ret != SR_ERR_OK) | |
560 | flog_err(EC_LIB_LIBSYSREPO, "sr_module_change_subscribe(): %s", | |
561 | sr_strerror(ret)); | |
562 | } | |
563 | ||
3bb513c3 | 564 | static int frr_sr_subscribe_state(const struct lysc_node *snode, void *arg) |
a7ca2199 | 565 | { |
e0ccfad2 | 566 | struct yang_module *module = arg; |
a7ca2199 RW |
567 | struct nb_node *nb_node; |
568 | int ret; | |
569 | ||
db452508 | 570 | if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R)) |
e0ccfad2 | 571 | return YANG_ITER_CONTINUE; |
a7ca2199 | 572 | /* We only need to subscribe to the root of the state subtrees. */ |
db452508 | 573 | if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R)) |
e0ccfad2 | 574 | return YANG_ITER_CONTINUE; |
a7ca2199 RW |
575 | |
576 | nb_node = snode->priv; | |
9bde0b25 RW |
577 | if (!nb_node) |
578 | return YANG_ITER_CONTINUE; | |
9eb2c0a1 | 579 | |
bbeaa033 | 580 | DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: providing data to '%s'", |
9eb2c0a1 | 581 | nb_node->xpath); |
a7ca2199 | 582 | |
24ed137c RW |
583 | ret = sr_oper_get_items_subscribe( |
584 | session, snode->module->name, nb_node->xpath, frr_sr_state_cb, | |
585 | NULL, SR_SUBSCR_CTX_REUSE, &module->sr_subscription); | |
a7ca2199 | 586 | if (ret != SR_ERR_OK) |
24ed137c | 587 | flog_err(EC_LIB_LIBSYSREPO, "sr_oper_get_items_subscribe(): %s", |
a7ca2199 | 588 | sr_strerror(ret)); |
e0ccfad2 RW |
589 | |
590 | return YANG_ITER_CONTINUE; | |
a7ca2199 RW |
591 | } |
592 | ||
3bb513c3 | 593 | static int frr_sr_subscribe_rpc(const struct lysc_node *snode, void *arg) |
a7ca2199 | 594 | { |
e0ccfad2 | 595 | struct yang_module *module = arg; |
a7ca2199 RW |
596 | struct nb_node *nb_node; |
597 | int ret; | |
598 | ||
599 | if (snode->nodetype != LYS_RPC) | |
e0ccfad2 | 600 | return YANG_ITER_CONTINUE; |
a7ca2199 RW |
601 | |
602 | nb_node = snode->priv; | |
9bde0b25 RW |
603 | if (!nb_node) |
604 | return YANG_ITER_CONTINUE; | |
9eb2c0a1 | 605 | |
bbeaa033 | 606 | DEBUGD(&nb_dbg_client_sysrepo, "sysrepo: providing RPC to '%s'", |
9eb2c0a1 | 607 | nb_node->xpath); |
a7ca2199 RW |
608 | |
609 | ret = sr_rpc_subscribe(session, nb_node->xpath, frr_sr_config_rpc_cb, | |
24ed137c | 610 | NULL, 0, SR_SUBSCR_CTX_REUSE, |
a7ca2199 RW |
611 | &module->sr_subscription); |
612 | if (ret != SR_ERR_OK) | |
613 | flog_err(EC_LIB_LIBSYSREPO, "sr_rpc_subscribe(): %s", | |
614 | sr_strerror(ret)); | |
e0ccfad2 RW |
615 | |
616 | return YANG_ITER_CONTINUE; | |
a7ca2199 RW |
617 | } |
618 | ||
9eb2c0a1 RW |
619 | /* CLI commands. */ |
620 | DEFUN (debug_nb_sr, | |
621 | debug_nb_sr_cmd, | |
622 | "[no] debug northbound client sysrepo", | |
623 | NO_STR | |
624 | DEBUG_STR | |
625 | "Northbound debugging\n" | |
626 | "Northbound client\n" | |
627 | "Sysrepo\n") | |
628 | { | |
629 | uint32_t mode = DEBUG_NODE2MODE(vty->node); | |
630 | bool no = strmatch(argv[0]->text, "no"); | |
631 | ||
632 | DEBUG_MODE_SET(&nb_dbg_client_sysrepo, mode, !no); | |
633 | ||
634 | return CMD_SUCCESS; | |
635 | } | |
636 | ||
637 | static int frr_sr_debug_config_write(struct vty *vty) | |
638 | { | |
639 | if (DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo, DEBUG_MODE_CONF)) | |
640 | vty_out(vty, "debug northbound client sysrepo\n"); | |
641 | ||
642 | return 0; | |
643 | } | |
644 | ||
645 | static int frr_sr_debug_set_all(uint32_t flags, bool set) | |
646 | { | |
647 | DEBUG_FLAGS_SET(&nb_dbg_client_sysrepo, flags, set); | |
648 | ||
649 | /* If all modes have been turned off, don't preserve options. */ | |
650 | if (!DEBUG_MODE_CHECK(&nb_dbg_client_sysrepo, DEBUG_MODE_ALL)) | |
651 | DEBUG_CLEAR(&nb_dbg_client_sysrepo); | |
652 | ||
653 | return 0; | |
654 | } | |
655 | ||
656 | static void frr_sr_cli_init(void) | |
657 | { | |
658 | hook_register(nb_client_debug_config_write, frr_sr_debug_config_write); | |
659 | hook_register(nb_client_debug_set_all, frr_sr_debug_set_all); | |
660 | ||
661 | install_element(ENABLE_NODE, &debug_nb_sr_cmd); | |
662 | install_element(CONFIG_NODE, &debug_nb_sr_cmd); | |
663 | } | |
664 | ||
a7ca2199 | 665 | /* FRR's Sysrepo initialization. */ |
24ed137c | 666 | static int frr_sr_init(void) |
a7ca2199 RW |
667 | { |
668 | struct yang_module *module; | |
24ed137c | 669 | int ret; |
a7ca2199 RW |
670 | |
671 | /* Connect to Sysrepo. */ | |
24ed137c | 672 | ret = sr_connect(SR_CONN_DEFAULT, &connection); |
a7ca2199 RW |
673 | if (ret != SR_ERR_OK) { |
674 | flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_connect(): %s", __func__, | |
675 | sr_strerror(ret)); | |
676 | goto cleanup; | |
677 | } | |
678 | ||
679 | /* Start session. */ | |
24ed137c | 680 | ret = sr_session_start(connection, SR_DS_RUNNING, &session); |
a7ca2199 RW |
681 | if (ret != SR_ERR_OK) { |
682 | flog_err(EC_LIB_SYSREPO_INIT, "%s: sr_session_start(): %s", | |
683 | __func__, sr_strerror(ret)); | |
684 | goto cleanup; | |
685 | } | |
686 | ||
687 | /* Perform subscriptions. */ | |
688 | RB_FOREACH (module, yang_modules, &yang_modules) { | |
24ed137c RW |
689 | int event_pipe; |
690 | ||
a7ca2199 | 691 | frr_sr_subscribe_config(module); |
8d869d37 RW |
692 | yang_snodes_iterate(module->info, frr_sr_subscribe_state, 0, |
693 | module); | |
694 | yang_snodes_iterate(module->info, frr_sr_subscribe_rpc, 0, | |
695 | module); | |
24ed137c RW |
696 | |
697 | /* Watch subscriptions. */ | |
698 | ret = sr_get_event_pipe(module->sr_subscription, &event_pipe); | |
699 | if (ret != SR_ERR_OK) { | |
700 | flog_err(EC_LIB_SYSREPO_INIT, | |
701 | "%s: sr_get_event_pipe(): %s", __func__, | |
702 | sr_strerror(ret)); | |
703 | goto cleanup; | |
704 | } | |
7640e3c6 | 705 | thread_add_read(master, frr_sr_read_cb, module, |
24ed137c | 706 | event_pipe, &module->sr_thread); |
a7ca2199 RW |
707 | } |
708 | ||
709 | hook_register(nb_notification_send, frr_sr_notification_send); | |
710 | ||
a7ca2199 RW |
711 | return 0; |
712 | ||
713 | cleanup: | |
714 | frr_sr_finish(); | |
715 | ||
716 | return -1; | |
717 | } | |
718 | ||
719 | static int frr_sr_finish(void) | |
720 | { | |
721 | struct yang_module *module; | |
722 | ||
723 | RB_FOREACH (module, yang_modules, &yang_modules) { | |
724 | if (!module->sr_subscription) | |
725 | continue; | |
24ed137c RW |
726 | sr_unsubscribe(module->sr_subscription); |
727 | THREAD_OFF(module->sr_thread); | |
a7ca2199 RW |
728 | } |
729 | ||
730 | if (session) | |
731 | sr_session_stop(session); | |
732 | if (connection) | |
733 | sr_disconnect(connection); | |
734 | ||
a7ca2199 RW |
735 | return 0; |
736 | } | |
737 | ||
2bafda27 | 738 | static int frr_sr_module_config_loaded(struct thread_master *tm) |
a7ca2199 RW |
739 | { |
740 | master = tm; | |
741 | ||
24ed137c | 742 | if (frr_sr_init() < 0) { |
a7ca2199 RW |
743 | flog_err(EC_LIB_SYSREPO_INIT, |
744 | "failed to initialize the Sysrepo module"); | |
745 | return -1; | |
746 | } | |
747 | ||
748 | hook_register(frr_fini, frr_sr_finish); | |
88e635ee RW |
749 | |
750 | return 0; | |
751 | } | |
752 | ||
753 | static int frr_sr_module_late_init(struct thread_master *tm) | |
754 | { | |
9eb2c0a1 | 755 | frr_sr_cli_init(); |
a7ca2199 RW |
756 | |
757 | return 0; | |
758 | } | |
759 | ||
760 | static int frr_sr_module_init(void) | |
761 | { | |
762 | hook_register(frr_late_init, frr_sr_module_late_init); | |
2bafda27 | 763 | hook_register(frr_config_post, frr_sr_module_config_loaded); |
a7ca2199 RW |
764 | |
765 | return 0; | |
766 | } | |
767 | ||
768 | FRR_MODULE_SETUP(.name = "frr_sysrepo", .version = FRR_VERSION, | |
769 | .description = "FRR sysrepo integration module", | |
80413c20 DL |
770 | .init = frr_sr_module_init, |
771 | ); |