diff --git a/apps/snmp/snmp_handler.c b/apps/snmp/snmp_handler.c index 61a84721..d236eb74 100644 --- a/apps/snmp/snmp_handler.c +++ b/apps/snmp/snmp_handler.c @@ -309,11 +309,22 @@ snmp_scalar_get(clicon_handle h, } /*! Scalar handler, get a value from clixon + * @param[in] h Clixon handle + * @param[in] ys Yang node + * @param[in] cvk Vector of index/Key variables, if any + * @param[in] db Clixon datastore, typically "candidate" + * @param[in] rowstatus Special case: transform createAndGo -> active, createAndWait -> notInService + * @param[in] reqinfo + * @param[in] requestvb SNMP variables + * @retval 0 OK + * @retval -1 Error */ static int snmp_scalar_set(clicon_handle h, yang_stmt *ys, cvec *cvk, + char *db, + int rowstatus, netsnmp_agent_request_info *reqinfo, netsnmp_request_info *requests) { @@ -333,7 +344,6 @@ snmp_scalar_set(clicon_handle h, int asn1_type; clicon_debug(1, "%s", __FUNCTION__); - if ((yspec = clicon_dbspec_yang(h)) == NULL){ clicon_err(OE_FATAL, 0, "No DB_SPEC"); goto done; @@ -366,6 +376,25 @@ snmp_scalar_set(clicon_handle h, goto done; if (ret == 0) goto ok; + /* Special case translation of rowstatus values: + * createAndGo -> active, createAndWait -> notInService + */ + if (rowstatus){ + if (strcmp(valstr, "createAndGo") == 0){ + free(valstr); + if ((valstr = strdup("active")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + else if (strcmp(valstr, "createAndWait") == 0){ + free(valstr); + if ((valstr = strdup("notInService")) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + } + } if (xml_value_set(xb, valstr) < 0) goto done; if ((cb = cbuf_new()) == NULL){ @@ -374,7 +403,7 @@ snmp_scalar_set(clicon_handle h, } if (clixon_xml2cbuf(cb, xtop, 0, 0, -1, 0) < 0) goto done; - if (clicon_rpc_edit_config(h, "candidate", OP_MERGE, cbuf_get(cb)) < 0) + if (clicon_rpc_edit_config(h, db, OP_MERGE, cbuf_get(cb)) < 0) goto done; ok: retval = 0; @@ -440,7 +469,7 @@ clixon_snmp_scalar_handler(netsnmp_mib_handler *handler, case MODE_SET_RESERVE2: /* 1 */ break; case MODE_SET_ACTION: /* 2 */ - if (snmp_scalar_set(sh->sh_h, sh->sh_ys, NULL, reqinfo, requests) < 0) + if (snmp_scalar_set(sh->sh_h, sh->sh_ys, NULL, "candidate", 0, reqinfo, requests) < 0) goto done; break; case MODE_SET_COMMIT: /* 3 */ @@ -460,6 +489,79 @@ clixon_snmp_scalar_handler(netsnmp_mib_handler *handler, return retval; } +/*! Specialized get-config to get value of row-status, if any + * + * @param[in] h Clixon handle + * @param[in] ys Yang node + * @param[in] cvk Vector of index/Key variables, if any + * @param[out] rowstatus Enmu rowstatus: 0 invalid, 1 active, etc + * @retval 0 OK + * @retval -1 Error + */ +static int +snmp_table_rowstatus_get(clicon_handle h, + yang_stmt *ys, + yang_stmt *yrestype, + cvec *cvk, + int32_t *rowstatus) +{ + int retval = -1; + cvec *nsc = NULL; + cxobj *xt = NULL; + cxobj *xerr; + char *xpath = NULL; + cxobj *xr; + int ret; + char *body; + char *intstr; + char *reason = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + /* Prepare backend call by constructing namespace context */ + if (xml_nsctx_yang(ys, &nsc) < 0) + goto done; + /* Create xpath from yang */ + if (snmp_yang2xpath(ys, cvk, &xpath) < 0) + goto done; + /* Do the backend call */ + if (clicon_rpc_get_config(h, NULL, "candidate", xpath, nsc, &xt) < 0) + goto done; + if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ + clixon_netconf_error(xerr, "clicon_rpc_get_config", NULL); + goto done; + } + if ((xr = xpath_first(xt, nsc, "%s", xpath)) != NULL && + (body = xml_body(xr)) != NULL) { + if ((ret = yang_enum2valstr(yrestype, body, &intstr)) < 0) + goto done; + if (ret == 0){ + clicon_debug(1, "%s %s invalid or not found", __FUNCTION__, body); + *rowstatus = 0; + } + else { + if ((ret = parse_int32(intstr, rowstatus, &reason)) < 0) + goto done; + if (ret == 0){ + clicon_debug(1, "%s parse_int32: %s", __FUNCTION__, reason); + *rowstatus = 0; + } + } + } + else + *rowstatus = 0; + retval = 0; + done: + if (xt) + xml_free(xt); + if (xpath) + free(xpath); + if (nsc) + xml_nsctx_free(nsc); + if (reason) + free(reason); + return retval; +} + /*! Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name * Get yang of leaf from first part of OID * Create xpath with right keys from later part of OID @@ -605,8 +707,11 @@ snmp_table_set(clicon_handle h, size_t oidleaflen = MAX_OID_LEN; oid *oidi; size_t oidilen; + yang_stmt *yi; yang_stmt *ys; + yang_stmt *yrowst; yang_stmt *yk; + yang_stmt *yrestype = NULL; char *xpath = NULL; cvec *cvk_orig; cvec *cvk_val; @@ -615,27 +720,44 @@ snmp_table_set(clicon_handle h, int ret; int asn1_type; netsnmp_variable_list *requestvb; + int rowstatus = 0; + char *origtype; /* Get OID from table /list */ if ((ret = yangext_oid_get(yt, oidt, &oidtlen, NULL)) < 0) goto done; if (ret == 0) goto done; - /* Get yang of leaf from first part of OID */ + /* Get yang of leaf from first part of OID + * and also leaf with rowstatus type + */ ys = NULL; - while ((ys = yn_each(yt, ys)) != NULL) { - if (yang_keyword_get(ys) != Y_LEAF) + yrowst = NULL; + yi = NULL; + while ((yi = yn_each(yt, yi)) != NULL) { + if (yang_keyword_get(yi) != Y_LEAF) continue; /* reset oid */ oidleaflen = MAX_OID_LEN; - if ((ret = yangext_oid_get(ys, oidleaf, &oidleaflen, NULL)) < 0) + if ((ret = yangext_oid_get(yi, oidleaf, &oidleaflen, NULL)) < 0) goto done; if (ret == 0) goto done; if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */ continue; - if (oids[oidleaflen-1] == oidleaf[oidleaflen-1]) - break; + if (oids[oidleaflen-1] == oidleaf[oidleaflen-1]){ + ys = yi; + } + origtype = NULL; + if (snmp_yang_type_get(yi, NULL, &origtype, &yrestype, NULL) < 0) + goto done; + if (strcmp(origtype, "RowStatus") == 0){ + yrowst = yi; + } + if (origtype){ + free(origtype); + origtype = NULL; + } } if (ys == NULL){ /* No leaf with matching OID */ @@ -679,11 +801,67 @@ snmp_table_set(clicon_handle h, clicon_err(OE_YANG, 0, "Expected oidlen 0 but is %zu", oidilen); goto fail; } - if (snmp_scalar_set(h, ys, - cvk_val, - reqinfo, - requests) < 0) - goto done; + if (ys == yrowst){ + if (snmp_scalar_set(h, ys, + cvk_val, + "candidate", + 1, + reqinfo, + requests) < 0) + goto done; + } + else{ + if (yrowst){ + /* Check rowstatus */ + if ((ret = snmp_table_rowstatus_get(h, + yrowst, + yrestype, + cvk_val, + &rowstatus)) < 0) + goto done; + } + else{ + /* If no rowstatus object, default to active */ + rowstatus = 1; + } + switch (rowstatus){ + case 0: // not found, invalid + if ((ret = netsnmp_request_set_error(requests, SNMP_ERR_INCONSISTENTVALUE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto ok; + } + break; + case 1: // active + case 4: // createAndGo + if (snmp_scalar_set(h, ys, + cvk_val, + "candidate", + 0, + reqinfo, + requests) < 0) + goto done; + break; + case 2: // notInService + case 5: // createAndWait + if (snmp_scalar_set(h, ys, + cvk_val, + "candidate", + 0, + reqinfo, + requests) < 0) + goto done; + break; + case 3: // notReady + if ((ret = netsnmp_request_set_error(requests, SNMP_ERR_INCONSISTENTVALUE)) != SNMPERR_SUCCESS){ + clicon_err(OE_SNMP, ret, "netsnmp_request_set_error"); + goto ok; + } + break; + case 6: // destroy + // XXX NYI + break; + } + } ok: retval = 1; done: @@ -691,6 +869,8 @@ snmp_table_set(clicon_handle h, cvec_free(cvk_val); if (xpath) free(xpath); + if (origtype) + free(origtype); return retval; fail: retval = 0; @@ -826,11 +1006,11 @@ snmp_table_getnext(clicon_handle h, * * build_new_oid */ -int -clixon_snmp_table_handler(netsnmp_mib_handler *handler, - netsnmp_handler_registration *nhreg, - netsnmp_agent_request_info *reqinfo, - netsnmp_request_info *requests) +static int +clixon_snmp_table_handler1(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) { int retval = -1; clixon_snmp_handle *sh = NULL; @@ -920,3 +1100,31 @@ clixon_snmp_table_handler(netsnmp_mib_handler *handler, xml_nsctx_free(nsc); return retval; } + + +/*! Top level request handler, loop over individual requests + */ +int +clixon_snmp_table_handler(netsnmp_mib_handler *handler, + netsnmp_handler_registration *nhreg, + netsnmp_agent_request_info *reqinfo, + netsnmp_request_info *requests) +{ + int retval = -1; + netsnmp_request_info *req; + int ret; + + clicon_debug(1, "%s", __FUNCTION__); + for (req = requests; req; req = req->next){ + ret = clixon_snmp_table_handler1(handler, nhreg, reqinfo, req); + if (ret != SNMP_ERR_NOERROR){ + retval = ret; + goto done; + break; + } + + } + retval = SNMP_ERR_NOERROR; + done: + return retval; +} diff --git a/apps/snmp/snmp_lib.c b/apps/snmp/snmp_lib.c index 56537a6f..68236ba7 100644 --- a/apps/snmp/snmp_lib.c +++ b/apps/snmp/snmp_lib.c @@ -261,7 +261,7 @@ oid_print(FILE *f, /*! Variant of yang_type_get that follows leafrefs */ -static int +int snmp_yang_type_get(yang_stmt *ys, yang_stmt **yrefp, char **origtypep, @@ -1000,10 +1000,9 @@ snmp_oid2str(oid **oidi, cprintf(enc, "%c", (char)((*oidi)[i]&0xff)); } break; - case CLIXON_ASN_FIXED_STRING: // XXX - for (; i<7; i++){ + case CLIXON_ASN_FIXED_STRING: + for (; i < *oidilen; i++) cprintf(enc, "%c", (char)((*oidi)[i]&0xff)); - } break; default: break; diff --git a/apps/snmp/snmp_lib.h b/apps/snmp/snmp_lib.h index 61790203..fc47f1f0 100644 --- a/apps/snmp/snmp_lib.h +++ b/apps/snmp/snmp_lib.h @@ -63,7 +63,6 @@ struct clixon_snmp_handle { cvec *sh_cvk_orig; /* Index/Key variable values (original) */ netsnmp_table_registration_info *sh_table_info; /* To mimic table-handler in libnetsnmp code * save only to free properly */ - }; typedef struct clixon_snmp_handle clixon_snmp_handle; @@ -74,6 +73,7 @@ int oid_eq(const oid * objid0, size_t objid0len, const oid * objid1, size_t o int oid_append(const oid *objid0, size_t *objid0len, const oid *objid1, size_t objid1len); int oid_cbuf(cbuf *cb, const oid *objid, size_t objidlen); int oid_print(FILE *f, const oid *objid, size_t objidlen); +int snmp_yang_type_get(yang_stmt *ys, yang_stmt **yrefp, char **origtypep, yang_stmt **yrestypep, char **restypep); int yangext_oid_get(yang_stmt *yn, oid *objid, size_t *objidlen, char **objidstr); int snmp_access_str2int(char *modes_str); const char *snmp_msg_int2str(int msg); diff --git a/test/test_snmp_rowstatus.sh b/test/test_snmp_rowstatus.sh index eb80e8e2..687774fe 100755 --- a/test/test_snmp_rowstatus.sh +++ b/test/test_snmp_rowstatus.sh @@ -102,43 +102,56 @@ function testrun_createAndGo() new "createAndGo" new "Configuring a value without a row is a failure" - echo "$snmpset SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\' = 2" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\' = 2)" 0 "Error in packet." - new "RowStatus is active after createAndGo; can configure additional values afterwards" - echo "$snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = createAndGo SNMP-NOTIFICATION-MIB::snmpNotifyTag.'notify1' = 2" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\' = 2 2>&1)" 2 "Reason: inconsistentValue" + + new "Set RowStatus to CreateAndGo and set tag" expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = createAndGo SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\' = 2)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = INTEGER: createAndGo(4)" + new "Rowstatus is active" expectpart "$($snmpget SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\')" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = INTEGER: active(1)" - expectpart "$($snmpget SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\')" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyTag.'notify1' = 2" + new "Get tag" + expectpart "$($snmpget SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\')" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyTag.'notify1' = STRING: 2" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.\'notify1\' = 1)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.'notify1' = 1" + new "set storage type" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.\'notify1\' = 1)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.'notify1' = INTEGER: other(1)" } function testrun_createAndWait() { new "createAndWait" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = createAndWait SNMP-NOTIFICATION-MIB::snmpNotifyTag.'notify1' = 2)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = createAndWait" + new "Set RowStatus to CreateAndWait and set tag" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = createAndWait SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\' = 2)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = INTEGER: createAndWait(5)" - expectpart "$($snmpget SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\')" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyTag.'notify1' = 2" + new "Get tag" + expectpart "$($snmpget SNMP-NOTIFICATION-MIB::snmpNotifyTag.\'notify1\')" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyTag.'notify1' = STRING: 2" + new "Get rowstatus" expectpart "$($snmpget SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\')" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = INTEGER: notInService(2)" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.\'notify1\' = 1)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.'notify1' = 1" + new "Set storagetype" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.\'notify1\' = 1)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.'notify1' = INTEGER: other(1)" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = active)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = active" + new "Set rowstatus to active/ commit" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = active)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = INTEGER: active(1)" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.\'notify1\' = 5)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.'notify1' = 5" + new "Set storagetype again" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.\'notify1\' = 5)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyStorageType.'notify1' = INTEGER: readOnly(5)" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = createAndWait)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = createAndWait" + new "Set rowstatus to createAndWait" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = createAndWait)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = INTEGER: createAndWait(5)" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify2' = createAndGo)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify2' = createAndGo" + new "Set second rowstatus to createAndGo" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify2\' = createAndGo)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify2' = INTEGER: createAndGo(4)" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify3' = createAndWait)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify3' = createAndWait" + new "Set third rowstatus to createAndWait" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify3\' = createAndWait)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify3' = INTEGER: createAndWait(5)" - expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify3' = active)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify3' = active" + new "Set third rowstatus to active" + expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify3\' = active)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify3' = INTEGER: active(1)" + new "Get rowstatus" expectpart "$($snmpget SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\')" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = INTEGER: notInService(2)" } @@ -146,12 +159,16 @@ function testrun_removeRows() { new "removeRows" + new "Set rowstatus to createandgo" expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = createAndGo)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1'" + new "Set rowstatus to destroy" expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\' = destroy)" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1' = destroy" + new "Set rowstatus to destroy" expectpart "$($snmpset SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\')" 0 "SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.'notify1'" + new "get rowstatus" expectpart "$($snmpget SNMP-NOTIFICATION-MIB::snmpNotifyRowStatus.\'notify1\')" 0 "No Such Instance currently exists at this OID)" } @@ -163,9 +180,12 @@ function testexit() new "SNMP tests" testinit +testrun_createAndGo + +testrun_createAndWait + if $snmp_debug; then - testrun_createAndGo - testrun_createAndWait + # NYI testrun_removeRows fi diff --git a/test/test_snmp_set.sh b/test/test_snmp_set.sh index 1549c725..b00f4e38 100755 --- a/test/test_snmp_set.sh +++ b/test/test_snmp_set.sh @@ -197,10 +197,9 @@ function testrun() else echo "$snmpset $oid $set_type $value" expectpart "$($snmpset $oid $set_type $value)" 0 "$type: $value2" - fi - new "Check $name via SNMP" - if [ $type == "STRING" ]; then + new "Check $name via SNMP" + if [ "$type" == "STRING" ]; then expectpart "$($snmpget $oid)" 0 "$type:" "$value" else expectpart "$($snmpget $oid)" 0 "$type: $value2" @@ -227,7 +226,10 @@ testrun clixonExampleString STRING foobar foobar foobar ${MIB}.1.3 testrun ifPromiscuousMode INTEGER 1 1 true ${MIB}.1.10 # boolean testrun ifIpAddr IPADDRESS 1.2.3.4 1.2.3.4 1.2.3.4 ${MIB}.1.13 # InetAddress testrun ifPhysAddress STRING ff:ee:dd:cc:bb:aa ff:ee:dd:cc:bb:aa ff:ee:dd:cc:bb:aa ${IFMIB}.2.2.1.6.1 -testrun ifStackStatus INTEGER 4 "createAndGo(4)" createAndGo ${IFMIB}.31.1.2.1.3.5.9 + +if $snmp_debug; then # rowstatus + testrun ifStackStatus INTEGER 4 "createAndGo(4)" active ${IFMIB}.31.1.2.1.3.5.9 +fi new "Cleaning up" testexit