Skip to content

Commit

Permalink
Merge pull request snabbco#941 from dpino/alarm-purge-alarms
Browse files Browse the repository at this point in the history
Implement purge alarms
  • Loading branch information
dpino authored Sep 14, 2017
2 parents 33d451e + 27874ea commit 57dab91
Show file tree
Hide file tree
Showing 8 changed files with 339 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/apps/config/leader.lua
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,18 @@ function Leader:rpc_set_alarm_operator_state (args)
if success then return response else return {status=1, error=response} end
end

function Leader:rpc_purge_alarms (args)
local function purge()
if args.schema ~= self.schema_name then
return false, ("Purge-alarms operation not supported in"..
"'%s' schema"):format(args.schema)
end
return { purged_alarms = alarms.purge_alarms(args) }
end
local success, response = pcall(purge)
if success then return response else return {status=1, error=response} end
end

local function path_parser_for_grammar(grammar, path)
local getter, subgrammar = path_mod.resolver(grammar, path)
return data.data_parser_from_grammar(subgrammar)
Expand Down
179 changes: 179 additions & 0 deletions src/lib/yang/alarms.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ local util = require('lib.yang.util')
local alarm_codec = require('apps.config.alarm_codec')

local format_date_as_iso_8601 = util.format_date_as_iso_8601
local parse_date_as_iso_8601 = util.parse_date_as_iso_8601

local state = {
alarm_inventory = {
Expand Down Expand Up @@ -274,6 +275,141 @@ function set_operator_state (key, args)
return true
end

-- Purge alarms.

local ages = {seconds=1, minutes=60, hours=3600, days=3600*24, weeks=3600*24*7}

local function toseconds (date)
if type(date) == 'table' then
assert(date.age_spec and date.value, "Not a valid 'older_than' data type")

local multiplier = assert(ages[date.age_spec],
"Not a valid 'age_spec' value: "..date.age_spec)
return date.value * multiplier
elseif type(date) == 'string' then
local t = {}
t.year, t.month, t.day, t.hour, t.min, t.sec = parse_date_as_iso_8601(date)
return os.time(t)
else
error('Wrong data type: '..type(date))
end
end

-- `purge_alarms` requests the server to delete entries from the alarm list
-- according to the supplied criteria. Typically it can be used to delete
-- alarms that are in closed operator state and older than a specified time.
-- The number of purged alarms is returned as an output parameter.
--
-- args: {status, older_than, severity, operator_state}
function purge_alarms (args)
local alarm_list = state.alarm_list
local alarms = state.alarm_list.alarm
args.alarm_status = args.alarm_status or 'any'
local function purge_alarm (key)
alarms[key] = nil
alarm_list.number_of_alarms = alarm_list.number_of_alarms - 1
end
local function by_status (alarm, args)
local status = assert(args.alarm_status)
local alarm_statuses = lib.set('any', 'cleared', 'not-cleared')
assert(alarm_statuses[status], 'Not a valid status value: '..status)
if status == 'any' then return true end
if status == 'cleared' then return alarm.is_cleared end
if status == 'not-cleared' then return not alarm.is_cleared end
return false
end
local function by_older_than (alarm, args)
local older_than = assert(args.older_than)
if type(older_than) == 'string' then
local age_spec, value = older_than:match("([%w]+):([%d]+)")
older_than = {value = value, age_spec = age_spec}
end
assert(type(older_than) == 'table')
local alarm_time = toseconds(alarm.time_created)
local threshold = toseconds(older_than)
return util.gmtime() - alarm_time >= threshold
end
local function by_severity (alarm, args)
local severity = assert(args.severity)
if type(severity) == 'string' then
local sev_spec, value = severity:match("([%w]+):([%w]+)")
severity = {sev_spec = sev_spec, value = value}
end
assert(type(severity) == 'table' and severity.sev_spec and severity.value,
'Not valid severity data type')
local severities = {indeterminate=2, minor=3 , warning=4, major=5, critical=6}
local function tonumber (severity)
return severities[severity]
end
local sev_spec, severity = severity.sev_spec, tonumber(severity.value)
local alarm_severity = tonumber(alarm.perceived_severity)
if sev_spec == 'below' then
return alarm_severity < severity
elseif sev_spec == 'is' then
return alarm_severity == severity
elseif sev_spec == 'above' then
return alarm_severity > severity
else
error('Not valid sev-spec value: '..sev_spec)
end
return false
end
local function by_operator_state (alarm, args)
local operator_state = assert(args.operator_state_filter)
local state, user
if type(operator_state) == 'string' then
state, user = operator_state:match("([%w]+):([%w]+)")
if not state then
state, user = operator_state, operator_state
end
operator_state = {state=state, user=user}
end
assert(type(operator_state) == 'table')
local function tonumber (state)
return operator_states[state]
end
state, user = operator_state.state, operator_state.user
if state or user then
for _, state_change in pairs(alarm.operator_state_change or {}) do
if state and tonumber(state_change.state) == tonumber(state) then
return true
elseif user and state_change.user == user then
return true
end
end
end
return false
end
local args_to_filters = { older_than=by_older_than,
severity = by_severity,
operator_state_filter = by_operator_state, }
local filter = {}
function filter:initialize (args)
self.args = args
self.filters = { by_status }
for name, filter in pairs(args_to_filters) do
if args[name] then
table.insert(self.filters, filter)
end
end
end
function filter:apply (alarm)
for _, filter in ipairs(self.filters) do
if not filter(alarm, self.args) then return false end
end
return true
end
local count = 0
filter:initialize(args)
for key, alarm in pairs(alarms) do
if filter:apply(alarm) then
purge_alarm(key)
count = count + 1
end
end
return count
end

--

function selftest ()
Expand All @@ -292,6 +428,7 @@ function selftest ()

-- Check alarm inventory has been loaded.
require("apps.ipv4.arp")
require("apps.lwaftr.ndp")
assert(table_size(state.alarm_inventory.alarm_type) > 0)

-- Check number of alarms is zero.
Expand Down Expand Up @@ -376,5 +513,47 @@ function selftest ()
local success = pcall(set_operator_state, key, {state='ack'})
assert(not success)

-- Test toseconds.
assert(toseconds({age_spec='weeks', value=1}) == 3600*24*7)
assert(toseconds('1970-01-01T00:00:00Z') == 0)

-- Purge alarms by status.
assert(table_size(state.alarm_list.alarm) == 1)
assert(purge_alarms({alarm_status = 'any'}) == 1)
assert(table_size(state.alarm_list.alarm) == 0)
assert(purge_alarms({alarm_status = 'any'}) == 0)

-- Purge alarms filtering by older_than.
local key = alarm_keys:fetch('external-interface', 'arp-resolution')
raise_alarm(key)
sleep(1)
assert(purge_alarms({older_than={age_spec='seconds', value='1'}}) == 1)

-- Purge alarms by severity.
local key = alarm_keys:fetch('external-interface', 'arp-resolution')
raise_alarm(key)
assert(table_size(state.alarm_list.alarm) == 1)
assert(purge_alarms({severity={sev_spec='is', value='minor'}}) == 0)
assert(purge_alarms({severity={sev_spec='below', value='minor'}}) == 0)
assert(purge_alarms({severity={sev_spec='above', value='minor'}}) == 1)

raise_alarm(key, {perceived_severity='minor'})
assert(purge_alarms({severity={sev_spec='is', value='minor'}}) == 1)

raise_alarm(alarm_keys:fetch('external-interface', 'arp-resolution'))
raise_alarm(alarm_keys:fetch('internal-interface', 'ndp-resolution'))
assert(table_size(state.alarm_list.alarm) == 2)
assert(purge_alarms({severity={sev_spec='above', value='minor'}}) == 2)

-- Purge alarms by operator_state_filter.
local key = alarm_keys:fetch('external-interface', 'arp-resolution')
raise_alarm(key)
assert(table_size(state.alarm_list.alarm) == 1)
local success = set_operator_state(key, {state='ack'})
assert(success)
local alarm = assert(state.alarm_list.alarm[key])
assert(table_size(alarm.operator_state_change) == 1)
assert(purge_alarms({operator_state_filter={state='ack'}}) == 1)

print("ok")
end
47 changes: 47 additions & 0 deletions src/lib/yang/snabb-config-leader-v1.yang
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,51 @@ module snabb-config-leader-v1 {
leaf success { type boolean; description "True if operation succeeded."; }
}
}

grouping filter-input {
description
"Grouping to specify a filter construct on alarm information.";
leaf alarm-status {
type string;
mandatory true;
description
"The clearance status of the alarm.";
}
leaf older-than {
type string;
description "Matches the 'last-status-change' leaf in the alarm.";
}
leaf severity {
type string;
description "Filter based on severity.";
}
leaf operator-state-filter {
type string;
description "Filter based on operator state.";
}
}

rpc purge-alarms {
description
"This operation requests the server to delete entries from the
alarm list according to the supplied criteria. Typically it
can be used to delete alarms that are in closed operator state
and older than a specified time. The number of purged alarms
is returned as an output parameter";
input {
leaf schema { type string; mandatory true; }
leaf revision { type string; }
leaf print-default { type boolean; }
leaf format { type string; }
uses filter-input;
}
output {
uses error-reporting;
leaf purged-alarms {
type uint32;
description "Number of purged alarms.";
}
}
}

}
10 changes: 9 additions & 1 deletion src/lib/yang/util.lua
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ function memoize(f, max_occupancy)
end
end

local function gmtime ()
function gmtime ()
local now = os.time()
local utcdate = os.date("!*t", now)
local localdate = os.date("*t", now)
Expand All @@ -143,6 +143,14 @@ function format_date_as_iso_8601 (time)
return os.date("%Y-%m-%dT%H:%M:%SZ", time)
end

-- XXX: ISO 8601 can be more complex. We asumme date is the format returned
-- by 'format_date_as_iso8601'.
function parse_date_as_iso_8601 (date)
assert(type(date) == 'string')
local pattern = "(%d%d%d%d)-(%d%d)-(%d%d)T(%d%d):(%d%d):(%d%d)Z"
return assert(date:match(pattern))
end

function selftest()
print('selftest: lib.yang.util')
assert(tointeger('0') == 0)
Expand Down
3 changes: 3 additions & 0 deletions src/program/config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ include:
* [`snabb config set-alarm-operator-state`](./set_alarm_operator_state/README):
add a new operator-state to an alarm

* [`snabb config purge-alarms`](./purge_alarms/README):
purge alarms by several criteria

The `snabb config get` et al commands are the normal way that Snabb
users interact with Snabb applications in an ad-hoc fashion via the
command line. `snabb config listen` is the standard way that a NETCONF
Expand Down
32 changes: 32 additions & 0 deletions src/program/config/purge_alarms/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
Usage: snabb config purge-alarms [OPTION]... ID STATE
Adds a new operator-state in an alarm.

Available options:
-s, --schema SCHEMA YANG data interface to request.
-r, --revision REVISION Require a specific revision of the YANG module.
-f, --format Selects output format (yang or xpath). Default: yang.
--print-default Forces print out of default values.
-h, --help Displays this message.

Filtering options:
--by-severity [severity-spec:value].
Severity spec: 'is', 'below', 'above'.
Value: 'indeterminate', 'minor', 'warning', 'major', 'critical'.
--by-older-than [age-spec:value].
Age spec: 'seconds', 'minutes', 'hours', 'days', 'weeks'.
Value: integer.
--by-operator-state-filter [state:user].
State (optional): 'none', 'ack', 'closed', 'shelved', 'un-shelved'.
User (optional): string.

Given an instance identifier and an alarm state ('any', 'cleared' or 'not-cleared')
remove all alarms that match the filtering options.

Typical usage:

$ sudo ./snabb config purge-alarms lwaftr any
$ sudo ./snabb config purge-alarms --by-severity above:minor lwaftr any
$ sudo ./snabb config purge-alarms --by-older-than minutes:5 lwaftr cleared

See https://github.com/Igalia/snabb/blob/lwaftr/src/program/config/README.md
for full documentation.
1 change: 1 addition & 0 deletions src/program/config/purge_alarms/README.inc
Loading

0 comments on commit 57dab91

Please sign in to comment.