/*
*
Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren
This file is part of CLIXON.
CLIXON is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
CLIXON is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with CLIXON; see the file LICENSE. If not, see
.
*/
#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
#include
#include
#include
#include
#include
#include
/* cligen */
#include
/* clicon */
#include
#include "clixon_backend_transaction.h"
#include "backend_plugin.h"
#include "backend_handle.h"
#include "backend_commit.h"
#include "backend_client.h"
/*! Key values are checked for validity independent of user-defined callbacks
*
* Key values are checked as follows:
* 1. If no value and default value defined, add it.
* 2. If no value and mandatory flag set in spec, report error.
* 3. Validate value versus spec, and report error if no match. Currently only int ranges and
* string regexp checked.
* See also db_lv_set() where defaults are also filled in. The case here for defaults
* are if code comes via XML/NETCONF.
* @param yspec Yang spec
* @param td Transaction data
*/
static int
generic_validate(yang_spec *yspec,
transaction_data_t *td)
{
int retval = -1;
cxobj *x1;
cxobj *x2;
int i;
yang_stmt *ys;
/* changed entries */
for (i=0; itd_clen; i++){
x1 = td->td_scvec[i]; /* source changed */
x2 = td->td_tcvec[i]; /* target changed */
ys = xml_spec(x1);
if (xml_yang_validate(x2, ys) < 0)
goto done;
}
/* deleted entries */
for (i=0; itd_dlen; i++){
x1 = td->td_dvec[i];
ys = xml_spec(x1);
if (yang_mandatory(ys)){
clicon_err(OE_CFG, 0,"Removed mandatory variable: %s",
xml_name(x1));
goto done;
}
}
/* added entries */
for (i=0; itd_alen; i++){
x2 = td->td_avec[i];
if (xml_yang_validate(x2, xml_spec(x2)) < 0)
goto done;
if (xml_apply(x2, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate, NULL) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Do a diff between candidate and running, then start a commit transaction
*
* The code reverts changes if the commit fails. But if the revert
* fails, we just ignore the errors and proceed. Maybe we should
* do something more drastic?
* @param[in] h Clicon handle
* @param[in] running The current database. The original backend state
* @param[in] candidate: The candidate database. The wanted backend state
*/
int
candidate_commit(clicon_handle h,
char *candidate,
char *running)
{
int retval = -1;
int i;
cxobj *xn;
struct stat sb;
void *firsterr = NULL;
yang_spec *yspec;
transaction_data_t *td = NULL;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
/* Sanity checks that databases exists. */
if (stat(running, &sb) < 0){
clicon_err(OE_DB, errno, "%s", running);
goto done;
}
if (stat(candidate, &sb) < 0){
clicon_err(OE_DB, errno, "%s", candidate);
goto done;
}
/* 1. Start transaction */
if ((td = transaction_new()) == NULL)
goto done;
/* 2. Parse xml trees */
if (xmldb_get(running, "/", yspec, &td->td_src) < 0)
goto done;
if (xmldb_get(candidate, "/", yspec, &td->td_target) < 0)
goto done;
/* 3. Compute differences */
if (xml_diff(yspec,
td->td_src,
td->td_target,
&td->td_dvec, /* removed: only in running */
&td->td_dlen,
&td->td_avec, /* added: only in candidate */
&td->td_alen,
&td->td_scvec, /* changed: original values */
&td->td_tcvec, /* changed: wanted values */
&td->td_clen) < 0)
goto done;
if (debug)
transaction_print(stderr, td);
/* Mark as changed in tree */
for (i=0; itd_dlen; i++){ /* Also down */
xn = td->td_dvec[i];
xml_flag_set(xn, XML_FLAG_DEL);
xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_DEL);
}
for (i=0; itd_alen; i++){ /* Also down */
xn = td->td_avec[i];
xml_flag_set(xn, XML_FLAG_ADD);
xml_apply(xn, CX_ELMNT, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_ADD);
}
for (i=0; itd_clen; i++){ /* Also up */
xn = td->td_scvec[i];
xml_flag(xn, XML_FLAG_CHANGE);
xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
xn = td->td_tcvec[i];
xml_flag_set(xn, XML_FLAG_CHANGE);
xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
}
/* 4. Call plugin transaction start callbacks */
if (plugin_transaction_begin(h, td) < 0)
goto done;
/* 5. Make generic validation on all new or changed data. */
if (generic_validate(yspec, td) < 0)
goto done;
/* 6. Call plugin transaction validate callbacks */
if (plugin_transaction_validate(h, td) < 0)
goto done;
/* 7. Call plugin transaction complete callbacks */
if (plugin_transaction_complete(h, td) < 0)
goto done;
/* 7. Call plugin transaction commit callbacks */
if (plugin_transaction_commit(h, td) < 0)
goto done;
/* 8. Success: Copy candidate to running */
if (file_cp(candidate, running) < 0){
clicon_err(OE_UNIX, errno, "file_cp(candidate; running)");
goto done;
}
/* 9. Call plugin transaction end callbacks */
plugin_transaction_end(h, td);
/* 8. Copy running back to running in case end functions updated running */
if (file_cp(running, candidate) < 0){
/* ignore errors or signal major setback ? */
clicon_err(OE_UNIX, errno, "file_cp(running, candidate)");
clicon_log(LOG_NOTICE, "Error in rollback, trying to continue");
goto done;
}
retval = 0;
done:
/* In case of failure, call plugin transaction termination callbacks */
if (retval < 0 && td)
plugin_transaction_abort(h, td);
if (td)
transaction_free(td);
if (firsterr)
clicon_err_restore(firsterr);
return retval;
}
/*! Do a diff between candidate and running, then start a validate transaction
*
* @param[in] h Clicon handle
* @param[in] running The current database. The original backend state
* @param[in] candidate: The candidate database. The wanted backend state
*/
int
candidate_validate(clicon_handle h,
char *candidate,
char *running)
{
int retval = -1;
struct stat sb;
yang_spec *yspec;
transaction_data_t *td = NULL;
int i;
cxobj *xn;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
/* Sanity checks that databases exists. */
if (stat(running, &sb) < 0){
clicon_err(OE_DB, errno, "%s", running);
goto done;
}
if (stat(candidate, &sb) < 0){
clicon_err(OE_DB, errno, "%s", candidate);
goto done;
}
/* 1. Start transaction */
if ((td = transaction_new()) == NULL)
goto done;
/* 2. Parse xml trees */
if (xmldb_get(running, "/", yspec, &td->td_src) < 0)
goto done;
if (xmldb_get(candidate, "/", yspec, &td->td_target) < 0)
goto done;
/* 3. Compute differences */
if (xml_diff(yspec,
td->td_src,
td->td_target,
&td->td_dvec, /* removed: only in running */
&td->td_dlen,
&td->td_avec, /* added: only in candidate */
&td->td_alen,
&td->td_scvec, /* changed: original values */
&td->td_tcvec, /* changed: wanted values */
&td->td_clen) < 0)
goto done;
if (debug)
transaction_print(stderr, td);
/* Mark as changed in tree */
for (i=0; itd_dlen; i++){ /* Also down */
xn = td->td_dvec[i];
xml_flag_set(xn, XML_FLAG_DEL);
}
for (i=0; itd_alen; i++){ /* Also down */
xn = td->td_avec[i];
xml_flag_set(xn, XML_FLAG_ADD);
}
for (i=0; itd_clen; i++){ /* Also up */
xn = td->td_scvec[i];
xml_flag_set(xn, XML_FLAG_CHANGE);
xn = td->td_tcvec[i];
xml_flag(xn, XML_FLAG_CHANGE);
}
/* 4. Call plugin start transaction callbacks */
if (plugin_transaction_begin(h, td) < 0)
goto done;
/* 5. Make generic validation on all new or changed data. */
if (generic_validate(yspec, td) < 0)
goto done;
/* 6. Call plugin validate transaction callbacks */
if (plugin_transaction_validate(h, td) < 0)
goto done;
/* 7. Call plugin complete transaction callbacks */
if (plugin_transaction_complete(h, td) < 0)
goto done;
retval = 0;
done:
/* In case of failure, call plugin transaction termination callbacks */
if (retval < 0 && td)
plugin_transaction_abort(h, td);
if (td)
transaction_free(td);
return retval;
}
/*! Handle an incoming commit message from a client.
* XXX: If commit succeeds and snapshot/startup fails, we have strange state:
* the commit has succeeded but an error message is returned.
*/
int
from_client_commit(clicon_handle h,
int s,
struct clicon_msg *msg,
const char *label)
{
int retval = -1;
char *candidate;
char *running;
uint32_t snapshot;
uint32_t startup;
char *snapshot_0;
char *archive_dir;
char *startup_config;
if (clicon_msg_commit_decode(msg, &candidate, &running,
&snapshot, &startup, label) < 0)
goto err;
if (candidate_commit(h, candidate, running) < 0){
clicon_debug(1, "Commit %s failed", candidate);
retval = 0; /* We ignore errors from commit, but maybe
we should fail on fatal errors? */
goto err;
}
clicon_debug(1, "Commit %s", candidate);
if (snapshot){
if ((archive_dir = clicon_archive_dir(h)) == NULL){
clicon_err(OE_PLUGIN, 0, "snapshot set and clicon_archive_dir not defined");
goto err;
}
if (config_snapshot(h, running, archive_dir) < 0)
goto err;
}
if (startup){
if ((archive_dir = clicon_archive_dir(h)) == NULL){
clicon_err(OE_PLUGIN, 0, "startup set but clicon_archive_dir not defined");
goto err;
}
if ((startup_config = clicon_startup_config(h)) == NULL){
clicon_err(OE_PLUGIN, 0, "startup set but startup_config not defined");
goto err;
}
snapshot_0 = chunk_sprintf(__FUNCTION__, "%s/0", archive_dir);
if (file_cp(snapshot_0, startup_config) < 0){
clicon_err(OE_PROTO, errno, "%s: Error when creating startup",
__FUNCTION__);
goto err;
}
}
retval = 0;
if (send_msg_ok(s) < 0)
goto done;
goto done;
err:
/* XXX: more elaborate errstring? */
if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
retval = -1;
done:
unchunk_group(__FUNCTION__);
return retval; /* may be zero if we ignoring errors from commit */
} /* from_client_commit */
/*
* Call backend plugin
*/
int
from_client_validate(clicon_handle h,
int s,
struct clicon_msg *msg,
const char *label)
{
char *dbname;
char *running_db;
int retval = -1;
if (clicon_msg_validate_decode(msg, &dbname, label) < 0){
send_msg_err(s, clicon_errno, clicon_suberrno,
clicon_err_reason);
goto err;
}
clicon_debug(1, "Validate %s", dbname);
if ((running_db = clicon_running_db(h)) == NULL){
clicon_err(OE_FATAL, 0, "running db not set");
goto err;
}
if (candidate_validate(h, dbname, running_db) < 0){
clicon_debug(1, "Validate %s failed", dbname);
retval = 0; /* We ignore errors from commit, but maybe
we should fail on fatal errors? */
goto err;
}
retval = 0;
if (send_msg_ok(s) < 0)
goto done;
goto done;
err:
/* XXX: more elaborate errstring? */
if (send_msg_err(s, clicon_errno, clicon_suberrno, "%s", clicon_err_reason) < 0)
retval = -1;
done:
unchunk_group(__FUNCTION__);
return retval;
} /* from_client_validate */