diff options
Diffstat (limited to 'agent/mibgroup/disman/event/mteTrigger.c')
-rw-r--r-- | agent/mibgroup/disman/event/mteTrigger.c | 1200 |
1 files changed, 1200 insertions, 0 deletions
diff --git a/agent/mibgroup/disman/event/mteTrigger.c b/agent/mibgroup/disman/event/mteTrigger.c new file mode 100644 index 0000000..7fca1b0 --- /dev/null +++ b/agent/mibgroup/disman/event/mteTrigger.c @@ -0,0 +1,1200 @@ +/* + * DisMan Event MIB: + * Core implementation of the trigger handling behaviour + */ + +#include <net-snmp/net-snmp-config.h> +#include <net-snmp/net-snmp-features.h> +#include <net-snmp/net-snmp-includes.h> +#include <net-snmp/agent/net-snmp-agent-includes.h> +#include "disman/event/mteTrigger.h" +#include "disman/event/mteEvent.h" + +netsnmp_feature_child_of(disman_debugging, libnetsnmpmibs) +netsnmp_feature_child_of(mtetrigger, libnetsnmpmibs) +netsnmp_feature_child_of(mtetrigger_removeentry, mtetrigger) + +netsnmp_tdata *trigger_table_data; + +oid _sysUpTime_instance[] = { 1, 3, 6, 1, 2, 1, 1, 3, 0 }; +size_t _sysUpTime_inst_len = OID_LENGTH(_sysUpTime_instance); + +long mteTriggerFailures; + + /* + * Initialize the container for the (combined) mteTrigger*Table, + * regardless of which table initialisation routine is called first. + */ + +void +init_trigger_table_data(void) +{ + DEBUGMSGTL(("disman:event:init", "init trigger container\n")); + if (!trigger_table_data) { + trigger_table_data = netsnmp_tdata_create_table("mteTriggerTable", 0); + if (!trigger_table_data) { + snmp_log(LOG_ERR, "failed to create mteTriggerTable"); + return; + } + DEBUGMSGTL(("disman:event:init", "create trigger container (%p)\n", + trigger_table_data)); + } + mteTriggerFailures = 0; +} + + +/** Initializes the mteTrigger module */ +void +init_mteTrigger(void) +{ + init_trigger_table_data(); + +} + + /* =================================================== + * + * APIs for maintaining the contents of the (combined) + * mteTrigger*Table container. + * + * =================================================== */ + +#ifndef NETSNMP_FEATURE_REMOVE_DISMAN_DEBUGGING +void +_mteTrigger_dump(void) +{ + struct mteTrigger *entry; + netsnmp_tdata_row *row; + int i = 0; + + for (row = netsnmp_tdata_row_first(trigger_table_data); + row; + row = netsnmp_tdata_row_next(trigger_table_data, row)) { + entry = (struct mteTrigger *)row->data; + DEBUGMSGTL(("disman:event:dump", "TriggerTable entry %d: ", i)); + DEBUGMSGOID(("disman:event:dump", row->oid_index.oids, row->oid_index.len)); + DEBUGMSG(("disman:event:dump", "(%s, %s)", + row->indexes->val.string, + row->indexes->next_variable->val.string)); + DEBUGMSG(("disman:event:dump", ": %p, %p\n", row, entry)); + i++; + } + DEBUGMSGTL(("disman:event:dump", "TriggerTable %d entries\n", i)); +} +#endif /* NETSNMP_FEATURE_REMOVE_DISMAN_DEBUGGING */ + +/* + * Create a new row in the trigger table + */ +netsnmp_tdata_row * +mteTrigger_createEntry(const char *mteOwner, char *mteTName, int fixed) +{ + struct mteTrigger *entry; + netsnmp_tdata_row *row; + size_t mteOwner_len = (mteOwner) ? strlen(mteOwner) : 0; + size_t mteTName_len = (mteTName) ? strlen(mteTName) : 0; + + DEBUGMSGTL(("disman:event:table", "Create trigger entry (%s, %s)\n", + mteOwner, mteTName)); + /* + * Create the mteTrigger entry, and the + * (table-independent) row wrapper structure... + */ + entry = SNMP_MALLOC_TYPEDEF(struct mteTrigger); + if (!entry) + return NULL; + + row = netsnmp_tdata_create_row(); + if (!row) { + SNMP_FREE(entry); + return NULL; + } + row->data = entry; + + /* + * ... initialize this row with the indexes supplied + * and the default values for the row... + */ + if (mteOwner) + memcpy(entry->mteOwner, mteOwner, mteOwner_len); + netsnmp_table_row_add_index(row, ASN_OCTET_STR, + entry->mteOwner, mteOwner_len); + if (mteTName) + memcpy(entry->mteTName, mteTName, mteTName_len); + netsnmp_table_row_add_index(row, ASN_PRIV_IMPLIED_OCTET_STR, + entry->mteTName, mteTName_len); + + /* entry->mteTriggerTest = MTE_TRIGGER_BOOLEAN; */ + entry->mteTriggerValueID_len = 2; /* .0.0 */ + entry->mteTriggerFrequency = 600; + memcpy(entry->mteDeltaDiscontID, _sysUpTime_instance, + sizeof(_sysUpTime_instance)); + entry->mteDeltaDiscontID_len = _sysUpTime_inst_len; + entry->mteDeltaDiscontIDType = MTE_DELTAD_TTICKS; + entry->flags |= MTE_TRIGGER_FLAG_SYSUPT; + entry->mteTExTest = (MTE_EXIST_PRESENT | MTE_EXIST_ABSENT); + entry->mteTExStartup = (MTE_EXIST_PRESENT | MTE_EXIST_ABSENT); + entry->mteTBoolComparison = MTE_BOOL_UNEQUAL; + entry->flags |= MTE_TRIGGER_FLAG_BSTART; + entry->mteTThStartup = MTE_THRESH_START_RISEFALL; + + if (fixed) + entry->flags |= MTE_TRIGGER_FLAG_FIXED; + + /* + * ... and insert the row into the (common) table container + */ + netsnmp_tdata_add_row(trigger_table_data, row); + DEBUGMSGTL(("disman:event:table", "Trigger entry created\n")); + return row; +} + +#ifndef NETSNMP_FEATURE_REMOVE_MTETRIGGER_REMOVEENTRY +/* + * Remove a row from the trigger table + */ +void +mteTrigger_removeEntry(netsnmp_tdata_row *row) +{ + struct mteTrigger *entry; + + if (!row) + return; /* Nothing to remove */ + entry = (struct mteTrigger *) + netsnmp_tdata_remove_and_delete_row(trigger_table_data, row); + if (entry) { + mteTrigger_disable( entry ); + SNMP_FREE(entry); + } +} +#endif /* NETSNMP_FEATURE_REMOVE_MTETRIGGER_REMOVEENTRY */ + + /* =================================================== + * + * APIs for evaluating a trigger, + * and firing the appropriate event + * + * =================================================== */ +const char *_ops[] = { "", + "!=", /* MTE_BOOL_UNEQUAL */ + "==", /* MTE_BOOL_EQUAL */ + "<", /* MTE_BOOL_LESS */ + "<=", /* MTE_BOOL_LESSEQUAL */ + ">", /* MTE_BOOL_GREATER */ + ">=" /* MTE_BOOL_GREATEREQUAL */ }; + +void +_mteTrigger_failure( /* int error, */ const char *msg ) +{ + /* + * XXX - Send an mteTriggerFailure trap + * (if configured to do so) + */ + mteTriggerFailures++; + snmp_log(LOG_ERR, "%s\n", msg ); + return; +} + +void +mteTrigger_run( unsigned int reg, void *clientarg) +{ + struct mteTrigger *entry = (struct mteTrigger *)clientarg; + netsnmp_variable_list *var, *vtmp; + netsnmp_variable_list *vp1, *vp1_prev; + netsnmp_variable_list *vp2, *vp2_prev; + netsnmp_variable_list *dvar = NULL; + netsnmp_variable_list *dv1 = NULL, *dv2 = NULL; + netsnmp_variable_list sysUT_var; + int cmp = 0, n, n2; + long value; + const char *reason; + + if (!entry) { + snmp_alarm_unregister( reg ); + return; + } + if (!(entry->flags & MTE_TRIGGER_FLAG_ENABLED ) || + !(entry->flags & MTE_TRIGGER_FLAG_ACTIVE ) || + !(entry->flags & MTE_TRIGGER_FLAG_VALID )) { + return; + } + + { + extern netsnmp_agent_session *netsnmp_processing_set; + if (netsnmp_processing_set) { + /* + * netsnmp_handle_request will not be responsive to our efforts to + * Retrieve the requested MIB value(s)... + * so we will skip it. + * https://sourceforge.net/tracker/ + * index.php?func=detail&aid=1557406&group_id=12694&atid=112694 + */ + DEBUGMSGTL(("disman:event:trigger:monitor", + "Skipping trigger (%s) while netsnmp_processing_set\n", + entry->mteTName)); + return; + } + } + + /* + * Retrieve the requested MIB value(s)... + */ + DEBUGMSGTL(( "disman:event:trigger:monitor", "Running trigger (%s)\n", entry->mteTName)); + var = (netsnmp_variable_list *)SNMP_MALLOC_TYPEDEF( netsnmp_variable_list ); + if (!var) { + _mteTrigger_failure("failed to create mteTrigger query varbind"); + return; + } + snmp_set_var_objid( var, entry->mteTriggerValueID, + entry->mteTriggerValueID_len ); + if ( entry->flags & MTE_TRIGGER_FLAG_VWILD ) { + n = netsnmp_query_walk( var, entry->session ); + } else { + n = netsnmp_query_get( var, entry->session ); + } + if ( n != SNMP_ERR_NOERROR ) { + DEBUGMSGTL(( "disman:event:trigger:monitor", "Trigger query (%s) failed: %d\n", + (( entry->flags & MTE_TRIGGER_FLAG_VWILD ) ? "walk" : "get"), n)); + _mteTrigger_failure( "failed to run mteTrigger query" ); + snmp_free_varbind(var); + return; + } + + /* + * ... canonicalise the results (to simplify later comparisons)... + */ + + vp1 = var; vp1_prev = NULL; + vp2 = entry->old_results; vp2_prev = NULL; + entry->count=0; + while (vp1) { + /* + * Flatten various missing values/exceptions into a single form + */ + switch (vp1->type) { + case SNMP_NOSUCHINSTANCE: + case SNMP_NOSUCHOBJECT: + case ASN_PRIV_RETRY: /* Internal only ? */ + vp1->type = ASN_NULL; + } + /* + * Keep track of how many entries have been retrieved. + */ + entry->count++; + + /* + * Ensure previous and current result match + * (with corresponding entries in both lists) + * and set the flags indicating which triggers are armed + */ + if (vp2) { + cmp = snmp_oid_compare(vp1->name, vp1->name_length, + vp2->name, vp2->name_length); + if ( cmp < 0 ) { + /* + * If a new value has appeared, insert a matching + * dummy entry into the previous result list. + * + * XXX - check how this is best done. + */ + vtmp = SNMP_MALLOC_TYPEDEF( netsnmp_variable_list ); + if (!vtmp) { + _mteTrigger_failure( + "failed to create mteTrigger temp varbind"); + snmp_free_varbind(var); + return; + } + vtmp->type = ASN_NULL; + snmp_set_var_objid( vtmp, vp1->name, vp1->name_length ); + vtmp->next_variable = vp2; + if (vp2_prev) { + vp2_prev->next_variable = vtmp; + } else { + entry->old_results = vtmp; + } + vp2_prev = vtmp; + vp1->index = MTE_ARMED_ALL; /* XXX - plus a new flag */ + vp1_prev = vp1; + vp1 = vp1->next_variable; + } + else if ( cmp == 0 ) { + /* + * If it's a continuing entry, just copy across the armed flags + */ + vp1->index = vp2->index; + vp1_prev = vp1; + vp1 = vp1->next_variable; + vp2_prev = vp2; + vp2 = vp2->next_variable; + } else { + /* + * If a value has just disappeared, insert a + * matching dummy entry into the current result list. + * + * XXX - check how this is best done. + * + */ + if ( vp2->type != ASN_NULL ) { + vtmp = SNMP_MALLOC_TYPEDEF( netsnmp_variable_list ); + if (!vtmp) { + _mteTrigger_failure( + "failed to create mteTrigger temp varbind"); + snmp_free_varbind(var); + return; + } + vtmp->type = ASN_NULL; + snmp_set_var_objid( vtmp, vp2->name, vp2->name_length ); + vtmp->next_variable = vp1; + if (vp1_prev) { + vp1_prev->next_variable = vtmp; + } else { + var = vtmp; + } + vp1_prev = vtmp; + vp2_prev = vp2; + vp2 = vp2->next_variable; + } else { + /* + * But only if this entry has *just* disappeared. If the + * entry from the last run was a dummy too, then remove it. + * (leaving vp2_prev unchanged) + */ + vtmp = vp2; + if (vp2_prev) { + vp2_prev->next_variable = vp2->next_variable; + } else { + entry->old_results = vp2->next_variable; + } + vp2 = vp2->next_variable; + vtmp->next_variable = NULL; + snmp_free_varbind( vtmp ); + } + } + } else { + /* + * No more old results to compare. + * Either all remaining values have only just been created ... + * (and we need to create dummy 'old' entries for them) + */ + if ( vp2_prev ) { + vtmp = SNMP_MALLOC_TYPEDEF( netsnmp_variable_list ); + if (!vtmp) { + _mteTrigger_failure( + "failed to create mteTrigger temp varbind"); + snmp_free_varbind(var); + return; + } + vtmp->type = ASN_NULL; + snmp_set_var_objid( vtmp, vp1->name, vp1->name_length ); + vtmp->next_variable = vp2_prev->next_variable; + vp2_prev->next_variable = vtmp; + vp2_prev = vtmp; + } + /* + * ... or this is the first run through + * (and there were no old results at all) + * + * In either case, mark the current entry as armed and new. + * Note that we no longer need to maintain 'vp1_prev' + */ + vp1->index = MTE_ARMED_ALL; /* XXX - plus a new flag */ + vp1 = vp1->next_variable; + } + } + + /* + * ... and then work through these result(s), deciding + * whether or not to trigger the corresponding event. + * + * Note that there's no point in evaluating Existence or + * Boolean tests if there's no corresponding event. + * (Even if the trigger matched, nothing would be done anyway). + */ + if ((entry->mteTriggerTest & MTE_TRIGGER_EXISTENCE) && + (entry->mteTExEvent[0] != '\0' )) { + /* + * If we don't have a record of previous results, + * this must be the first time through, so consider + * the mteTriggerExistenceStartup tests. + */ + if ( !entry->old_results ) { + /* + * With the 'present(0)' test, the trigger should fire + * for each value in the varbind list returned + * (whether the monitored value is wildcarded or not). + */ + if (entry->mteTExTest & entry->mteTExStartup & MTE_EXIST_PRESENT) { + for (vp1 = var; vp1; vp1=vp1->next_variable) { + DEBUGMSGTL(( "disman:event:trigger:fire", + "Firing initial existence test: ")); + DEBUGMSGOID(("disman:event:trigger:fire", + vp1->name, vp1->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", + " (present)\n"));; + entry->mteTriggerXOwner = entry->mteTExObjOwner; + entry->mteTriggerXObjects = entry->mteTExObjects; + entry->mteTriggerFired = vp1; + n = entry->mteTriggerValueID_len; + mteEvent_fire(entry->mteTExEvOwner, entry->mteTExEvent, + entry, vp1->name+n, vp1->name_length-n); + } + } + /* + * An initial 'absent(1)' test only makes sense when + * monitoring a non-wildcarded OID (how would we know + * which rows of the table "ought" to exist, but don't?) + */ + if (entry->mteTExTest & entry->mteTExStartup & MTE_EXIST_ABSENT) { + if (!(entry->flags & MTE_TRIGGER_FLAG_VWILD) && + var->type == ASN_NULL ) { + DEBUGMSGTL(( "disman:event:trigger:fire", + "Firing initial existence test: ")); + DEBUGMSGOID(("disman:event:trigger:fire", + var->name, var->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", + " (absent)\n"));; + entry->mteTriggerXOwner = entry->mteTExObjOwner; + entry->mteTriggerXObjects = entry->mteTExObjects; + /* + * It's unclear what value the 'mteHotValue' payload + * should take when a monitored instance does not + * exist on startup. The only sensible option is + * to report a NULL value, but this clashes with + * the syntax of the mteHotValue MIB object. + */ + entry->mteTriggerFired = var; + n = entry->mteTriggerValueID_len; + mteEvent_fire(entry->mteTExEvOwner, entry->mteTExEvent, + entry, var->name+n, var->name_length-n); + } + } + } /* !old_results */ + /* + * Otherwise, compare the current set of results with + * the previous ones, looking for changes. We can + * assume that the two lists match (see above). + */ + else { + for (vp1 = var, vp2 = entry->old_results; + vp1; + vp1=vp1->next_variable, vp2=vp2->next_variable) { + + /* Use this field to indicate that the trigger should fire */ + entry->mteTriggerFired = NULL; + reason = NULL; + + if ((entry->mteTExTest & MTE_EXIST_PRESENT) && + (vp1->type != ASN_NULL) && + (vp2->type == ASN_NULL)) { + /* A new instance has appeared */ + entry->mteTriggerFired = vp1; + reason = "(present)"; + + } else if ((entry->mteTExTest & MTE_EXIST_ABSENT) && + (vp1->type == ASN_NULL) && + (vp2->type != ASN_NULL)) { + + /* + * A previous instance has disappeared. + * + * It's unclear what value the 'mteHotValue' payload + * should take when this happens - the previous + * value (vp2), or a NULL value (vp1) ? + * NULL makes more sense logically, but clashes + * with the syntax of the mteHotValue MIB object. + */ + entry->mteTriggerFired = vp2; + reason = "(absent)"; + + } else if ((entry->mteTExTest & MTE_EXIST_CHANGED) && + ((vp1->val_len != vp2->val_len) || + (memcmp( vp1->val.string, vp2->val.string, + vp1->val_len) != 0 ))) { + /* + * This comparison detects changes in *any* type + * of value, numeric or string (or even OID). + * + * Unfortunately, the default 'mteTriggerFired' + * notification payload can't report non-numeric + * changes properly (see syntax of 'mteHotValue') + */ + entry->mteTriggerFired = vp1; + reason = "(changed)"; + } + if ( entry->mteTriggerFired ) { + /* + * One of the above tests has matched, + * so fire the trigger. + */ + DEBUGMSGTL(( "disman:event:trigger:fire", + "Firing existence test: ")); + DEBUGMSGOID(("disman:event:trigger:fire", + vp1->name, vp1->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", + " %s\n", reason));; + entry->mteTriggerXOwner = entry->mteTExObjOwner; + entry->mteTriggerXObjects = entry->mteTExObjects; + n = entry->mteTriggerValueID_len; + mteEvent_fire(entry->mteTExEvOwner, entry->mteTExEvent, + entry, vp1->name+n, vp1->name_length-n); + } + } + } /* !old_results - end of else block */ + } /* MTE_TRIGGER_EXISTENCE */ + + + if (( entry->mteTriggerTest & MTE_TRIGGER_BOOLEAN ) || + ( entry->mteTriggerTest & MTE_TRIGGER_THRESHOLD )) { + /* + * Although Existence tests can work with any syntax values, + * Boolean and Threshold tests are integer-only. Ensure that + * the returned value(s) are appropriate. + * + * Note that we only need to check the first value, since all + * instances of a given object should have the same syntax. + */ + switch (var->type) { + case ASN_INTEGER: + case ASN_COUNTER: + case ASN_GAUGE: + case ASN_TIMETICKS: + case ASN_UINTEGER: + case ASN_COUNTER64: +#ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES + case ASN_OPAQUE_COUNTER64: + case ASN_OPAQUE_U64: + case ASN_OPAQUE_I64: +#endif + /* OK */ + break; + default: + /* + * Other syntax values can't be used for Boolean/Theshold + * tests. Report this as an error, and then rotate the + * results ready for the next run, (which will presumably + * also detect this as an error once again!) + */ + DEBUGMSGTL(( "disman:event:trigger:fire", + "Returned non-integer result(s): ")); + DEBUGMSGOID(("disman:event:trigger:fire", + var->name, var->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", + " (boolean/threshold) %d\n", var->type));; + snmp_free_varbind( entry->old_results ); + entry->old_results = var; + return; + } + + + /* + * Retrieve the discontinuity markers for delta-valued samples. + * (including sysUpTime.0 if not specified explicitly). + */ + if ( entry->flags & MTE_TRIGGER_FLAG_DELTA ) { + /* + * We'll need sysUpTime.0 regardless... + */ + DEBUGMSGTL(("disman:event:delta", "retrieve sysUpTime.0\n")); + memset( &sysUT_var, 0, sizeof( netsnmp_variable_list )); + snmp_set_var_objid( &sysUT_var, _sysUpTime_instance, + _sysUpTime_inst_len ); + netsnmp_query_get( &sysUT_var, entry->session ); + + if (!(entry->flags & MTE_TRIGGER_FLAG_SYSUPT)) { + /* + * ... but only retrieve the configured discontinuity + * marker(s) if they refer to something different. + */ + DEBUGMSGTL(( "disman:event:delta", + "retrieve discontinuity marker(s): ")); + DEBUGMSGOID(("disman:event:delta", entry->mteDeltaDiscontID, + entry->mteDeltaDiscontID_len )); + DEBUGMSG(( "disman:event:delta", " %s\n", + (entry->flags & MTE_TRIGGER_FLAG_DWILD ? " (wild)" : ""))); + + dvar = (netsnmp_variable_list *) + SNMP_MALLOC_TYPEDEF( netsnmp_variable_list ); + if (!dvar) { + _mteTrigger_failure( + "failed to create mteTrigger delta query varbind"); + return; + } + snmp_set_var_objid( dvar, entry->mteDeltaDiscontID, + entry->mteDeltaDiscontID_len ); + if ( entry->flags & MTE_TRIGGER_FLAG_DWILD ) { + n = netsnmp_query_walk( dvar, entry->session ); + } else { + n = netsnmp_query_get( dvar, entry->session ); + } + if ( n != SNMP_ERR_NOERROR ) { + _mteTrigger_failure( "failed to run mteTrigger delta query" ); + snmp_free_varbind( dvar ); + return; + } + } + + /* + * We can't calculate delta values the first time through, + * so there's no point in evaluating the remaining tests. + * + * Save the results (and discontinuity markers), + * ready for the next run. + */ + if ( !entry->old_results ) { + entry->old_results = var; + entry->old_deltaDs = dvar; + entry->sysUpTime = *sysUT_var.val.integer; + return; + } + /* + * If the sysUpTime marker has been reset (or strictly, + * has advanced by less than the monitor frequency), + * there's no point in trying the remaining tests. + */ + + if (*sysUT_var.val.integer < entry->sysUpTime) { + DEBUGMSGTL(( "disman:event:delta", + "single discontinuity: (sysUT)\n")); + snmp_free_varbind( entry->old_results ); + snmp_free_varbind( entry->old_deltaDs ); + entry->old_results = var; + entry->old_deltaDs = dvar; + entry->sysUpTime = *sysUT_var.val.integer; + return; + } + /* + * Similarly if a separate (non-wildcarded) discontinuity + * marker has changed, then there's no + * point in trying to evaluate these tests either. + */ + if (!(entry->flags & MTE_TRIGGER_FLAG_DWILD) && + !(entry->flags & MTE_TRIGGER_FLAG_SYSUPT) && + (!entry->old_deltaDs || + (entry->old_deltaDs->val.integer != dvar->val.integer))) { + DEBUGMSGTL(( "disman:event:delta", "single discontinuity: (")); + DEBUGMSGOID(( "disman:event:delta", entry->mteDeltaDiscontID, + entry->mteDeltaDiscontID_len)); + DEBUGMSG(( "disman:event:delta", ")\n")); + snmp_free_varbind( entry->old_results ); + snmp_free_varbind( entry->old_deltaDs ); + entry->old_results = var; + entry->old_deltaDs = dvar; + entry->sysUpTime = *sysUT_var.val.integer; + return; + } + + /* + * Ensure that the list of (wildcarded) discontinuity + * markers matches the list of monitored values + * (inserting/removing discontinuity varbinds as needed) + * + * XXX - An alternative approach would be to use the list + * of monitored values (instance subidentifiers) to build + * the exact list of delta markers to retrieve earlier. + */ + if (entry->flags & MTE_TRIGGER_FLAG_DWILD) { + vp1 = var; + vp2 = dvar; + vp2_prev = NULL; + n = entry->mteTriggerValueID_len; + n2 = entry->mteDeltaDiscontID_len; + while (vp1) { + /* + * For each monitored instance, check whether + * there's a matching discontinuity entry. + */ + cmp = snmp_oid_compare(vp1->name+n, vp1->name_length-n, + vp2->name+n2, vp2->name_length-n2 ); + if ( cmp < 0 ) { + /* + * If a discontinuity entry is missing, + * insert a (dummy) varbind. + * The corresponding delta calculation will + * fail, but this simplifies the later code. + */ + vtmp = (netsnmp_variable_list *) + SNMP_MALLOC_TYPEDEF( netsnmp_variable_list ); + if (!vtmp) { + _mteTrigger_failure( + "failed to create mteTrigger discontinuity varbind"); + snmp_free_varbind(dvar); + return; + } + snmp_set_var_objid(vtmp, entry->mteDeltaDiscontID, + entry->mteDeltaDiscontID_len); + /* XXX - append instance subids */ + vtmp->next_variable = vp2; + vp2_prev->next_variable = vtmp; + vp2_prev = vtmp; + vp1 = vp1->next_variable; + } else if ( cmp == 0 ) { + /* + * Matching discontinuity entry - all OK. + */ + vp2_prev = vp2; + vp2 = vp2->next_variable; + vp1 = vp1->next_variable; + } else { + /* + * Remove unneeded discontinuity entry + */ + vtmp = vp2; + vp2_prev->next_variable = vp2->next_variable; + vp2 = vp2->next_variable; + vtmp->next_variable = NULL; + snmp_free_varbind( vtmp ); + } + } + /* + * XXX - Now need to ensure that the old list of + * delta discontinuity markers matches as well. + */ + } + } /* delta samples */ + } /* Boolean/Threshold test checks */ + + + + /* + * Only run the Boolean tests if there's an event to be triggered + */ + if ((entry->mteTriggerTest & MTE_TRIGGER_BOOLEAN) && + (entry->mteTBoolEvent[0] != '\0' )) { + + if (entry->flags & MTE_TRIGGER_FLAG_DELTA) { + vp2 = entry->old_results; + if (entry->flags & MTE_TRIGGER_FLAG_DWILD) { + dv1 = dvar; + dv2 = entry->old_deltaDs; + } + } + for ( vp1 = var; vp1; vp1=vp1->next_variable ) { + /* + * Determine the value to be monitored... + */ + if ( !vp1->val.integer ) { /* No value */ + if ( vp2 ) + vp2 = vp2->next_variable; + continue; + } + if (entry->flags & MTE_TRIGGER_FLAG_DELTA) { + if (entry->flags & MTE_TRIGGER_FLAG_DWILD) { + /* + * We've already checked any non-wildcarded + * discontinuity markers (inc. sysUpTime.0). + * Validate this particular sample against + * the relevant wildcarded marker... + */ + if ((dv1->type == ASN_NULL) || + (dv1->type != dv2->type) || + (*dv1->val.integer != *dv2->val.integer)) { + /* + * Bogus or changed discontinuity marker. + * Need to skip this sample. + */ + DEBUGMSGTL(( "disman:event:delta", "discontinuity occurred: ")); + DEBUGMSGOID(("disman:event:delta", vp1->name, + vp1->name_length )); + DEBUGMSG(( "disman:event:delta", " \n" )); + vp2 = vp2->next_variable; + continue; + } + } + /* + * ... and check there is a previous sample to calculate + * the delta value against (regardless of whether the + * discontinuity marker was wildcarded or not). + */ + if (vp2->type == ASN_NULL) { + DEBUGMSGTL(( "disman:event:delta", "missing sample: ")); + DEBUGMSGOID(("disman:event:delta", vp1->name, + vp1->name_length )); + DEBUGMSG(( "disman:event:delta", " \n" )); + vp2 = vp2->next_variable; + continue; + } + value = (*vp1->val.integer - *vp2->val.integer); + DEBUGMSGTL(( "disman:event:delta", "delta sample: ")); + DEBUGMSGOID(("disman:event:delta", vp1->name, + vp1->name_length )); + DEBUGMSG(( "disman:event:delta", " (%ld - %ld) = %ld\n", + *vp1->val.integer, *vp2->val.integer, value)); + vp2 = vp2->next_variable; + } else { + value = *vp1->val.integer; + } + + /* + * ... evaluate the comparison ... + */ + switch (entry->mteTBoolComparison) { + case MTE_BOOL_UNEQUAL: + cmp = ( value != entry->mteTBoolValue ); + break; + case MTE_BOOL_EQUAL: + cmp = ( value == entry->mteTBoolValue ); + break; + case MTE_BOOL_LESS: + cmp = ( value < entry->mteTBoolValue ); + break; + case MTE_BOOL_LESSEQUAL: + cmp = ( value <= entry->mteTBoolValue ); + break; + case MTE_BOOL_GREATER: + cmp = ( value > entry->mteTBoolValue ); + break; + case MTE_BOOL_GREATEREQUAL: + cmp = ( value >= entry->mteTBoolValue ); + break; + } + DEBUGMSGTL(( "disman:event:delta", "Bool comparison: (%ld %s %ld) %d\n", + value, _ops[entry->mteTBoolComparison], + entry->mteTBoolValue, cmp)); + + /* + * ... and decide whether to trigger the event. + * (using the 'index' field of the varbind structure + * to remember whether the trigger has already fired) + */ + if ( cmp ) { + if (vp1->index & MTE_ARMED_BOOLEAN ) { + vp1->index &= ~MTE_ARMED_BOOLEAN; + /* + * NB: Clear the trigger armed flag even if the + * (starting) event dosn't actually fire. + * Otherwise initially true (but suppressed) + * triggers will fire on the *second* probe. + */ + if ( entry->old_results || + (entry->flags & MTE_TRIGGER_FLAG_BSTART)) { + DEBUGMSGTL(( "disman:event:trigger:fire", + "Firing boolean test: ")); + DEBUGMSGOID(("disman:event:trigger:fire", + vp1->name, vp1->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", "%s\n", + (entry->old_results ? "" : " (startup)"))); + entry->mteTriggerXOwner = entry->mteTBoolObjOwner; + entry->mteTriggerXObjects = entry->mteTBoolObjects; + /* + * XXX - when firing a delta-based trigger, should + * 'mteHotValue' report the actual value sampled + * (as here), or the delta that triggered the event ? + */ + entry->mteTriggerFired = vp1; + n = entry->mteTriggerValueID_len; + mteEvent_fire(entry->mteTBoolEvOwner, entry->mteTBoolEvent, + entry, vp1->name+n, vp1->name_length-n); + } + } + } else { + vp1->index |= MTE_ARMED_BOOLEAN; + } + } + } + + + /* + * Only run the basic threshold tests if there's an event to + * be triggered. (Either rising or falling will do) + */ + if (( entry->mteTriggerTest & MTE_TRIGGER_THRESHOLD ) && + ((entry->mteTThRiseEvent[0] != '\0' ) || + (entry->mteTThFallEvent[0] != '\0' ))) { + + /* + * The same delta-sample validation from Boolean + * tests also applies here too. + */ + if (entry->flags & MTE_TRIGGER_FLAG_DELTA) { + vp2 = entry->old_results; + if (entry->flags & MTE_TRIGGER_FLAG_DWILD) { + dv1 = dvar; + dv2 = entry->old_deltaDs; + } + } + for ( vp1 = var; vp1; vp1=vp1->next_variable ) { + /* + * Determine the value to be monitored... + */ + if ( !vp1->val.integer ) { /* No value */ + if ( vp2 ) + vp2 = vp2->next_variable; + continue; + } + if (entry->flags & MTE_TRIGGER_FLAG_DELTA) { + if (entry->flags & MTE_TRIGGER_FLAG_DWILD) { + /* + * We've already checked any non-wildcarded + * discontinuity markers (inc. sysUpTime.0). + * Validate this particular sample against + * the relevant wildcarded marker... + */ + if ((dv1->type == ASN_NULL) || + (dv1->type != dv2->type) || + (*dv1->val.integer != *dv2->val.integer)) { + /* + * Bogus or changed discontinuity marker. + * Need to skip this sample. + */ + vp2 = vp2->next_variable; + continue; + } + } + /* + * ... and check there is a previous sample to calculate + * the delta value against (regardless of whether the + * discontinuity marker was wildcarded or not). + */ + if (vp2->type == ASN_NULL) { + vp2 = vp2->next_variable; + continue; + } + value = (*vp1->val.integer - *vp2->val.integer); + vp2 = vp2->next_variable; + } else { + value = *vp1->val.integer; + } + + /* + * ... evaluate the single-value comparisons, + * and decide whether to trigger the event. + */ + cmp = vp1->index; /* working copy of 'armed' flags */ + if ( value >= entry->mteTThRiseValue ) { + if (cmp & MTE_ARMED_TH_RISE ) { + cmp &= ~MTE_ARMED_TH_RISE; + cmp |= MTE_ARMED_TH_FALL; + /* + * NB: Clear the trigger armed flag even if the + * (starting) event dosn't actually fire. + * Otherwise initially true (but suppressed) + * triggers will fire on the *second* probe. + * Similarly for falling thresholds (see below). + */ + if ( entry->old_results || + (entry->mteTThStartup & MTE_THRESH_START_RISE)) { + DEBUGMSGTL(( "disman:event:trigger:fire", + "Firing rising threshold test: ")); + DEBUGMSGOID(("disman:event:trigger:fire", + vp1->name, vp1->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", "%s\n", + (entry->old_results ? "" : " (startup)"))); + /* + * If no riseEvent is configured, we need still to + * set the armed flags appropriately, but there's + * no point in trying to fire the (missing) event. + */ + if (entry->mteTThRiseEvent[0] != '\0' ) { + entry->mteTriggerXOwner = entry->mteTThObjOwner; + entry->mteTriggerXObjects = entry->mteTThObjects; + entry->mteTriggerFired = vp1; + n = entry->mteTriggerValueID_len; + mteEvent_fire(entry->mteTThRiseOwner, + entry->mteTThRiseEvent, + entry, vp1->name+n, vp1->name_length-n); + } + } + } + } + + if ( value <= entry->mteTThFallValue ) { + if (cmp & MTE_ARMED_TH_FALL ) { + cmp &= ~MTE_ARMED_TH_FALL; + cmp |= MTE_ARMED_TH_RISE; + /* Clear the trigger armed flag (see above) */ + if ( entry->old_results || + (entry->mteTThStartup & MTE_THRESH_START_FALL)) { + DEBUGMSGTL(( "disman:event:trigger:fire", + "Firing falling threshold test: ")); + DEBUGMSGOID(("disman:event:trigger:fire", + vp1->name, vp1->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", "%s\n", + (entry->old_results ? "" : " (startup)"))); + /* + * Similarly, if no fallEvent is configured, + * there's no point in trying to fire it either. + */ + if (entry->mteTThRiseEvent[0] != '\0' ) { + entry->mteTriggerXOwner = entry->mteTThObjOwner; + entry->mteTriggerXObjects = entry->mteTThObjects; + entry->mteTriggerFired = vp1; + n = entry->mteTriggerValueID_len; + mteEvent_fire(entry->mteTThFallOwner, + entry->mteTThFallEvent, + entry, vp1->name+n, vp1->name_length-n); + } + } + } + } + vp1->index = cmp; + } + } + + /* + * The same processing also works for delta-threshold tests (if configured) + */ + if (( entry->mteTriggerTest & MTE_TRIGGER_THRESHOLD ) && + ((entry->mteTThDRiseEvent[0] != '\0' ) || + (entry->mteTThDFallEvent[0] != '\0' ))) { + + /* + * Delta-threshold tests can only be used with + * absolute valued samples. + */ + vp2 = entry->old_results; + if (entry->flags & MTE_TRIGGER_FLAG_DELTA) { + DEBUGMSGTL(( "disman:event:trigger", + "Delta-threshold on delta-sample\n")); + } else if ( vp2 != NULL ) { + for ( vp1 = var; vp1; vp1=vp1->next_variable ) { + /* + * Determine the value to be monitored... + * (similar to previous delta-sample processing, + * but without the discontinuity marker checks) + */ + if (!vp2) { + break; /* Run out of 'old' values */ + } + if (( !vp1->val.integer ) || + (vp2->type == ASN_NULL)) { + vp2 = vp2->next_variable; + continue; + } + value = (*vp1->val.integer - *vp2->val.integer); + vp2 = vp2->next_variable; + + /* + * ... evaluate the single-value comparisons, + * and decide whether to trigger the event. + */ + cmp = vp1->index; /* working copy of 'armed' flags */ + if ( value >= entry->mteTThDRiseValue ) { + if (vp1->index & MTE_ARMED_TH_DRISE ) { + DEBUGMSGTL(( "disman:event:trigger:fire", + "Firing rising delta threshold test: ")); + DEBUGMSGOID(("disman:event:trigger:fire", + vp1->name, vp1->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", "\n")); + cmp &= ~MTE_ARMED_TH_DRISE; + cmp |= MTE_ARMED_TH_DFALL; + /* + * If no riseEvent is configured, we need still to + * set the armed flags appropriately, but there's + * no point in trying to fire the (missing) event. + */ + if (entry->mteTThDRiseEvent[0] != '\0' ) { + entry->mteTriggerXOwner = entry->mteTThObjOwner; + entry->mteTriggerXObjects = entry->mteTThObjects; + entry->mteTriggerFired = vp1; + n = entry->mteTriggerValueID_len; + mteEvent_fire(entry->mteTThDRiseOwner, + entry->mteTThDRiseEvent, + entry, vp1->name+n, vp1->name_length-n); + } + } + } + + if ( value <= entry->mteTThDFallValue ) { + if (vp1->index & MTE_ARMED_TH_DFALL ) { + DEBUGMSGTL(( "disman:event:trigger:fire", + "Firing falling delta threshold test: ")); + DEBUGMSGOID(("disman:event:trigger:fire", + vp1->name, vp1->name_length)); + DEBUGMSG(( "disman:event:trigger:fire", "\n")); + cmp &= ~MTE_ARMED_TH_DFALL; + cmp |= MTE_ARMED_TH_DRISE; + /* + * Similarly, if no fallEvent is configured, + * there's no point in trying to fire it either. + */ + if (entry->mteTThDRiseEvent[0] != '\0' ) { + entry->mteTriggerXOwner = entry->mteTThObjOwner; + entry->mteTriggerXObjects = entry->mteTThObjects; + entry->mteTriggerFired = vp1; + n = entry->mteTriggerValueID_len; + mteEvent_fire(entry->mteTThDFallOwner, + entry->mteTThDFallEvent, + entry, vp1->name+n, vp1->name_length-n); + } + } + } + vp1->index = cmp; + } + } + } + + /* + * Finally, rotate the results - ready for the next run. + */ + snmp_free_varbind( entry->old_results ); + entry->old_results = var; + if ( entry->flags & MTE_TRIGGER_FLAG_DELTA ) { + snmp_free_varbind( entry->old_deltaDs ); + entry->old_deltaDs = dvar; + entry->sysUpTime = *sysUT_var.val.integer; + } +} + +void +mteTrigger_enable( struct mteTrigger *entry ) +{ + if (!entry) + return; + + if (entry->alarm) { + /* XXX - or explicitly call mteTrigger_disable ?? */ + snmp_alarm_unregister( entry->alarm ); + entry->alarm = 0; + } + + if (entry->mteTriggerFrequency) { + /* + * register once to run ASAP, and another to run + * at the trigger frequency + */ + snmp_alarm_register(0, 0, mteTrigger_run, entry ); + entry->alarm = snmp_alarm_register( + entry->mteTriggerFrequency, SA_REPEAT, + mteTrigger_run, entry ); + } +} + +void +mteTrigger_disable( struct mteTrigger *entry ) +{ + if (!entry) + return; + + if (entry->alarm) { + snmp_alarm_unregister( entry->alarm ); + entry->alarm = 0; + /* XXX - perhaps release any previous results */ + } +} + +long _mteTrigger_MaxCount = 0; +long _mteTrigger_countEntries(void) +{ + struct mteTrigger *entry; + netsnmp_tdata_row *row; + long count = 0; + + for (row = netsnmp_tdata_row_first(trigger_table_data); + row; + row = netsnmp_tdata_row_next(trigger_table_data, row)) { + entry = (struct mteTrigger *)row->data; + count += entry->count; + } + + return count; +} + +long mteTrigger_getNumEntries(int max) +{ + long count; + /* XXX - implement some form of caching ??? */ + count = _mteTrigger_countEntries(); + if ( count > _mteTrigger_MaxCount ) + _mteTrigger_MaxCount = count; + + return ( max ? _mteTrigger_MaxCount : count); +} |