1 /*
2 *
3 ***** BEGIN LICENSE BLOCK *****
4
5 Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
6 Copyright (C) 2017-2019 Olof Hagsand
7 Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate)
8
9 This file is part of CLIXON.
10
11 Licensed under the Apache License, Version 2.0 (the "License");
12 you may not use this file except in compliance with the License.
13 You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22
23 Alternatively, the contents of this file may be used under the terms of
24 the GNU General Public License Version 3 or later (the "GPL"),
25 in which case the provisions of the GPL are applicable instead
26 of those above. If you wish to allow use of your version of this file only
27 under the terms of the GPL, and not to allow others to
28 use your version of this file under the terms of Apache License version 2, indicate
29 your decision by deleting the provisions above and replace them with the
30 notice and other provisions required by the GPL. If you do not delete
31 the provisions above, a recipient may use your version of this file under
32 the terms of any one of the Apache License version 2 or the GPL.
33
34 ***** END LICENSE BLOCK *****
35
36 * The example have the following optional arguments that you can pass as
37 * argc/argv after -- in clixon_backend:
38 * -r enable the reset function
39 * -s enable the state function
40 * -S <file> read state data from file, otherwise construct it programmatically (requires -s)
41 * -i read state file on init not by request for optimization (requires -sS <file>)
42 * -u enable upgrade function - auto-upgrade testing
43 * -U general-purpose upgrade
44 * -t enable transaction logging (cal syslog for every transaction)
45 * -v <xpath> Failing validate and commit if <xpath> is present (synthetic error)
46 */
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <stdint.h>
50 #include <inttypes.h>
51 #include <string.h>
52 #include <errno.h>
53 #include <signal.h>
54 #include <unistd.h>
55 #include <syslog.h>
56 #include <fcntl.h>
57 #include <sys/time.h>
58
59 /* clicon */
60 #include <cligen/cligen.h>
61
62 /* Clicon library functions. */
63 #include <clixon/clixon.h>
64
65 /* These include signatures for plugin and transaction callbacks. */
66 #include <clixon/clixon_backend.h>
67
68 /* Command line options to be passed to getopt(3) */
69 #define BACKEND_EXAMPLE_OPTS "rsS:iuUt:v:"
70
71 /*! Variable to control if reset code is run.
72 * The reset code inserts "extra XML" which assumes ietf-interfaces is
73 * loaded, and this is not always the case.
74 * Start backend with -- -r
75 */
76 static int _reset = 0;
77
78 /*! Variable to control if state code is run
79 * The state code adds extra non-config data
80 * Start backend with -- -s
81 */
82 static int _state = 0;
83
84 /*! File where state XML is read from, if _state is true -- -sS <file>
85 * Primarily for testing
86 * Start backend with -- -sS <file>
87 */
88 static char *_state_file = NULL;
89
90 /*! Read state file init on startup instead of on request
91 * Primarily for testing
92 * Start backend with -- -siS <file>
93 */
94 static int _state_file_init = 0;
95 static cxobj *_state_xstate = NULL;
96
97 /*! Variable to control module-specific upgrade callbacks.
98 * If set, call test-case for upgrading ietf-interfaces, otherwise call
99 * auto-upgrade
100 * Start backend with -- -u
101 */
102 static int _module_upgrade = 0;
103
104 /*! Variable to control general-purpose upgrade callbacks.
105 * Start backend with -- -U
106 */
107 static int _general_upgrade = 0;
108
109 /*! Variable to control transaction logging (for debug)
110 * If set, call syslog for every transaction callback
111 * Start backend with -- -t
112 */
113 static int _transaction_log = 0;
114
115 /*! Variable to control transaction logging (for debug)
116 * If set, call syslog for every transaction callback
117 * Start backend with -- -v <xpath>
118 */
119 static char *_validate_fail_xpath = NULL;
120 static int _validate_fail_toggle = 0; /* fail at validate and commit */
121
122 /* forward */
123 static int example_stream_timer_setup(clicon_handle h);
124
125 int
main_begin(clicon_handle h,transaction_data td)126 main_begin(clicon_handle h,
127 transaction_data td)
128 {
129 if (_transaction_log)
130 transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
131 return 0;
132 }
133 /*! This is called on validate (and commit). Check validity of candidate
134 */
135 int
main_validate(clicon_handle h,transaction_data td)136 main_validate(clicon_handle h,
137 transaction_data td)
138 {
139 if (_transaction_log)
140 transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
141 if (_validate_fail_xpath){
142 if (_validate_fail_toggle==0 &&
143 xpath_first(transaction_target(td), NULL, "%s", _validate_fail_xpath)){
144 _validate_fail_toggle = 1; /* toggle if triggered */
145 clicon_err(OE_XML, 0, "User error");
146 return -1; /* induce fail */
147 }
148 }
149 return 0;
150 }
151
152 int
main_complete(clicon_handle h,transaction_data td)153 main_complete(clicon_handle h,
154 transaction_data td)
155 {
156 if (_transaction_log)
157 transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
158 return 0;
159 }
160
161 /*! This is called on commit. Identify modifications and adjust machine state
162 */
163 int
main_commit(clicon_handle h,transaction_data td)164 main_commit(clicon_handle h,
165 transaction_data td)
166 {
167 cxobj *target = transaction_target(td); /* wanted XML tree */
168 cxobj **vec = NULL;
169 int i;
170 int len;
171 cvec *nsc = NULL;
172
173 if (_transaction_log)
174 transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
175
176 if (_validate_fail_xpath){
177 if (_validate_fail_toggle==1 &&
178 xpath_first(transaction_target(td), NULL, "%s", _validate_fail_xpath)){
179 _validate_fail_toggle = 0; /* toggle if triggered */
180 clicon_err(OE_XML, 0, "User error");
181 return -1; /* induce fail */
182 }
183 }
184
185 /* Create namespace context for xpath */
186 if ((nsc = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL)
187 goto done;
188
189 /* Get all added i/fs */
190 if (xpath_vec_flag(target, nsc, "//interface", XML_FLAG_ADD, &vec, &len) < 0)
191 return -1;
192 if (clicon_debug_get())
193 for (i=0; i<len; i++) /* Loop over added i/fs */
194 xml_print(stdout, vec[i]); /* Print the added interface */
195 done:
196 if (nsc)
197 xml_nsctx_free(nsc);
198 if (vec)
199 free(vec);
200 return 0;
201 }
202
203 int
main_commit_done(clicon_handle h,transaction_data td)204 main_commit_done(clicon_handle h,
205 transaction_data td)
206 {
207 if (_transaction_log)
208 transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
209 return 0;
210 }
211
212 int
main_revert(clicon_handle h,transaction_data td)213 main_revert(clicon_handle h,
214 transaction_data td)
215 {
216 if (_transaction_log)
217 transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
218 return 0;
219 }
220
221 int
main_end(clicon_handle h,transaction_data td)222 main_end(clicon_handle h,
223 transaction_data td)
224 {
225 if (_transaction_log)
226 transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
227 return 0;
228 }
229
230 int
main_abort(clicon_handle h,transaction_data td)231 main_abort(clicon_handle h,
232 transaction_data td)
233 {
234 if (_transaction_log)
235 transaction_log(h, td, LOG_NOTICE, __FUNCTION__);
236 return 0;
237 }
238
239 /*! Routing example notification timer handler. Here is where the periodic action is
240 */
241 static int
example_stream_timer(int fd,void * arg)242 example_stream_timer(int fd,
243 void *arg)
244 {
245 int retval = -1;
246 clicon_handle h = (clicon_handle)arg;
247
248 /* XXX Change to actual netconf notifications and namespace */
249 if (stream_notify(h, "EXAMPLE", "<event xmlns=\"urn:example:clixon\"><event-class>fault</event-class><reportingEntity><card>Ethernet0</card></reportingEntity><severity>major</severity></event>") < 0)
250 goto done;
251 if (example_stream_timer_setup(h) < 0)
252 goto done;
253 retval = 0;
254 done:
255 return retval;
256 }
257
258 /*! Set up example stream notification timer
259 */
260 static int
example_stream_timer_setup(clicon_handle h)261 example_stream_timer_setup(clicon_handle h)
262 {
263 struct timeval t, t1;
264
265 gettimeofday(&t, NULL);
266 t1.tv_sec = 5; t1.tv_usec = 0;
267 timeradd(&t, &t1, &t);
268 return clixon_event_reg_timeout(t, example_stream_timer, h, "example stream timer");
269 }
270
271 /*! Smallest possible RPC declaration for test
272 * Yang/XML:
273 * If the RPC operation invocation succeeded and no output parameters
274 * are returned, the <rpc-reply> contains a single <ok/> element defined
275 * in [RFC6241].
276 */
277 static int
empty_rpc(clicon_handle h,cxobj * xe,cbuf * cbret,void * arg,void * regarg)278 empty_rpc(clicon_handle h, /* Clicon handle */
279 cxobj *xe, /* Request: <rpc><xn></rpc> */
280 cbuf *cbret, /* Reply eg <rpc-reply>... */
281 void *arg, /* client_entry */
282 void *regarg) /* Argument given at register */
283 {
284 cprintf(cbret, "<rpc-reply xmlns=\"%s\"><ok/></rpc-reply>", NETCONF_BASE_NAMESPACE);
285 return 0;
286 }
287
288 /*! More elaborate example RPC for testing
289 * The RPC returns the incoming parameters
290 */
291 static int
example_rpc(clicon_handle h,cxobj * xe,cbuf * cbret,void * arg,void * regarg)292 example_rpc(clicon_handle h, /* Clicon handle */
293 cxobj *xe, /* Request: <rpc><xn></rpc> */
294 cbuf *cbret, /* Reply eg <rpc-reply>... */
295 void *arg, /* client_entry */
296 void *regarg) /* Argument given at register */
297 {
298 int retval = -1;
299 cxobj *x = NULL;
300 char *namespace;
301
302 /* get namespace from rpc name, return back in each output parameter */
303 if ((namespace = xml_find_type_value(xe, NULL, "xmlns", CX_ATTR)) == NULL){
304 clicon_err(OE_XML, ENOENT, "No namespace given in rpc %s", xml_name(xe));
305 goto done;
306 }
307 cprintf(cbret, "<rpc-reply xmlns=\"%s\">", NETCONF_BASE_NAMESPACE);
308 if (!xml_child_nr_type(xe, CX_ELMNT))
309 cprintf(cbret, "<ok/>");
310 else while ((x = xml_child_each(xe, x, CX_ELMNT)) != NULL) {
311 if (xmlns_set(x, NULL, namespace) < 0)
312 goto done;
313 if (clicon_xml2cbuf(cbret, x, 0, 0, -1) < 0)
314 goto done;
315 }
316 cprintf(cbret, "</rpc-reply>");
317 retval = 0;
318 done:
319 return retval;
320 }
321
322 /*! This will be called as a hook right after the original system copy-config
323 */
324 static int
example_copy_extra(clicon_handle h,cxobj * xe,cbuf * cbret,void * arg,void * regarg)325 example_copy_extra(clicon_handle h, /* Clicon handle */
326 cxobj *xe, /* Request: <rpc><xn></rpc> */
327 cbuf *cbret, /* Reply eg <rpc-reply>... */
328 void *arg, /* client_entry */
329 void *regarg) /* Argument given at register */
330 {
331 int retval = -1;
332
333 // fprintf(stderr, "%s\n", __FUNCTION__);
334 retval = 0;
335 // done:
336 return retval;
337 }
338
339 /*! Called to get state data from plugin
340 * @param[in] h Clicon handle
341 * @param[in] nsc External XML namespace context, or NULL
342 * @param[in] xpath String with XPATH syntax. or NULL for all
343 * @param[in] xstate XML tree, <config/> on entry.
344 * @retval 0 OK
345 * @retval -1 Error
346 * @see xmldb_get
347 * @note this example code returns requires this yang snippet:
348 container state {
349 config false;
350 description "state data for example application";
351 leaf-list op {
352 type string;
353 }
354 }
355 * This yang snippet is present in clixon-example.yang for example.
356 */
357 int
example_statedata(clicon_handle h,cvec * nsc,char * xpath,cxobj * xstate)358 example_statedata(clicon_handle h,
359 cvec *nsc,
360 char *xpath,
361 cxobj *xstate)
362 {
363 int retval = -1;
364 cxobj **xvec = NULL;
365 size_t xlen = 0;
366 cbuf *cb = cbuf_new();
367 int i;
368 cxobj *xt = NULL;
369 char *name;
370 cvec *nsc1 = NULL;
371 cvec *nsc2 = NULL;
372 yang_stmt *yspec = NULL;
373 int fd;
374
375 if (!_state)
376 goto ok;
377 yspec = clicon_dbspec_yang(h);
378
379 /* If -S is set, then read state data from file, otherwise construct it programmatically */
380 if (_state_file){
381 if (_state_file_init){
382 #if 0 /* This is just for a zero-copy version (only works once) */
383 {
384 cxobj *xx = NULL;
385 while (xml_child_nr(_state_xstate)){
386 xx = xml_child_i(_state_xstate,0);
387 if (xml_addsub(xstate, xx) < 0)
388 goto done;
389 }
390 }
391 #else
392 if (xml_copy(_state_xstate, xstate) < 0)
393 goto done;
394 #endif
395 }
396 else{
397 cxobj *x1;
398 if ((fd = open(_state_file, O_RDONLY)) < 0){
399 clicon_err(OE_UNIX, errno, "open(%s)", _state_file);
400 goto done;
401 }
402 if ((xt = xml_new("config", NULL, CX_ELMNT)) == NULL)
403 goto done;
404 if (clixon_xml_parse_file(fd, YB_MODULE, yspec, NULL, &xt, NULL) < 0)
405 goto done;
406 close(fd);
407 if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath) < 0)
408 goto done;
409 for (i=0; i<xlen; i++){
410 x1 = xvec[i];
411 xml_flag_set(x1, XML_FLAG_MARK);
412 }
413 /* Remove everything that is not marked */
414 if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
415 goto done;
416 for (i=0; i<xlen; i++){
417 x1 = xvec[i];
418 xml_flag_reset(x1, XML_FLAG_MARK);
419 }
420 if (xml_copy(xt, xstate) < 0)
421 goto done;
422 if (xvec){
423 free(xvec);
424 xvec = 0;
425 xlen = 0;
426 }
427 }
428 }
429 else {
430 /* Example of statedata, in this case merging state data with
431 * state information. In this case adding dummy interface operation state
432 * to configured interfaces.
433 * Get config according to xpath */
434 if ((nsc1 = xml_nsctx_init(NULL, "urn:ietf:params:xml:ns:yang:ietf-interfaces")) == NULL)
435 goto done;
436 if (xmldb_get0(h, "running", YB_MODULE, nsc1, "/interfaces/interface/name", 1, &xt, NULL) < 0)
437 goto done;
438 if (xpath_vec(xt, nsc1, "/interfaces/interface/name", &xvec, &xlen) < 0)
439 goto done;
440 if (xlen){
441 cprintf(cb, "<interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">");
442 for (i=0; i<xlen; i++){
443 name = xml_body(xvec[i]);
444 cprintf(cb, "<interface xmlns:ex=\"urn:example:clixon\"><name>%s</name><type>ex:eth</type><oper-status>up</oper-status>", name);
445 cprintf(cb, "<ex:my-status><ex:int>42</ex:int><ex:str>foo</ex:str></ex:my-status>");
446 cprintf(cb, "</interface>");
447 }
448 cprintf(cb, "</interfaces>");
449 if (clixon_xml_parse_string(cbuf_get(cb), YB_NONE, NULL, &xstate, NULL) < 0)
450 goto done;
451 }
452 /* State in test_yang.sh , test_restconf.sh and test_order.sh */
453 if (yang_find_module_by_namespace(yspec, "urn:example:clixon") != NULL){
454 if (clixon_xml_parse_string("<state xmlns=\"urn:example:clixon\">"
455 "<op>42</op>"
456 "<op>41</op>"
457 "<op>43</op>" /* should not be ordered */
458 "</state>",
459 YB_NONE,
460 NULL, &xstate, NULL) < 0)
461 goto done; /* For the case when urn:example:clixon is not loaded */
462 }
463 /* Event state from RFC8040 Appendix B.3.1
464 * Note: (1) order is by-system so is different,
465 * (2) event-count is XOR on name, so is not 42 and 4
466 */
467 if (yang_find_module_by_namespace(yspec, "urn:example:events") != NULL){
468 cbuf_reset(cb);
469 cprintf(cb, "<events xmlns=\"urn:example:events\">");
470 cprintf(cb, "<event><name>interface-down</name><event-count>90</event-count></event>");
471 cprintf(cb, "<event><name>interface-up</name><event-count>77</event-count></event>");
472 cprintf(cb, "</events>");
473 if (clixon_xml_parse_string(cbuf_get(cb), YB_NONE, NULL, &xstate, NULL) < 0)
474 goto done;
475 }
476 }
477 ok:
478 retval = 0;
479 done:
480 if (nsc1)
481 xml_nsctx_free(nsc1);
482 if (nsc2)
483 xml_nsctx_free(nsc2);
484 if (xt)
485 xml_free(xt);
486 if (cb)
487 cbuf_free(cb);
488 if (xvec)
489 free(xvec);
490 return retval;
491 }
492
493 /*! Callback for yang extensions example:e4
494 *
495 * @param[in] h Clixon handle
496 * @param[in] yext Yang node of extension
497 * @param[in] ys Yang node of (unknown) statement belonging to extension
498 * @retval 0 OK, all callbacks executed OK
499 * @retval -1 Error in one callback
500 */
501 int
example_extension(clicon_handle h,yang_stmt * yext,yang_stmt * ys)502 example_extension(clicon_handle h,
503 yang_stmt *yext,
504 yang_stmt *ys)
505 {
506 int retval = -1;
507 char *extname;
508 char *modname;
509 yang_stmt *ymod;
510 yang_stmt *yc;
511 yang_stmt *yn = NULL;
512
513 ymod = ys_module(yext);
514 modname = yang_argument_get(ymod);
515 extname = yang_argument_get(yext);
516 if (strcmp(modname, "example") != 0 || strcmp(extname, "e4") != 0)
517 goto ok;
518 clicon_debug(1, "%s Enabled extension:%s:%s", __FUNCTION__, modname, extname);
519 if ((yc = yang_find(ys, 0, NULL)) == NULL)
520 goto ok;
521 if ((yn = ys_dup(yc)) == NULL)
522 goto done;
523 if (yn_insert(yang_parent_get(ys), yn) < 0)
524 goto done;
525 ok:
526 retval = 0;
527 done:
528 return retval;
529 }
530
531 /* Here follows code for general-purpose datastore upgrade
532 * Nodes affected are identified by paths.
533 * In this example nodes' namespaces are changed, or they are removed altogether
534 * @note Order is significant, the rules are traversed in the order stated here, which means that if
535 * namespaces changed, or objects are removed in one rule, you have to take that into account
536 * in the next rule.
537 */
538 /* Remove these paths */
539 static const char *remove_map[] = {
540 "/a:remove_me",
541 /* add more paths to be deleted here */
542 NULL
543 };
544
545 /* Rename the namespaces of these paths.
546 * That is, paths (on the left) should get namespaces (to the right)
547 */
548 static const map_str2str namespace_map[] = {
549 {"/a:x/a:y/a:z/descendant-or-self::node()", "urn:example:b"},
550 /* add more paths to be renamed here */
551 {NULL, NULL}
552 };
553
554 /*! General-purpose datastore upgrade callback called once on startup
555 *
556 * Gets called on startup after initial XML parsing, but before module-specific upgrades
557 * and before validation.
558 * @param[in] h Clicon handle
559 * @param[in] db Name of datastore, eg "running", "startup" or "tmp"
560 * @param[in] xt XML tree. Upgrade this "in place"
561 * @param[in] msd Info on datastore module-state, if any
562 * @retval -1 Error
563 * @retval 0 OK
564 */
565 int
example_upgrade(clicon_handle h,const char * db,cxobj * xt,modstate_diff_t * msd)566 example_upgrade(clicon_handle h,
567 const char *db,
568 cxobj *xt,
569 modstate_diff_t *msd)
570 {
571 int retval = -1;
572 cvec *nsc = NULL; /* Canonical namespace */
573 yang_stmt *yspec;
574 const struct map_str2str *ms; /* map iterator */
575 cxobj **xvec = NULL; /* vector of result nodes */
576 size_t xlen;
577 int i;
578 const char **pp;
579
580 if (_general_upgrade == 0)
581 goto ok;
582 if (strcmp(db, "startup") != 0) /* skip other than startup datastore */
583 goto ok;
584 if (msd && msd->md_status) /* skip if there is proper module-state in datastore */
585 goto ok;
586 yspec = clicon_dbspec_yang(h); /* Get all yangs */
587 /* Get canonical namespaces for using "normalized" prefixes */
588 if (xml_nsctx_yangspec(yspec, &nsc) < 0)
589 goto done;
590 /* 1. Remove paths */
591 for (pp = remove_map; *pp; ++pp){
592 /* Find all nodes matching n */
593 if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, *pp) < 0)
594 goto done;
595 /* Remove them */
596 /* Loop through all nodes matching mypath and change theoir namespace */
597 for (i=0; i<xlen; i++){
598 if (xml_purge(xvec[i]) < 0)
599 goto done;
600 }
601 if (xvec){
602 free(xvec);
603 xvec = NULL;
604 }
605 }
606 /* 2. Rename namespaces of the paths declared in the namespace map
607 */
608 for (ms = &namespace_map[0]; ms->ms_s0; ms++){
609 char *mypath;
610 char *mynamespace;
611 char *myprefix = NULL;
612
613 mypath = ms->ms_s0;
614 mynamespace = ms->ms_s1;
615 if (xml_nsctx_get_prefix(nsc, mynamespace, &myprefix) == 0){
616 clicon_err(OE_XML, ENOENT, "Namespace %s not found in canonical namespace map",
617 mynamespace);
618 goto done;
619 }
620 /* Find all nodes matching mypath */
621 if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, mypath) < 0)
622 goto done;
623 /* Loop through all nodes matching mypath and change theoir namespace */
624 for (i=0; i<xlen; i++){
625 /* Change namespace of this node (using myprefix) */
626 if (xml_namespace_change(xvec[i], mynamespace, myprefix) < 0)
627 goto done;
628 }
629 if (xvec){
630 free(xvec);
631 xvec = NULL;
632 }
633 }
634 ok:
635 retval = 0;
636 done:
637 if (xvec)
638 free(xvec);
639 if (nsc)
640 cvec_free(nsc);
641 return retval;
642 }
643
644 /*! Testcase module-specific upgrade function moving interfaces-state to interfaces
645 * @param[in] h Clicon handle
646 * @param[in] xn XML tree to be updated
647 * @param[in] ns Namespace of module (for info)
648 * @param[in] op One of XML_FLAG_ADD, _DEL, _CHANGE
649 * @param[in] from From revision on the form YYYYMMDD
650 * @param[in] to To revision on the form YYYYMMDD (0 not in system)
651 * @param[in] arg User argument given at rpc_callback_register()
652 * @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error.. if retval = 0
653 * @retval 1 OK
654 * @retval 0 Invalid
655 * @retval -1 Error
656 * @see clicon_upgrade_cb
657 * @see test_upgrade_interfaces.sh
658 * @see upgrade_2014_to_2016
659 * This example shows a two-step upgrade where the 2014 function does:
660 * - Move /if:interfaces-state/if:interface/if:admin-status to
661 * /if:interfaces/if:interface/
662 * - Move /if:interfaces-state/if:interface/if:statistics to
663 * /if:interfaces/if:interface/
664 * - Rename /interfaces/interface/description to descr
665 */
666 static int
upgrade_2014_to_2016(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,void * arg,cbuf * cbret)667 upgrade_2014_to_2016(clicon_handle h,
668 cxobj *xt,
669 char *ns,
670 uint16_t op,
671 uint32_t from,
672 uint32_t to,
673 void *arg,
674 cbuf *cbret)
675 {
676 int retval = -1;
677 yang_stmt *yspec;
678 yang_stmt *ym;
679 cxobj **vec = NULL;
680 cxobj *xc;
681 cxobj *xi; /* xml /interfaces-states/interface node */
682 cxobj *x;
683 cxobj *xif; /* xml /interfaces/interface node */
684 size_t vlen;
685 int i;
686 char *name;
687
688 clicon_debug(1, "%s from:%d to:%d", __FUNCTION__, from, to);
689 if (op != XML_FLAG_CHANGE) /* Only treat fully present modules */
690 goto ok;
691 /* Get Yang module for this namespace. Note it may not exist (if obsolete) */
692 yspec = clicon_dbspec_yang(h);
693 if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL)
694 goto ok; /* shouldnt happen */
695 /* Get all XML nodes with that namespace */
696 if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0)
697 goto done;
698 for (i=0; i<vlen; i++){
699 xc = vec[i];
700 /* Iterate through interfaces-state */
701 if (strcmp(xml_name(xc),"interfaces-state") == 0){
702 /* Note you cannot delete or move xml objects directly under xc
703 * in the loop (eg xi objects) but you CAN move children of xi
704 */
705 xi = NULL;
706 while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
707 if (strcmp(xml_name(xi), "interface"))
708 continue;
709 if ((name = xml_find_body(xi, "name")) == NULL)
710 continue; /* shouldnt happen */
711 /* Get corresponding /interfaces/interface entry */
712 xif = xpath_first(xt, NULL, "/interfaces/interface[name=\"%s\"]", name);
713 /* - Move /if:interfaces-state/if:interface/if:admin-status to
714 * /if:interfaces/if:interface/ */
715 if ((x = xml_find(xi, "admin-status")) != NULL && xif){
716 if (xml_addsub(xif, x) < 0)
717 goto done;
718 }
719 /* - Move /if:interfaces-state/if:interface/if:statistics to
720 * /if:interfaces/if:interface/*/
721 if ((x = xml_find(xi, "statistics")) != NULL){
722 if (xml_addsub(xif, x) < 0)
723 goto done;
724 }
725 }
726 }
727 else if (strcmp(xml_name(xc),"interfaces") == 0){
728 /* Iterate through interfaces */
729 xi = NULL;
730 while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
731 if (strcmp(xml_name(xi), "interface"))
732 continue;
733 /* Rename /interfaces/interface/description to descr */
734 if ((x = xml_find(xi, "description")) != NULL)
735 if (xml_name_set(x, "descr") < 0)
736 goto done;
737 }
738 }
739 }
740 ok:
741 retval = 1;
742 done:
743 if (vec)
744 free(vec);
745 return retval;
746 }
747
748 /*! Testcase upgrade function removing interfaces-state
749 * @param[in] h Clicon handle
750 * @param[in] xn XML tree to be updated
751 * @param[in] ns Namespace of module (for info)
752 * @param[in] op One of XML_FLAG_ADD, _DEL, _CHANGE
753 * @param[in] from From revision on the form YYYYMMDD
754 * @param[in] to To revision on the form YYYYMMDD (0 not in system)
755 * @param[in] arg User argument given at rpc_callback_register()
756 * @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error.. if retval = 0
757 * @retval 1 OK
758 * @retval 0 Invalid
759 * @retval -1 Error
760 * @see clicon_upgrade_cb
761 * @see test_upgrade_interfaces.sh
762 * @see upgrade_2016_to_2018
763 * The 2016 function does:
764 * - Delete /if:interfaces-state
765 * - Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr
766 * - Change type /interfaces/interface/statistics/in-octets to decimal64 with
767 * fraction-digits 3 and divide all values with 1000
768 */
769 static int
upgrade_2016_to_2018(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,void * arg,cbuf * cbret)770 upgrade_2016_to_2018(clicon_handle h,
771 cxobj *xt,
772 char *ns,
773 uint16_t op,
774 uint32_t from,
775 uint32_t to,
776 void *arg,
777 cbuf *cbret)
778 {
779 int retval = -1;
780 yang_stmt *yspec;
781 yang_stmt *ym;
782 cxobj **vec = NULL;
783 cxobj *xc;
784 cxobj *xi;
785 cxobj *x;
786 cxobj *xb;
787 size_t vlen;
788 int i;
789
790 clicon_debug(1, "%s from:%d to:%d", __FUNCTION__, from, to);
791 if (op != XML_FLAG_CHANGE) /* Only treat fully present modules */
792 goto ok;
793 /* Get Yang module for this namespace. Note it may not exist (if obsolete) */
794 yspec = clicon_dbspec_yang(h);
795 if ((ym = yang_find_module_by_namespace(yspec, ns)) == NULL)
796 goto ok; /* shouldnt happen */
797 clicon_debug(1, "%s module %s", __FUNCTION__, ym?yang_argument_get(ym):"none");
798 /* Get all XML nodes with that namespace */
799 if (xml_namespace_vec(h, xt, ns, &vec, &vlen) < 0)
800 goto done;
801 for (i=0; i<vlen; i++){
802 xc = vec[i];
803 /* Delete /if:interfaces-state */
804 if (strcmp(xml_name(xc), "interfaces-state") == 0)
805 xml_purge(xc);
806 /* Iterate through interfaces */
807 else if (strcmp(xml_name(xc),"interfaces") == 0){
808 /* Iterate through interfaces */
809 xi = NULL;
810 while ((xi = xml_child_each(xc, xi, CX_ELMNT)) != NULL) {
811 if (strcmp(xml_name(xi), "interface"))
812 continue;
813 /* Wrap /interfaces/interface/descr to /interfaces/interface/docs/descr */
814 if ((x = xml_find(xi, "descr")) != NULL)
815 if (xml_wrap(x, "docs") < 0)
816 goto done;
817 /* Change type /interfaces/interface/statistics/in-octets to
818 * decimal64 with fraction-digits 3 and divide values with 1000
819 */
820 if ((x = xpath_first(xi, NULL, "statistics/in-octets")) != NULL){
821 if ((xb = xml_body_get(x)) != NULL){
822 uint64_t u64;
823 cbuf *cb = cbuf_new();
824 parse_uint64(xml_value(xb), &u64, NULL);
825 cprintf(cb, "%" PRIu64 ".%03d", u64/1000, (int)(u64%1000));
826 xml_value_set(xb, cbuf_get(cb));
827 cbuf_free(cb);
828 }
829 }
830 }
831 }
832 }
833 ok:
834 retval = 1;
835 done:
836 if (vec)
837 free(vec);
838 return retval;
839 }
840
841 /*! Testcase module-specific upgrade function moving interfaces-state to interfaces
842 * @param[in] h Clicon handle
843 * @param[in] xn XML tree to be updated
844 * @param[in] ns Namespace of module (for info)
845 * @param[in] op One of XML_FLAG_ADD, _DEL, _CHANGE
846 * @param[in] from From revision on the form YYYYMMDD
847 * @param[in] to To revision on the form YYYYMMDD (0 not in system)
848 * @param[in] arg User argument given at rpc_callback_register()
849 * @param[out] cbret Return xml tree, eg <rpc-reply>..., <rpc-error.. if retval = 0
850 * @retval 1 OK
851 * @retval 0 Invalid
852 * @retval -1 Error
853 * @see clicon_upgrade_cb
854 * @see test_upgrade_interfaces.sh
855 * @see upgrade_2014_to_2016
856 * This example shows a two-step upgrade where the 2014 function does:
857 * - Move /if:interfaces-state/if:interface/if:admin-status to
858 * /if:interfaces/if:interface/
859 * - Move /if:interfaces-state/if:interface/if:statistics to
860 * /if:interfaces/if:interface/
861 * - Rename /interfaces/interface/description to descr
862 */
863 static int
upgrade_interfaces(clicon_handle h,cxobj * xt,char * ns,uint16_t op,uint32_t from,uint32_t to,void * arg,cbuf * cbret)864 upgrade_interfaces(clicon_handle h,
865 cxobj *xt,
866 char *ns,
867 uint16_t op,
868 uint32_t from,
869 uint32_t to,
870 void *arg,
871 cbuf *cbret)
872 {
873 int retval = -1;
874
875 if (_module_upgrade) /* For testing */
876 clicon_log(LOG_NOTICE, "%s %s op:%s from:%d to:%d",
877 __FUNCTION__, ns,
878 (op&XML_FLAG_ADD)?"ADD":(op&XML_FLAG_DEL)?"DEL":"CHANGE",
879 from, to);
880 if (from <= 20140508){
881 if ((retval = upgrade_2014_to_2016(h, xt, ns, op, from, to, arg, cbret)) < 0)
882 goto done;
883 if (retval == 0)
884 goto done;
885 }
886 if (from <= 20160101){
887 if ((retval = upgrade_2016_to_2018(h, xt, ns, op, from, to, arg, cbret)) < 0)
888 goto done;
889 if (retval == 0)
890 goto done;
891 }
892 // ok:
893 retval = 1;
894 done:
895 return retval;
896 }
897
898 /*! Plugin state reset. Add xml or set state in backend machine.
899 * Called in each backend plugin. plugin_reset is called after all plugins
900 * have been initialized. This give the application a chance to reset
901 * system state back to a base state.
902 * This is generally done when a system boots up to
903 * make sure the initial system state is well defined. This can be creating
904 * default configuration files for various daemons, set interface flags etc.
905 * @param[in] h Clicon handle
906 * @param[in] db Name of database. Not may be other than "running"
907 * In this example, a loopback interface is added
908 * @note This assumes example yang with interfaces/interface
909 */
910 int
example_reset(clicon_handle h,const char * db)911 example_reset(clicon_handle h,
912 const char *db)
913 {
914 int retval = -1;
915 cxobj *xt = NULL;
916 int ret;
917 cbuf *cbret = NULL;
918
919 if (!_reset)
920 goto ok; /* Note not enabled by default */
921 if (clixon_xml_parse_string("<config><interfaces xmlns=\"urn:ietf:params:xml:ns:yang:ietf-interfaces\">"
922 "<interface><name>lo</name><type>ex:loopback</type>"
923 "</interface></interfaces></config>", YB_NONE, NULL, &xt, NULL) < 0)
924 goto done;
925 /* Replace parent w first child */
926 if (xml_rootchild(xt, 0, &xt) < 0)
927 goto done;
928 if ((cbret = cbuf_new()) == NULL){
929 clicon_err(OE_UNIX, errno, "cbuf_new");
930 goto done;
931 }
932 /* Merge user reset state */
933 if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, clicon_username_get(h), cbret)) < 0)
934 goto done;
935 if (ret == 0){
936 clicon_err(OE_XML, 0, "Error when writing to XML database: %s",
937 cbuf_get(cbret));
938 goto done;
939 }
940 ok:
941 retval = 0;
942 done:
943 if (cbret)
944 cbuf_free(cbret);
945 if (xt != NULL)
946 xml_free(xt);
947 return retval;
948 }
949
950 /*! Plugin start.
951 * @param[in] h Clicon handle
952 *
953 * plugin_start is called once everything has been initialized, right before
954 * the main event loop is entered.
955 */
956 int
example_start(clicon_handle h)957 example_start(clicon_handle h)
958 {
959 return 0;
960 }
961
962 /*! Plugin daemon.
963 * @param[in] h Clicon handle
964 *
965 * plugin_daemon is called once after damonization has been made but before lowering of privileges
966 * the main event loop is entered.
967 */
968 int
example_daemon(clicon_handle h)969 example_daemon(clicon_handle h)
970 {
971 int retval = -1;
972 int ret;
973
974 /* Read state file (or should this be in init/start?) */
975 if (_state && _state_file && _state_file_init){
976 int fd;
977 yang_stmt *yspec = clicon_dbspec_yang(h);
978
979 if ((fd = open(_state_file, O_RDONLY)) < 0){
980 clicon_err(OE_UNIX, errno, "open(%s)", _state_file);
981 goto done;
982 }
983 if ((ret = clixon_xml_parse_file(fd, YB_MODULE, yspec, NULL, &_state_xstate, NULL)) < 0)
984 goto done;
985 close(fd);
986 if (ret == 0){
987 fprintf(stderr, "%s error\n", __FUNCTION__);
988 goto done;
989 }
990 fprintf(stderr, "%s done\n", __FUNCTION__);
991 }
992 retval = 0;
993 done:
994 return retval;
995 }
996
997 int
example_exit(clicon_handle h)998 example_exit(clicon_handle h)
999 {
1000 return 0;
1001 }
1002
1003 clixon_plugin_api *clixon_plugin_init(clicon_handle h);
1004
1005 static clixon_plugin_api api = {
1006 "example", /* name */
1007 clixon_plugin_init, /* init - must be called clixon_plugin_init */
1008 example_start, /* start */
1009 example_exit, /* exit */
1010 .ca_extension=example_extension, /* yang extensions */
1011 .ca_daemon=example_daemon, /* daemon */
1012 .ca_reset=example_reset, /* reset */
1013 .ca_statedata=example_statedata, /* statedata */
1014 .ca_trans_begin=main_begin, /* trans begin */
1015 .ca_trans_validate=main_validate, /* trans validate */
1016 .ca_trans_complete=main_complete, /* trans complete */
1017 .ca_trans_commit=main_commit, /* trans commit */
1018 .ca_trans_commit_done=main_commit_done, /* trans commit done */
1019 .ca_trans_revert=main_revert, /* trans revert */
1020 .ca_trans_end=main_end, /* trans end */
1021 .ca_trans_abort=main_abort, /* trans abort */
1022 .ca_datastore_upgrade=example_upgrade, /* general-purpose upgrade. */
1023 };
1024
1025 /*! Backend plugin initialization
1026 * @param[in] h Clixon handle
1027 * @retval NULL Error with clicon_err set
1028 * @retval api Pointer to API struct
1029 * In this example, you can pass -r, -s, -u to control the behaviour, mainly
1030 * for use in the test suites.
1031 */
1032 clixon_plugin_api *
clixon_plugin_init(clicon_handle h)1033 clixon_plugin_init(clicon_handle h)
1034 {
1035 struct timeval retention = {0,0};
1036 int argc; /* command-line options (after --) */
1037 char **argv;
1038 int c;
1039
1040 clicon_debug(1, "%s backend", __FUNCTION__);
1041
1042 /* Get user command-line options (after --) */
1043 if (clicon_argv_get(h, &argc, &argv) < 0)
1044 goto done;
1045 opterr = 0;
1046 optind = 1;
1047 while ((c = getopt(argc, argv, BACKEND_EXAMPLE_OPTS)) != -1)
1048 switch (c) {
1049 case 'r':
1050 _reset = 1;
1051 break;
1052 case 's': /* state callback */
1053 _state = 1;
1054 break;
1055 case 'S': /* state file (requires -s) */
1056 _state_file = optarg;
1057 break;
1058 case 'i': /* read state file on init not by request (requires -sS <file> */
1059 _state_file_init = 1;
1060 break;
1061 case 'u': /* module-specific upgrade */
1062 _module_upgrade = 1;
1063 break;
1064 case 'U': /* general-purpose upgrade */
1065 _general_upgrade = 1;
1066 break;
1067 case 't': /* transaction log */
1068 _transaction_log = 1;
1069 break;
1070 case 'v': /* validate fail */
1071 _validate_fail_xpath = optarg;
1072 break;
1073 }
1074
1075 /* Example stream initialization:
1076 * 1) Register EXAMPLE stream
1077 * 2) setup timer for notifications, so something happens on stream
1078 * 3) setup stream callbacks for notification to push channel
1079 */
1080 if (clicon_option_exists(h, "CLICON_STREAM_RETENTION"))
1081 retention.tv_sec = clicon_option_int(h, "CLICON_STREAM_RETENTION");
1082 if (stream_add(h, "EXAMPLE", "Example event stream", 1, &retention) < 0)
1083 goto done;
1084 /* Enable nchan pub/sub streams
1085 * assumes: CLIXON_PUBLISH_STREAMS, eg configure --enable-publish
1086 */
1087 if (clicon_option_exists(h, "CLICON_STREAM_PUB") &&
1088 stream_publish(h, "EXAMPLE") < 0)
1089 goto done;
1090 if (example_stream_timer_setup(h) < 0)
1091 goto done;
1092
1093 /* Register callback for routing rpc calls
1094 */
1095 /* From example.yang (clicon) */
1096 if (rpc_callback_register(h, empty_rpc,
1097 NULL,
1098 "urn:example:clixon",
1099 "empty"/* Xml tag when callback is made */
1100 ) < 0)
1101 goto done;
1102 /* Same as example but with optional input/output */
1103 if (rpc_callback_register(h, example_rpc,
1104 NULL,
1105 "urn:example:clixon",
1106 "optional"/* Xml tag when callback is made */
1107 ) < 0)
1108 goto done;
1109 /* Same as example but with optional input/output */
1110 if (rpc_callback_register(h, example_rpc,
1111 NULL,
1112 "urn:example:clixon",
1113 "example"/* Xml tag when callback is made */
1114 ) < 0)
1115 goto done;
1116 /* Called after the regular system copy_config callback */
1117 if (rpc_callback_register(h, example_copy_extra,
1118 NULL,
1119 NETCONF_BASE_NAMESPACE,
1120 "copy-config"
1121 ) < 0)
1122 goto done;
1123 /* Upgrade callback: if you start the backend with -- -u you will get the
1124 * test interface example. Otherwise the auto-upgrade feature is enabled.
1125 */
1126 if (_module_upgrade){
1127 if (upgrade_callback_register(h, upgrade_interfaces, "urn:example:interfaces", NULL) < 0)
1128 goto done;
1129 }
1130 else
1131 if (upgrade_callback_register(h, xml_changelog_upgrade, NULL, NULL) < 0)
1132 goto done;
1133
1134 /* Return plugin API */
1135 return &api;
1136 done:
1137 return NULL;
1138 }
1139