/* * ***** BEGIN LICENSE BLOCK ***** Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren Copyright (C) 2017-2019 Olof Hagsand Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Alternatively, the contents of this file may be used under the terms of the GNU General Public License Version 3 or later (the "GPL"), in which case the provisions of the GPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of the GPL, and not to allow others to use your version of this file under the terms of Apache License version 2, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the Apache License version 2 or the GPL. ***** END LICENSE BLOCK ***** * */ #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* cligen */ #include /* clicon */ #include #include "clixon_backend_transaction.h" #include "backend_plugin.h" #include "backend_commit.h" /*! Request plugins to reset system state * The system 'state' should be the same as the contents of running_db * @param[in] cp Plugin handle * @param[in] h Clicon handle * @param[in] db Name of datastore * @retval 0 OK * @retval -1 Error */ int clixon_plugin_reset_one(clixon_plugin *cp, clicon_handle h, char *db) { int retval = -1; plgreset_t *fn; /* callback */ if ((fn = cp->cp_api.ca_reset) != NULL){ if (fn(h, db) < 0) { if (clicon_errno < 0) clicon_log(LOG_WARNING, "%s: Internal error: Reset callback in plugin: %s returned -1 but did not make a clicon_err call", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /*! Call all plugins reset callbacks * The system 'state' should be the same as the contents of running_db * @param[in] h Clixon handle * @param[in] db Name of datastore * @retval 0 OK * @retval -1 Error */ int clixon_plugin_reset_all(clicon_handle h, char *db) { int retval = -1; clixon_plugin *cp = NULL; /* Loop through all plugins, call callbacks in each */ while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (clixon_plugin_reset_one(cp, h, db) < 0) goto done; } retval = 0; done: return retval; } /*! Call single plugin "post-" daemonize callback * @param[in] cp Plugin handle * @param[in] h Clixon handle * @retval 0 OK * @retval -1 Error */ int clixon_plugin_daemon_one(clixon_plugin *cp, clicon_handle h) { int retval = -1; plgdaemon_t *fn; /* Daemonize plugin callback function */ if ((fn = cp->cp_api.ca_daemon) != NULL){ if (fn(h) < 0) { if (clicon_errno < 0) clicon_log(LOG_WARNING, "%s: Internal error: Daemon callback in plugin: %s returned -1 but did not make a clicon_err call", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /*! Call all plugins "post-" daemonize callbacks * @param[in] h Clicon handle * @retval 0 OK * @retval -1 Error */ int clixon_plugin_daemon_all(clicon_handle h) { int retval = -1; clixon_plugin *cp = NULL; /* Loop through all plugins, call callbacks in each */ while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (clixon_plugin_daemon_one(cp, h) < 0) goto done; } retval = 0; done: return retval; } /*! Call single backend statedata callback * @param[in] cp Plugin handle * @param[in] h clicon handle * @param[in] xpath String with XPATH syntax. or NULL for all * @retval -1 Error * @retval 0 Statedata callback failed * @retval 1 OK if callback found (and called) xp is set, otherwise xp is not set * @note xtop can be replaced */ static int clixon_plugin_statedata_one(clixon_plugin *cp, clicon_handle h, cvec *nsc, char *xpath, cxobj **xp) { int retval = -1; plgstatedata_t *fn; /* Plugin statedata fn */ cxobj *x = NULL; if ((fn = cp->cp_api.ca_statedata) != NULL){ if ((x = xml_new("config", NULL, CX_ELMNT)) == NULL) goto done; if (fn(h, nsc, xpath, x) < 0){ if (clicon_errno < 0) clicon_log(LOG_WARNING, "%s: Internal error: State callback in plugin: %s returned -1 but did not make a clicon_err call", __FUNCTION__, cp->cp_name); goto fail; /* Dont quit here on user callbacks */ } } if (xp && x) *xp = x; retval = 1; done: return retval; fail: retval = 0; goto done; } /*! Go through all backend statedata callbacks and collect state data * This is internal system call, plugin is invoked (does not call) this function * Backend plugins can register * @param[in] h clicon handle * @param[in] yspec Yang spec * @param[in] nsc Namespace context * @param[in] xpath String with XPATH syntax. or NULL for all * @param[in,out] xtop State XML tree is merged with existing tree. * @retval -1 Error * @retval 0 Statedata callback failed (xret set with netconf-error) * @retval 1 OK * @note xtop can be replaced */ int clixon_plugin_statedata_all(clicon_handle h, yang_stmt *yspec, cvec *nsc, char *xpath, cxobj **xret) { int retval = -1; int ret; cxobj *x = NULL; clixon_plugin *cp = NULL; cbuf *cberr = NULL; cxobj *xerr = NULL; clicon_debug(1, "%s", __FUNCTION__); while ((cp = clixon_plugin_each(h, cp)) != NULL) { if ((ret = clixon_plugin_statedata_one(cp, h, nsc, xpath, &x)) < 0) goto done; if (ret == 0) goto fail; if (x == NULL) continue; if (xml_child_nr(x) == 0){ xml_free(x); x = NULL; continue; } #if 1 if (clicon_debug_get()) clicon_log_xml(LOG_DEBUG, x, "%s STATE:", __FUNCTION__); #endif /* XXX: ret == 0 invalid yang binding should be handled as internal error */ if ((ret = xml_bind_yang(x, YB_MODULE, yspec, &xerr)) < 0) goto done; if (ret == 0){ if (clixon_netconf_internal_error(xerr, ". Internal error, state callback returned invalid XML: ", cp->cp_name) < 0) goto done; xml_free(*xret); *xret = xerr; xerr = NULL; goto fail; } if (xml_sort_recurse(x) < 0) goto done; if (xml_default_recurse(x) < 0) goto done; if ((ret = netconf_trymerge(x, yspec, xret)) < 0) goto done; if (ret == 0) goto fail; if (x){ xml_free(x); x = NULL; } } /* while plugin */ retval = 1; done: if (xerr) xml_free(xerr); if (cberr) cbuf_free(cberr); if (x) xml_free(x); return retval; fail: retval = 0; goto done; } /*! Create and initialize a validate/commit transaction * @retval td New alloced transaction, * @retval NULL Error * @see transaction_free which deallocates the returned handle */ transaction_data_t * transaction_new(void) { transaction_data_t *td; static uint64_t id = 0; /* Global transaction id */ if ((td = malloc(sizeof(*td))) == NULL){ clicon_err(OE_CFG, errno, "malloc"); return NULL; } memset(td, 0, sizeof(*td)); td->td_id = id++; return td; } /*! Free transaction structure * * @param[in] td Transaction data will be deallocated after the call */ int transaction_free(transaction_data_t *td) { if (td->td_src) xml_free(td->td_src); if (td->td_target) xml_free(td->td_target); if (td->td_dvec) free(td->td_dvec); if (td->td_avec) free(td->td_avec); if (td->td_scvec) free(td->td_scvec); if (td->td_tcvec) free(td->td_tcvec); free(td); return 0; } /*! Call single plugin transaction_begin() before a validate/commit. * @param[in] cp Plugin handle * @param[in] h Clixon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error */ int plugin_transaction_begin_one(clixon_plugin *cp, clicon_handle h, transaction_data_t *td) { int retval = -1; trans_cb_t *fn; if ((fn = cp->cp_api.ca_trans_begin) != NULL){ if (fn(h, (transaction_data)td) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /* The plugin_transaction routines need access to struct plugin which is local to this file */ /*! Call transaction_begin() in all plugins before a validate/commit. * @param[in] h Clicon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error: one of the plugin callbacks returned error */ int plugin_transaction_begin_all(clicon_handle h, transaction_data_t *td) { int retval = -1; clixon_plugin *cp = NULL; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (plugin_transaction_begin_one(cp, h, td) < 0) goto done; } retval = 0; done: return retval; } /*! Call single plugin transaction_validate() in a validate/commit transaction * @param[in] cp Plugin handle * @param[in] h Clixon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error */ int plugin_transaction_validate_one(clixon_plugin *cp, clicon_handle h, transaction_data_t *td) { int retval = -1; trans_cb_t *fn; if ((fn = cp->cp_api.ca_trans_validate) != NULL){ if (fn(h, (transaction_data)td) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /*! Call transaction_validate callbacks in all backend plugins * @param[in] h Clicon handle * @param[in] td Transaction data * @retval 0 OK. Validation succeeded in all plugins * @retval -1 Error: one of the plugin callbacks returned validation fail */ int plugin_transaction_validate_all(clicon_handle h, transaction_data_t *td) { int retval = -1; clixon_plugin *cp = NULL; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (plugin_transaction_validate_one(cp, h, td) < 0) goto done; } retval = 0; done: return retval; } /*! Call single plugin transaction_complete() in a validate/commit transaction * complete is called after validate (before commit) * @param[in] cp Plugin handle * @param[in] h Clixon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error */ int plugin_transaction_complete_one(clixon_plugin *cp, clicon_handle h, transaction_data_t *td) { int retval = -1; trans_cb_t *fn; if ((fn = cp->cp_api.ca_trans_complete) != NULL){ if (fn(h, (transaction_data)td) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /*! Call transaction_complete() in all plugins after validation (before commit) * @param[in] h Clicon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error: one of the plugin callbacks returned error * @note Call plugins which have commit dependencies? * @note Rename to transaction_complete? */ int plugin_transaction_complete_all(clicon_handle h, transaction_data_t *td) { int retval = -1; clixon_plugin *cp = NULL; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (plugin_transaction_complete_one(cp, h, td) < 0) goto done; } retval = 0; done: return retval; } /*! Revert a commit * @param[in] h CLICON handle * @param[in] td Transaction data * @param[in] nr The plugin where an error occured. * @retval 0 OK * @retval -1 Error * The revert is made in plugin before this one. Eg if error occurred in * plugin 2, then the revert will be made in plugins 1 and 0. */ static int plugin_transaction_revert_all(clicon_handle h, transaction_data_t *td, int nr) { int retval = 0; clixon_plugin *cp = NULL; trans_cb_t *fn; while ((cp = clixon_plugin_each_revert(h, cp, nr)) != NULL) { if ((fn = cp->cp_api.ca_trans_revert) == NULL) continue; if ((retval = fn(h, (transaction_data)td)) < 0){ clicon_log(LOG_NOTICE, "%s: Plugin '%s' trans_revert callback failed", __FUNCTION__, cp->cp_name); break; } } return retval; /* ignore errors */ } /*! Call single plugin transaction_commit() in a commit transaction * @param[in] cp Plugin handle * @param[in] h Clixon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error */ int plugin_transaction_commit_one(clixon_plugin *cp, clicon_handle h, transaction_data_t *td) { int retval = -1; trans_cb_t *fn; if ((fn = cp->cp_api.ca_trans_commit) != NULL){ if (fn(h, (transaction_data)td) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /*! Call transaction_commit callbacks in all backend plugins * @param[in] h Clicon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error: one of the plugin callbacks returned error * If any of the commit callbacks fail by returning -1, a revert of the * transaction is tried by calling the commit callbacsk with reverse arguments * and in reverse order. */ int plugin_transaction_commit_all(clicon_handle h, transaction_data_t *td) { int retval = -1; clixon_plugin *cp = NULL; int i=0; while ((cp = clixon_plugin_each(h, cp)) != NULL) { i++; if (plugin_transaction_commit_one(cp, h, td) < 0){ /* Make an effort to revert transaction */ plugin_transaction_revert_all(h, td, i-1); goto done; } } retval = 0; done: return retval; } /*! Call single plugin transaction_commit_done() in a commit transaction * @param[in] cp Plugin handle * @param[in] h Clixon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error */ int plugin_transaction_commit_done_one(clixon_plugin *cp, clicon_handle h, transaction_data_t *td) { int retval = -1; trans_cb_t *fn; if ((fn = cp->cp_api.ca_trans_commit_done) != NULL){ if (fn(h, (transaction_data)td) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /*! Call transaction_commit_done callbacks in all backend plugins * @param[in] h Clicon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error: one of the plugin callbacks returned error * @note no revert is done */ int plugin_transaction_commit_done_all(clicon_handle h, transaction_data_t *td) { int retval = -1; clixon_plugin *cp = NULL; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (plugin_transaction_commit_done_one(cp, h, td) < 0) goto done; } retval = 0; done: return retval; } /*! Call single plugin transaction_end() in a commit/validate transaction * @param[in] cp Plugin handle * @param[in] h Clixon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error */ int plugin_transaction_end_one(clixon_plugin *cp, clicon_handle h, transaction_data_t *td) { int retval = -1; trans_cb_t *fn; if ((fn = cp->cp_api.ca_trans_end) != NULL){ if (fn(h, (transaction_data)td) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /*! Call transaction_end() in all plugins after a successful commit. * @param[in] h Clicon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error */ int plugin_transaction_end_all(clicon_handle h, transaction_data_t *td) { int retval = -1; clixon_plugin *cp = NULL; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (plugin_transaction_end_one(cp, h, td) < 0) goto done; } retval = 0; done: return retval; } int plugin_transaction_abort_one(clixon_plugin *cp, clicon_handle h, transaction_data_t *td) { int retval = -1; trans_cb_t *fn; if ((fn = cp->cp_api.ca_trans_abort) != NULL){ if (fn(h, (transaction_data)td) < 0){ if (!clicon_errno) /* sanity: log if clicon_err() is not called ! */ clicon_log(LOG_NOTICE, "%s: Plugin '%s' callback does not make clicon_err call on error", __FUNCTION__, cp->cp_name); goto done; } } retval = 0; done: return retval; } /*! Call transaction_abort() in all plugins after a failed validation/commit. * @param[in] h Clicon handle * @param[in] td Transaction data * @retval 0 OK * @retval -1 Error */ int plugin_transaction_abort_all(clicon_handle h, transaction_data_t *td) { int retval = -1; clixon_plugin *cp = NULL; while ((cp = clixon_plugin_each(h, cp)) != NULL) { if (plugin_transaction_abort_one(cp, h, td) < 0) ; /* dont abort on error */ } retval = 0; return retval; }