clixon/apps/backend/backend_commit.c
2017-04-03 15:21:13 +02:00

400 lines
12 KiB
C

/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2017 Olof Hagsand and Benny Holmgren
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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdarg.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <time.h>
#include <pwd.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <netinet/in.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#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; i<td->td_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; i<td->td_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; i<td->td_alen; i++){
x2 = td->td_avec[i];
ys = xml_spec(x2);
if (xml_yang_validate(x2, ys) < 0)
goto done;
if (xml_apply(x2, CX_ELMNT,
(xml_applyfn_t*)xml_yang_validate, NULL) < 0)
goto done;
}
retval = 0;
done:
return retval;
}
/*! Common code of candidate_validate and candidate_commit
* @param[in] h Clicon handle
* @param[in] candidate The candidate database. The wanted backend state
* @retval 0 OK
* @retval -1 Fatal error or netconf error XXX Differentiate
*/
static int
validate_common(clicon_handle h,
char *candidate,
transaction_data_t *td)
{
int retval = -1;
yang_spec *yspec;
int i;
cxobj *xn;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_FATAL, 0, "No DB_SPEC");
goto done;
}
/* 2. Parse xml trees */
if (xmldb_get(h, "running", "/", &td->td_src, NULL, NULL) < 0)
goto done;
if (xmldb_get(h, candidate, "/", &td->td_target, NULL, NULL) < 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>1)
transaction_print(stderr, td);
/* Mark as changed in tree */
for (i=0; i<td->td_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);
xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
}
for (i=0; i<td->td_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);
xml_apply_ancestor(xn, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
}
for (i=0; i<td->td_clen; i++){ /* Also up */
xn = td->td_scvec[i];
xml_flag_set(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;
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
*/
int
candidate_commit(clicon_handle h,
char *candidate)
{
int retval = -1;
transaction_data_t *td = NULL;
/* 1. Start transaction */
if ((td = transaction_new()) == NULL)
goto done;
/* Common steps (with validate) */
if (validate_common(h, candidate, 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 (xmldb_copy(h, candidate, "running") < 0)
goto done;
/* 9. Call plugin transaction end callbacks */
plugin_transaction_end(h, td);
/* 8. Copy running back to candidate in case end functions updated running */
if (xmldb_copy(h, "running", candidate) < 0){
/* ignore errors or signal major setback ? */
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);
return retval;
}
/*! Commit changes from candidate to running
* @param[in] h Clicon handle
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client
* @retval -1 (Local) Error
*/
int
from_client_commit(clicon_handle h,
int mypid,
cbuf *cbret)
{
int retval = -1;
int piddb;
/* Check if target locked by other client */
piddb = xmldb_islocked(h, "running");
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
if (candidate_commit(h, "candidate") < 0){
clicon_debug(1, "Commit candidate failed");
/* XXX: candidate_validate should have proper error handling */
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>missing-attribute</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>%s</error-message>"
"</rpc-error></rpc-reply>",
clicon_err_reason);
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;
// done:
return retval; /* may be zero if we ignoring errors from commit */
} /* from_client_commit */
/*! Discard all changes in candidate / revert to running
* @param[in] h Clicon handle
* @param[in] mypid Process/session id of calling client
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client
* @retval -1 (Local) Error
*/
int
from_client_discard_changes(clicon_handle h,
int mypid,
cbuf *cbret)
{
int retval = -1;
int piddb;
/* Check if target locked by other client */
piddb = xmldb_islocked(h, "candidate");
if (piddb && mypid != piddb){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>lock-denied</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>Operation failed, lock is already held</error-message>"
"<error-info><session-id>%d</session-id></error-info>"
"</rpc-error></rpc-reply>",
piddb);
goto ok;
}
if (xmldb_copy(h, "running", "candidate") < 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>operation-failed</error-tag>"
"<error-type>application</error-type>"
"<error-severity>error</error-severity>"
"<error-info>read-registry</error-info>"
"</rpc-error></rpc-reply>");
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;
// done:
return retval; /* may be zero if we ignoring errors from commit */
}
/*! Handle an incoming validate message from a client.
* @param[in] h Clicon handle
* @param[in] db Database name
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client (eg invalid)
* @retval -1 (Local) Error
*/
int
from_client_validate(clicon_handle h,
char *db,
cbuf *cbret)
{
int retval = -1;
transaction_data_t *td = NULL;
if (strcmp(db, "candidate") != 0 && strcmp(db, "tmp") != 0){
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>invalid-value</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"</rpc-error></rpc-reply>");
goto ok;
}
clicon_debug(1, "Validate %s", db);
/* 1. Start transaction */
if ((td = transaction_new()) == NULL)
goto done;
/* Common steps (with commit) */
if (validate_common(h, db, td) < 0){
clicon_debug(1, "Validate %s failed", db);
/* XXX: candidate_validate should have proper error handling */
cprintf(cbret, "<rpc-reply><rpc-error>"
"<error-tag>missing-attribute</error-tag>"
"<error-type>protocol</error-type>"
"<error-severity>error</error-severity>"
"<error-message>%s</error-message>"
"</rpc-error></rpc-reply>",
clicon_err_reason);
goto ok;
}
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
ok:
retval = 0;
done:
if (retval < 0 && td)
plugin_transaction_abort(h, td);
if (td)
transaction_free(td);
return retval;
} /* from_client_validate */