diff --git a/CHANGELOG.md b/CHANGELOG.md index 850eb04d..ae81a95b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,7 +39,7 @@ Expected: April * The netconf client will terminate (close the socket) if the client does not comply * You can set `CLICON_NETCONF_HELLO_OPTIONAL` to true to use the old behavior of essentially ignoring hellos. * New clixon-lib@2020-03-08.yang revision - * Changed: RPC process-control output to choice dependent on operation + * Changed: RPC process-control output to choice with status fields * New clixon-config@2020-03-08.yang revision * Added: `CLICON_NETCONF_HELLO_OPTIONAL` @@ -55,6 +55,7 @@ Developers may need to change their code ### Minor features +* Added several fields to process-control status operation: active, description, command, status, starttime, pid * Changed signal handling * Moved clixon-proc sigchild handling from handler to clixon_events * The base capability has been changed to "urn:ietf:params:netconf:base:1.1" following RFC6241. diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 9a7d4fa3..0dc24a30 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -1584,7 +1584,6 @@ from_client_process_control(clicon_handle h, cxobj *x; char *name = NULL; char *opstr = NULL; - uint32_t pid = 0; proc_operation op = PROC_OP_NONE; if ((x = xml_find_type(xe, NULL, "name", CX_ELMNT)) != NULL) @@ -1594,16 +1593,16 @@ from_client_process_control(clicon_handle h, op = clixon_process_op_str2int(opstr); } /* Make the actual process operation (with wrap function enabled) */ - if (clixon_process_operation(h, name, op, 1, &pid) < 0) - goto done; - if (op == PROC_OP_STATUS) - cprintf(cbret, "%s%u", - NETCONF_BASE_NAMESPACE, - CLIXON_LIB_NS, pid?"true":"false", - CLIXON_LIB_NS, pid); - else + if (op == PROC_OP_STATUS){ + if (clixon_process_status(h, name, cbret) < 0) + goto done; + } + else{ + if (clixon_process_operation(h, name, op, 1) < 0) + goto done; cprintf(cbret, "", NETCONF_BASE_NAMESPACE, CLIXON_LIB_NS); + } retval = 0; done: return retval; diff --git a/apps/backend/backend_plugin_restconf.c b/apps/backend/backend_plugin_restconf.c index 660fdfa6..8dd67a26 100644 --- a/apps/backend/backend_plugin_restconf.c +++ b/apps/backend/backend_plugin_restconf.c @@ -137,6 +137,7 @@ restconf_pseudo_process_control(clicon_handle h) argv[i++] = NULL; assert(i==nr); if (clixon_process_register(h, RESTCONF_PROCESS, + "Clixon RESTCONF process", NULL /* XXX network namespace */, restconf_rpc_wrapper, argv, nr) < 0) @@ -196,7 +197,7 @@ restconf_pseudo_process_commit(clicon_handle h, if ((cx = xpath_first(xtarget, NULL, "/restconf/enable")) != NULL && xml_flag(cx, XML_FLAG_CHANGE|XML_FLAG_ADD)){ if (clixon_process_operation(h, RESTCONF_PROCESS, - enabled?PROC_OP_START:PROC_OP_STOP, 0, NULL) < 0) + enabled?PROC_OP_START:PROC_OP_STOP, 0) < 0) goto done; } else if (enabled){ /* If something changed and running, restart process */ @@ -209,7 +210,7 @@ restconf_pseudo_process_commit(clicon_handle h, * Specifically, the socket is terminated where the reply is sent, which will * cause the curl to fail. */ - if (clixon_process_operation(h, RESTCONF_PROCESS, PROC_OP_RESTART, 0, NULL) < 0) + if (clixon_process_operation(h, RESTCONF_PROCESS, PROC_OP_RESTART, 0) < 0) goto done; } } diff --git a/lib/clixon/clixon_proc.h b/lib/clixon/clixon_proc.h index b2690442..6c8b1a05 100644 --- a/lib/clixon/clixon_proc.h +++ b/lib/clixon/clixon_proc.h @@ -62,9 +62,10 @@ int clixon_proc_socket(char **argv, pid_t *pid, int *sock); int clixon_proc_socket_close(pid_t pid, int sock); int clixon_proc_background(char **argv, const char *netns, pid_t *pid); proc_operation clixon_process_op_str2int(char *opstr); -int clixon_process_register(clicon_handle h, const char *name, const char *netns, proc_cb_t *callback, char **argv, int argc); +int clixon_process_register(clicon_handle h, const char *name, const char *descr, const char *netns, proc_cb_t *callback, char **argv, int argc); int clixon_process_delete_all(clicon_handle h); -int clixon_process_operation(clicon_handle h, const char *name, proc_operation op, const int wrapit, uint32_t *pid); +int clixon_process_operation(clicon_handle h, const char *name, proc_operation op, const int wrapit); +int clixon_process_status(clicon_handle h, const char *name, cbuf *cbret); int clixon_process_start_all(clicon_handle h); int clixon_process_sched_register(clicon_handle h); int clixon_process_waitpid(clicon_handle h); diff --git a/lib/src/clixon_event.c b/lib/src/clixon_event.c index 1a0a2622..2ab4ed79 100644 --- a/lib/src/clixon_event.c +++ b/lib/src/clixon_event.c @@ -52,6 +52,8 @@ #include #include +#include + #include "clixon_queue.h" #include "clixon_log.h" #include "clixon_hash.h" diff --git a/lib/src/clixon_proc.c b/lib/src/clixon_proc.c index 455ddd11..93e05f4e 100644 --- a/lib/src/clixon_proc.c +++ b/lib/src/clixon_proc.c @@ -71,10 +71,14 @@ #include "clixon_queue.h" #include "clixon_hash.h" #include "clixon_handle.h" +#include "clixon_options.h" #include "clixon_event.h" #include "clixon_sig.h" #include "clixon_string.h" #include "clixon_queue.h" +#include "clixon_yang.h" +#include "clixon_xml.h" +#include "clixon_netconf_lib.h" #include "clixon_proc.h" /* @@ -85,6 +89,7 @@ struct process_entry_t { qelem_t pe_qelem; /* List header */ char *pe_name; /* Name of process used for internal use. Unique with exiting=0 */ + char *pe_description; /* Description of service */ char *pe_netns; /* Network namespace */ char **pe_argv; /* argv with command as element 0 and NULL-terminated */ int pe_argc; /* Length of argc */ @@ -93,6 +98,7 @@ struct process_entry_t { int pe_clone; /* Duplicate when restarting, delete when reaped */ pid_t pe_status; /* Status on exit as defined in waitpid */ proc_operation pe_op; /* Operation pending? */ + struct timeval pe_starttime; /* Start time */ proc_cb_t *pe_callback; /* Wrapper function, may be called from process_operation */ }; @@ -336,6 +342,10 @@ clixon_process_register_dup(process_entry_t *pe0, clicon_err(OE_DB, errno, "strdup name"); goto done; } + if (pe0->pe_description && (pe1->pe_description = strdup(pe0->pe_description)) == NULL){ + clicon_err(OE_DB, errno, "strdup name"); + goto done; + } if (pe0->pe_netns && (pe1->pe_netns = strdup(pe0->pe_netns)) == NULL){ clicon_err(OE_DB, errno, "strdup netns"); goto done; @@ -363,6 +373,7 @@ clixon_process_register_dup(process_entry_t *pe0, * * @param[in] h Clixon handle * @param[in] name Process name + * @param[in] description Description of process * @param[in] netns Namespace netspace (or NULL) * @param[in] callback * @param[in] argv NULL-terminated vector of vectors @@ -374,6 +385,7 @@ clixon_process_register_dup(process_entry_t *pe0, int clixon_process_register(clicon_handle h, const char *name, + const char *description, const char *netns, proc_cb_t *callback, char **argv, @@ -400,6 +412,10 @@ clixon_process_register(clicon_handle h, clicon_err(OE_DB, errno, "strdup name"); goto done; } + if (description && (pe->pe_description = strdup(description)) == NULL){ + clicon_err(OE_DB, errno, "strdup description"); + goto done; + } if (netns && (pe->pe_netns = strdup(netns)) == NULL){ clicon_err(OE_DB, errno, "strdup netns"); goto done; @@ -430,6 +446,8 @@ clixon_process_delete_only(process_entry_t *pe) if (pe->pe_name) free(pe->pe_name); + if (pe->pe_description) + free(pe->pe_description); if (pe->pe_netns) free(pe->pe_netns); if (pe->pe_argv){ @@ -488,13 +506,12 @@ proc_op_run(pid_t pid0, return retval; } -/*! Find process operation entry given name and op and perform operation if found +/*! Find process entry given name and schedule operation * * @param[in] h clicon handle * @param[in] name Name of process * @param[in] op start, stop, restart, status * @param[in] wrapit If set, call potential callback, if false, dont call it - * @param[out] pid >0 process# and is running / 0: not running * @retval -1 Error * @retval 0 OK * @see upgrade_callback_reg_fn which registers the callbacks @@ -507,8 +524,7 @@ int clixon_process_operation(clicon_handle h, const char *name, proc_operation op, - int wrapit, - uint32_t *pid) + int wrapit) { int retval = -1; process_entry_t *pe; @@ -520,25 +536,16 @@ clixon_process_operation(clicon_handle h, pe = _proc_entry_list; do { if (strcmp(pe->pe_name, name) == 0){ - if (op == PROC_OP_STATUS){ - if (pe->pe_clone) - continue; /* this may be a dying duplicate */ - if (pid) - *pid = pe->pe_pid; - } - else { - /* Call wrapper function that eg changes op based on config */ - if (wrapit && pe->pe_callback != NULL) - if (pe->pe_callback(h, pe, &op) < 0) - goto done; - clicon_debug(1, "%s name: %s pid:%d op: %s", __FUNCTION__, - name, pe->pe_pid, clicon_int2str(proc_operation_map, op)); - if (op == PROC_OP_START || op == PROC_OP_STOP || op == PROC_OP_RESTART){ - pe->pe_op = op; - clicon_debug(1, "%s scheduling %s pid:%d", __FUNCTION__, name, pe->pe_pid); - sched++; - } - + /* Call wrapper function that eg changes op based on config */ + if (wrapit && pe->pe_callback != NULL) + if (pe->pe_callback(h, pe, &op) < 0) + goto done; + clicon_debug(1, "%s name: %s pid:%d op: %s", __FUNCTION__, + name, pe->pe_pid, clicon_int2str(proc_operation_map, op)); + if (op == PROC_OP_START || op == PROC_OP_STOP || op == PROC_OP_RESTART){ + pe->pe_op = op; + clicon_debug(1, "%s scheduling %s pid:%d", __FUNCTION__, name, pe->pe_pid); + sched++; } break; /* hit break here */ } @@ -553,6 +560,66 @@ clixon_process_operation(clicon_handle h, return retval; } +/*! Get process status according to clixon-lib.yang + * + * @param[in] h clicon handle + * @param[in] name Name of process + * @param[out] cbret XML status string + * @retval -1 Error + * @retval 0 OK + */ +int +clixon_process_status(clicon_handle h, + const char *name, + cbuf *cbret) + +{ + int retval = -1; + process_entry_t *pe; + int run; + int i; + char timestr[28]; + + if (_proc_entry_list == NULL) + goto ok; + pe = _proc_entry_list; + do { + if (strcmp(pe->pe_name, name) == 0){ + if (pe->pe_clone) + continue; /* this may be a dying duplicate */ + /* Check if running */ + run = 0; + if (pe->pe_pid && proc_op_run(pe->pe_pid, &run) < 0) + goto done; + cprintf(cbret, "%s", + NETCONF_BASE_NAMESPACE, CLIXON_LIB_NS, run?"true":"false"); + if (pe->pe_description) + cprintf(cbret, "%s", CLIXON_LIB_NS, pe->pe_description); + if (pe->pe_pid) + cprintf(cbret, "%u", CLIXON_LIB_NS, pe->pe_pid); + cprintf(cbret, "", CLIXON_LIB_NS); + for (i=0; ipe_argc-1; i++){ + if (i) + cprintf(cbret, " "); + cprintf(cbret, "%s", pe->pe_argv[i]); + } + cprintf(cbret, ""); + cprintf(cbret, "%u", CLIXON_LIB_NS, pe->pe_status); + if (run && time2str(pe->pe_starttime, timestr, sizeof(timestr)) < 0) + goto done; + cprintf(cbret, "%s", CLIXON_LIB_NS, timestr); + cprintf(cbret, ""); + break; /* hit break here */ + } + pe = NEXTQ(process_entry_t *, pe); + } while (pe != _proc_entry_list); + ok: + retval = 0; + done: + clicon_debug(1, "%s retval:%d", __FUNCTION__, retval); + return retval; +} + /*! Go through process list and start all processes that are enabled via config wrap function * @param[in] h Clixon handle * Commit rules should have done this, but there are some cases such as backend -s none mode @@ -623,7 +690,6 @@ clixon_process_sched(int fd, pe->pe_exiting == 0){ /* Check if running */ run = 0; - clicon_debug(1, "%s run: %d", __FUNCTION__, run); if (proc_op_run(pe->pe_pid, &run) < 0) goto done; switch (op){ @@ -642,6 +708,7 @@ clixon_process_sched(int fd, break; if (clixon_proc_background(pe->pe_argv, pe->pe_netns, &newpid) < 0) goto done; + gettimeofday(&pe->pe_starttime, NULL); clicon_debug(1, "%s restart pid:%d -> %d", __FUNCTION__, pe->pe_pid, newpid); /* Create a new pe */ if (clixon_process_register_dup(pe, &pe1) < 0) @@ -655,6 +722,7 @@ clixon_process_sched(int fd, break; if (clixon_proc_background(pe->pe_argv, pe->pe_netns, &pe->pe_pid) < 0) goto done; + gettimeofday(&pe->pe_starttime, NULL); clicon_debug(1, "%s started pid:%d", __FUNCTION__, pe->pe_pid); break; default: diff --git a/test/test_restconf_rpc.sh b/test/test_restconf_rpc.sh index 5a7718ba..4de3cac9 100755 --- a/test/test_restconf_rpc.sh +++ b/test/test_restconf_rpc.sh @@ -76,22 +76,18 @@ $DEFAULTHELLO EOF ) - expect1="" - match=$(echo "$ret" | grep --null -Go "$expect1") - if [ -z "$match" ]; then - err "$expect1" "$ret" - fi - # >&2 echo "ret:$ret" # debug - - expect2="]]>]]>" - match=$(echo "$ret" | grep --null -Go "$expect2") + + expect1="[0-9]*" + match=$(echo "$ret" | grep --null -Go "$expect1") +# >&2 echo "match:$match" # debug if [ -z "$match" ]; then - err "$expect2" "$ret" + pid=0 + else + pid=$(echo "$match" | awk -F'[<>]' '{print $3}') fi - new "check rpc $operation get pid" - pid=$(echo "$ret" | awk -F'[<>]' '{print $5}') >&2 echo "pid:$pid" # debug + if [ -z "$pid" ]; then err "Running process" "$ret" fi @@ -107,9 +103,9 @@ EOF err "Running process" fi fi + echo "$pid" # cant use return that only uses 0-255 fi sleep $DEMSLEEP - echo "$pid" # cant use return that only uses 0-255 } new "ENABLE true" @@ -141,8 +137,8 @@ if [ $BE -ne 0 ]; then fi # For debug ->&2 echo "curl $CURLOPTS -X POST -H \"Content-Type: application/yang-data+json\" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{\"clixon-lib:input\":{\"name\":\"restconf\",\"operation\":\"status\"}}'" -exit +#>&2 echo "curl $CURLOPTS -X POST -H \"Content-Type: application/yang-data+json\" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{\"clixon-lib:input\":{\"name\":\"restconf\",\"operation\":\"status\"}}'" + # Get pid of running process and check return xml new "1. Get rpc status" pid0=$(testrpc status 1) # Save pid0 @@ -165,7 +161,7 @@ new "wait restconf" wait_restconf new "try restconf rpc status" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{"clixon-lib:input":{"name":"restconf","operation":"status"}}')" 0 "HTTP/1.1 200 OK" '{"clixon-lib:output":{"pid":' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{"clixon-lib:input":{"name":"restconf","operation":"status"}}')" 0 "HTTP/1.1 200 OK" '{"clixon-lib:output":' '"active":' '"pid":' new "2. Get status" pid1=$(testrpc status 1) @@ -177,7 +173,7 @@ if [ "$pid0" -ne "$pid1" ]; then fi new "try restconf rpc restart" -expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{"clixon-lib:input":{"name":"restconf","operation":"restart"}}')" 0 "HTTP/1.1 200 OK" '{"clixon-lib:output":{"pid":' +expectpart "$(curl $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/operations/clixon-lib:process-control -d '{"clixon-lib:input":{"name":"restconf","operation":"restart"}}')" 0 "HTTP/1.1 204 No Content" new "3. Get status" pid1=$(testrpc status 1) diff --git a/yang/clixon/clixon-lib@2021-03-08.yang b/yang/clixon/clixon-lib@2021-03-08.yang index 9bdd90b0..cff8808b 100644 --- a/yang/clixon/clixon-lib@2021-03-08.yang +++ b/yang/clixon/clixon-lib@2021-03-08.yang @@ -3,6 +3,9 @@ module clixon-lib { namespace "http://clicon.org/lib"; prefix cl; + import ietf-yang-types { + prefix yang; + } organization "Clicon / Clixon"; @@ -178,13 +181,28 @@ module clixon-lib { case status { description "Output from status rpc"; - leaf status { + leaf active { description "True if process is running, false if not"; type boolean; } + leaf description { + type string; + description "Description of process. This is a static string"; + } + leaf command { + type string; + description "Start command with arguments"; + } + leaf status { + description "Exit Status as defined by waitpid() - if exited"; + type int32; + } + leaf starttime { + description "Time of starting process UTC"; + type yang:date-and-time; + } leaf pid { - description "Process-id of running process or 0 if not running - Value is only valid for operation status"; + description "Process-id of main running process (if active)"; type uint32; } }