diff --git a/src/controller_cli_callbacks.c b/src/controller_cli_callbacks.c index 0b6ed29..0a632d4 100644 --- a/src/controller_cli_callbacks.c +++ b/src/controller_cli_callbacks.c @@ -2005,10 +2005,87 @@ show_transaction_one(cxobj *xc) return retval; } +/*! Show one transaction in detail, human-readable (used to be a raw XML dump) + * + * @param[in] xc XML transaction + * @retval 0 OK + * @retval -1 Error + */ +static int +show_transaction_detail_one(cxobj *xc) +{ + int retval = -1; + cxobj *xdevices; + cxobj *xd; + char *tid; + char *username; + char *state; + char *result; + char *reason; + char *warning; + char *description; + char *origin; + char *timestamp0; + char *timestamp; + char *dname; + char *dresult; + char *dreason; + struct timeval tv0; + struct timeval tv1; + struct timeval tvdiff; + + tid = xml_find_body(xc, "tid"); + username = xml_find_body(xc, "username"); + state = xml_find_body(xc, "state"); + result = xml_find_body(xc, "result"); + reason = xml_find_body(xc, "reason"); + warning = xml_find_body(xc, "warning"); + description = xml_find_body(xc, "description"); + origin = xml_find_body(xc, "origin"); + timestamp0 = xml_find_body(xc, "timestamp0"); + timestamp = xml_find_body(xc, "timestamp"); + + cligen_output(stdout, "Transaction %s\n", tid?tid:"-"); + cligen_output(stdout, " %-13s%s\n", "User:", username?username:"-"); + cligen_output(stdout, " %-13s%s\n", "State:", state?state:"-"); + cligen_output(stdout, " %-13s%s\n", "Result:", result?result:"-"); + cligen_output(stdout, " %-13s%s\n", "Description:", description?description:"-"); + if (origin) + cligen_output(stdout, " %-13s%s\n", "Origin:", origin); + cligen_output(stdout, " %-13s%s\n", "Started:", timestamp0?timestamp0:"-"); + cligen_output(stdout, " %-13s%s\n", "Ended:", timestamp?timestamp:"-"); + if (timestamp0 && timestamp && + str2time(timestamp0, &tv0) == 0 && str2time(timestamp, &tv1) == 0){ + timersub(&tv1, &tv0, &tvdiff); + cligen_output(stdout, " %-13s%ld.%03lds\n", "Duration:", + tvdiff.tv_sec, tvdiff.tv_usec/1000); + } + if (reason) + cligen_output(stdout, " %-13s%s\n", "Reason:", reason); + else if (warning) + cligen_output(stdout, " %-13s%s\n", "Warning:", warning); + if ((xdevices = xml_find_type(xc, NULL, "devices", CX_ELMNT)) != NULL){ + cligen_output(stdout, " Devices:\n"); + xd = NULL; + while ((xd = xml_child_each(xdevices, xd, CX_ELMNT)) != NULL){ + if (strcmp(xml_name(xd), "device") != 0) + continue; + dname = xml_find_body(xd, "name"); + dresult = xml_find_body(xd, "result"); + dreason = xml_find_body(xd, "reason"); + cligen_output(stdout, " %-12s%-10s%s\n", + dname?dname:"-", dresult?dresult:"-", dreason?dreason:""); + } + } + cligen_output(stdout, "\n"); + retval = 0; + return retval; +} + /*! Show controller device states * * @param[in] h - * @param[in] cvv Keywords could be "detail", "all" + * @param[in] cvv Keywords could be "detail", "all", "tid" * @param[in] argv * @retval 0 OK * @retval -1 Error @@ -2018,47 +2095,70 @@ cli_show_transactions(clixon_handle h, cvec *cvv, cvec *argv) { - int retval = -1; - cvec *nsc = NULL; - cxobj *xc; - cxobj *xt; - cxobj *xerr; - cxobj *xn = NULL; /* XML of transactions */ - int all; - int detail; - int nr; - int i; + int retval = -1; + cvec *nsc = NULL; + cxobj *xc; + cxobj *xt; + cxobj *xerr; + cxobj *xn = NULL; /* XML of transactions */ + cbuf *cbxp = NULL; + cg_var *cvtid; + uint64_t tid = 0; + int all; + int detail; + int nr; + int i; all = cvec_find(cvv, "all") != NULL; detail = cvec_find(cvv, "detail") != NULL; + if ((cvtid = cvec_find(cvv, "tid")) != NULL){ + tid = cv_uint64_get(cvtid); + detail = 1; /* A single explicitly named transaction is always shown in detail */ + } /* Get config */ if ((nsc = xml_nsctx_init("co", CONTROLLER_NAMESPACE)) == NULL) goto done; - if (clicon_rpc_get(h, "co:transactions", nsc, CONTENT_ALL, -1, "report-all", &xn) < 0) + if ((cbxp = cbuf_new()) == NULL){ + clixon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + if (tid) + cprintf(cbxp, "co:transactions/co:transaction[co:tid='%" PRIu64 "']", tid); + else + cprintf(cbxp, "co:transactions"); + if (clicon_rpc_get(h, cbuf_get(cbxp), nsc, CONTENT_ALL, -1, "report-all", &xn) < 0) goto done; if ((xerr = xpath_first(xn, NULL, "/rpc-error")) != NULL){ clixon_err_netconf(h, OE_XML, 0, xerr, "Get transactions"); goto done; } /* Change top from "data" to "transactions" */ - if ((xc = xml_find_type(xn, NULL, "transactions", CX_ELMNT)) != NULL){ + if ((xc = xml_find_type(xn, NULL, "transactions", CX_ELMNT)) == NULL){ + if (tid) + cligen_output(stderr, "No such transaction: %" PRIu64 "\n", tid); + } + else{ if (xml_rootchild_node(xn, xc) < 0) goto done; xn = xc; + nr = xml_child_nr_type(xn, CX_ELMNT); + if (tid && nr == 0){ + cligen_output(stderr, "No such transaction: %" PRIu64 "\n", tid); + goto ok; + } if (detail){ - /* Detail mode: show full XML */ - if (all){ - nr = xml_child_nr_type(xn, CX_ELMNT); + /* Detail mode: human-readable per-transaction breakdown */ + if (all || tid){ for (i = nr; i > 0; i--){ if ((xc = xml_child_i(xn, i)) != NULL){ - if (clixon_xml2file(stdout, xc, 0, 1, NULL, cligen_output, 0, 1) < 0) + if (show_transaction_detail_one(xc) < 0) goto done; } } } else{ if ((xc = xml_child_i(xn, xml_child_nr(xn) - 1)) != NULL){ - if (clixon_xml2file(stdout, xc, 0, 1, NULL, cligen_output, 0, 1) < 0) + if (show_transaction_detail_one(xc) < 0) goto done; } } @@ -2074,7 +2174,6 @@ cli_show_transactions(clixon_handle h, "--------", "------------------------------"); if (all){ - nr = xml_child_nr_type(xn, CX_ELMNT); for (i = nr; i > 0; i--){ if ((xc = xml_child_i(xn, i)) != NULL){ if (show_transaction_one(xc) < 0) @@ -2090,8 +2189,11 @@ cli_show_transactions(clixon_handle h, } } } + ok: retval = 0; done: + if (cbxp) + cbuf_free(cbxp); if (nsc) cvec_free(nsc); if (xn) diff --git a/src/controller_operation.cli b/src/controller_operation.cli index 6248072..85cd835 100644 --- a/src/controller_operation.cli +++ b/src/controller_operation.cli @@ -84,7 +84,9 @@ show("Show a particular state of the system"){ @|pipe_common, cli_show_statistics(); } transactions("Show state of last transaction"){ - [] [], cli_show_transactions(); + [(("Show all transactions")| + ("Show a specific transaction by id"))] + [], cli_show_transactions(); @|pipe_show, cli_show_transactions(); } version("Show version"), cli_controller_show_version(); diff --git a/src/controller_rpc.c b/src/controller_rpc.c index 15509ab..40bfc64 100644 --- a/src/controller_rpc.c +++ b/src/controller_rpc.c @@ -3806,6 +3806,7 @@ rpc_device_rpc_template_apply(clixon_handle h, cvec *nsc = NULL; controller_transaction *ct = NULL; cbuf *cberr = NULL; + cbuf *cbtr = NULL; int groups = 0; cvec *devvec = NULL; cg_var *cv; @@ -3892,8 +3893,17 @@ rpc_device_rpc_template_apply(clixon_handle h, if (xml_sort_recurse(xconfig) < 0) goto done; } - /* Initiate new transaction */ - if ((ret = controller_transaction_new(h, ce, clicon_username_get(h), "rpc-device template", 0, &ct, &cberr)) < 0) + /* Initiate new transaction + * Include the template name in the description so it shows up in + * "show transactions" / "show transactions detail" instead of just + * the generic "rpc-device template". + */ + if ((cbtr = cbuf_new()) == NULL){ + clixon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbtr, "rpc-device template %s", tmplname ? tmplname : "(inline)"); + if ((ret = controller_transaction_new(h, ce, clicon_username_get(h), cbuf_get(cbtr), 0, &ct, &cberr)) < 0) goto done; if (ret == 0){ if (netconf_operation_failed(cbret, "application", "%s", cbuf_get(cberr))< 0) @@ -3936,6 +3946,8 @@ rpc_device_rpc_template_apply(clixon_handle h, done: if (cberr) cbuf_free(cberr); + if (cbtr) + cbuf_free(cbtr); if (cvv) cvec_free(cvv); if (xret) diff --git a/test/test-c-service.sh b/test/test-c-service.sh index c076707..645e7d9 100755 --- a/test/test-c-service.sh +++ b/test/test-c-service.sh @@ -595,7 +595,7 @@ fi new "commit diff 4: devices in transaction" ret=$(${clixon_cli} -1f $CFG -E $CFD show transactions detail 2>&1) #echo "ret:$ret" -match=$(echo "$ret" | grep --null -Eo "${IMG}1") || true +match=$(echo "$ret" | grep --null -E "^ ${IMG}1[[:space:]]") || true if [ -z "$match" ]; then err "${IMG}1 in transaction detail" "$ret" fi @@ -862,8 +862,8 @@ function check_devresult() local devblock out=$(${clixon_cli} -1f $CFG -E $CFD show transactions detail 2>&1) - devblock=$(echo "$out" | grep -A 5 "$dev") - resblock=$(echo "$out" | grep "$res") + devblock=$(echo "$out" | grep -E "^ ${dev}[[:space:]]") + resblock=$(echo "$out" | grep -E "^ Result:[[:space:]]*${res}$") if [ -z "$resblock" ]; then err1 "result=$res in transaction detail" "$out" fi diff --git a/test/test-commit-lock-fail.sh b/test/test-commit-lock-fail.sh index be407ce..87a232b 100755 --- a/test/test-commit-lock-fail.sh +++ b/test/test-commit-lock-fail.sh @@ -87,13 +87,13 @@ expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions)" 0 "FAILED" new "show transactions detail: $DEV1 result FAILED (own lock failure)" out=$($clixon_cli -1 -f $CFG -E $CFD show transactions detail 2>&1) -devblock=$(echo "$out" | grep -A 5 "$DEV1") +devblock=$(echo "$out" | grep -E "^ ${DEV1}[[:space:]]") if ! echo "$devblock" | grep -q "FAILED"; then err1 "$DEV1 result FAILED in transaction detail" "$out" fi new "show transactions detail: $DEV2 result FAILED (aborted due to peer failure, not left as default SUCCESS)" -devblock=$(echo "$out" | grep -A 5 "$DEV2") +devblock=$(echo "$out" | grep -E "^ ${DEV2}[[:space:]]") if ! echo "$devblock" | grep -q "FAILED"; then err1 "$DEV2 result FAILED in transaction detail" "$out" fi diff --git a/test/test-connect-timeout-error.sh b/test/test-connect-timeout-error.sh index 5c5a69c..8743714 100755 --- a/test/test-connect-timeout-error.sh +++ b/test/test-connect-timeout-error.sh @@ -76,7 +76,7 @@ new "wait past connect-timeout" sleep 6 new "show transactions detail: $DEV marked ERROR after connect-timeout" -expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions detail)" 0 "$DEV" "ERROR" "Timeout waiting for remote peer" +expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions detail)" 0 "$DEV.*ERROR" "Timeout waiting for remote peer" new "show transactions: brief table Devices column shows 1 ERROR" expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions)" 0 "1 ERROR" diff --git a/test/test-connect.sh b/test/test-connect.sh index 6fe8b43..d0a3981 100755 --- a/test/test-connect.sh +++ b/test/test-connect.sh @@ -186,7 +186,7 @@ if [ "$res" != "$nr1" ]; then fi new "Check errmsg" -expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transaction detail)" 0 "FAILED" "wrong@" "Permission denied (publickey,password,keyboard-interactive)." || true +expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions detail)" 0 "Result:.*FAILED" "wrong@" "Permission denied (publickey,password,keyboard-interactive)." new "Verify first device is framing 1.0" expectpart "$($clixon_cli -1 -f $CFG -E $CFD show state devices device ${IMG}1 netconf-framing)" 0 "netconf-framing 1.0" diff --git a/test/test-dead-connection-fast-fail.sh b/test/test-dead-connection-fast-fail.sh index 48c1e07..a9e6a1a 100755 --- a/test/test-dead-connection-fast-fail.sh +++ b/test/test-dead-connection-fast-fail.sh @@ -155,7 +155,7 @@ expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions)" 0 "FAILED" new "show transactions detail: $DEV marked FAILED" out=$($clixon_cli -1 -f $CFG -E $CFD show transactions detail 2>&1) -devblock=$(echo "$out" | grep -A 5 "$DEV") +devblock=$(echo "$out" | grep -E "^ ${DEV}[[:space:]]") if [ -z "$devblock" ]; then err1 "$DEV in transaction detail" "$out"; fi if ! echo "$devblock" | grep -q "FAILED"; then err1 "$DEV result FAILED in transaction detail" "$out" diff --git a/test/test-service-lock.sh b/test/test-service-lock.sh index 4cd854b..6600838 100755 --- a/test/test-service-lock.sh +++ b/test/test-service-lock.sh @@ -265,10 +265,10 @@ new "Wait until timeout" sleep 10 new "Verify action timeout" -ret=$($clixon_cli -1 -f $CFG -E $CFD show transaction detail) -match=$(echo "$ret" | grep --null -Eo "Timeout waiting for service daemon") || true +ret=$($clixon_cli -1 -f $CFG -E $CFD show transactions detail) +match=$(echo "$ret" | grep --null -F "Timeout waiting for service daemon") || true if [ -z "$match" ]; then - err "" "$ret" + err "Timeout waiting for service daemon" "$ret" fi new "restconf POST service CC" # Here is still held diff --git a/test/test-transaction.sh b/test/test-transaction.sh index 5f52385..a7cf51e 100755 --- a/test/test-transaction.sh +++ b/test/test-transaction.sh @@ -338,31 +338,31 @@ function check_tx(){ out=$($clixon_cli -1 -f $CFG -E $CFD show transactions detail 2>&1) case "$devr" in absent) - if echo "$out" | grep -q "$dev"; then + if echo "$out" | grep -qE "^ ${dev}[[:space:]]"; then err1 "$dev absent from transaction detail" "$out" fi ;; success) - devblock=$(echo "$out" | grep -A 5 "$dev") + devblock=$(echo "$out" | grep -E "^ ${dev}[[:space:]]") if [ -z "$devblock" ]; then err1 "$dev in transaction detail" "$out"; fi if echo "$devblock" | grep -q "SKIPPED\|FAILED"; then err1 "$dev result SUCCESS (not SKIPPED/FAILED)" "$out" fi ;; skip) - devblock=$(echo "$out" | grep -A 5 "$dev") + devblock=$(echo "$out" | grep -E "^ ${dev}[[:space:]]") if ! echo "$devblock" | grep -q "SKIPPED"; then err1 "$dev result SKIPPED in transaction detail" "$out" fi ;; failed) - devblock=$(echo "$out" | grep -A 5 "$dev") + devblock=$(echo "$out" | grep -E "^ ${dev}[[:space:]]") if ! echo "$devblock" | grep -q "FAILED"; then err1 "$dev result FAILED in transaction detail" "$out" fi ;; error) - devblock=$(echo "$out" | grep -A 5 "$dev") + devblock=$(echo "$out" | grep -E "^ ${dev}[[:space:]]") if ! echo "$devblock" | grep -q "ERROR"; then err1 "$dev result ERROR in transaction detail" "$out" fi @@ -569,7 +569,7 @@ new "Timeout: wait past connect-timeout" sleep 6 new "Timeout: show transactions detail: $NAME2 Timeout reason" -expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions detail)" 0 "$NAME2" "ERROR" "Timeout waiting for remote peer" +expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions detail)" 0 "$NAME2.*ERROR" "Timeout waiting for remote peer" new "Timeout: show transactions: brief table Devices column shows 1 ERROR" expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions)" 0 "1 ERROR" @@ -650,6 +650,38 @@ connect_device $NAME2 # Xc3: No edits commit push manual open check_tx $NAME2 "Xc3 commit diff manual changed closed" configure "commit diff" silent success absent +# ============================================================ +# T: show transactions - show a single transaction by id +# ============================================================ +# Read the last transaction's tid from state data rather than parsing the CLI +# table, since its Description/Reason columns can contain spaces (and, for a +# multi-line device reason, embedded newlines). +new "T: get id of last transaction via NETCONF" +ret=$(${clixon_netconf} -qe0 -f $CFG -E $CFD < + + + +]]>]]> +EOF + ) +tid=$(echo "$ret" | grep -o '[0-9]*' | tail -1 | sed 's/\([0-9]*\)<\/tid>/\1/') +if [ -z "$tid" ]; then err1 "at least one transaction exists" "$ret"; fi + +new "T: show transactions shows that transaction, always in detail (no 'detail' keyword needed)" +expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions $tid 2>&1)" 0 "^Transaction $tid\$" "User:" "Devices:" + +new "T: show transactions == show transactions detail" +out1=$($clixon_cli -1 -f $CFG -E $CFD show transactions $tid 2>&1) +out2=$($clixon_cli -1 -f $CFG -E $CFD show transactions $tid detail 2>&1) +if [ "$out1" != "$out2" ]; then + err1 "show transactions identical to show transactions detail" "[$out1] != [$out2]" +fi + +new "T: show transactions reports not found" +badtid=$((tid + 1000000)) +expectpart "$($clixon_cli -1 -f $CFG -E $CFD show transactions $badtid 2>&1)" 0 "No such transaction: $badtid" + # ============================================================ # Cleanup # ============================================================