Manage the maintenance state of alarms

🟢 Beginner

alarm dashboard

In this example, we will present a way to manage the maintenance state of alarms.

  • Configure a manual enable/disable action with parameters for device, node or site scope.

  • Add an automatic disable action that scans expired maintenances and resets severity and journal.

  • Create a pre-insert hook to check active maintenance when a new alarm arrives.

  • Expose maintenance controls via a right-click menu on the alarm table.

git checkout origin/osp-alarms-configuration .
git checkout origin/osp-web-configuration .
git checkout origin/osp-scripts-configuration .
git checkout origin/osp-variables-configuration .
git checkout origin/example-alarms-maintenance-management .

Prerequisite

Steps

Note

To simplify scripting and share helper functions, all the logic is inside the root/maintenance/maintenance.lua. The following code snippet does not detail the contents of the helper function.

1. Create the configuration to enable/disable maintenance

This action is used for enabling/disabling the maintenance manually. It takes as parameter :

  • An actionType to define if the activation is for a device, node or site.

  • An optional location if the activation is for a site.

  • An optional node if the activation is for a node.

  • An optional serial if the activation is for a device.

The script determines which alarms need to be put in maintenance depending on those parameters.

root/maintenance/manual-enable-disable/action.alarms

{
    "moduleId": "modules.alarms.alarms-1",
    "scriptFile": "root/maintenance/maintenance.lua",
    "accessedValues": [],
    "beginBatch": "begin_batch",
    "criteria": "criteria",
    "execute": "execute",
    "endBatch": "end_batch"
}

root/maintenance/maintenance.lua

156---------------------------------------------------------------------
157-- Enable maintenance upon a user action.
158-- used by root.maintenance.manual-enable-disable
159function begin_batch()
160    batch_execution_data = {}
161end
162
163function criteria(data)
164    local actionType = data[ACTION_TYPE]
165    local location = data[ACTION_TYPE_SITE]
166    local node = data[ACTION_TYPE_NODE]
167    local serial = data[ACTION_TYPE_DEVICE]
168
169    if type(location) ~= "table" then
170        location = {location}
171    end
172
173    if type(node) ~= "table" then
174        node = {node}
175    end
176
177    if type(serial) ~= "table" then
178        serial = {serial}
179    end
180
181    if actionType == ACTION_TYPE_DEVICE and next(serial) ~= nil then
182        local filter = FILTER_BEGIN
183        local filter_middle = [[{'_id': '%s'},]]
184        for _, v in pairs(serial) do
185            filter = filter .. string.format(filter_middle, v)
186        end
187        return filter .. FILTER_END
188    elseif actionType == ACTION_TYPE_NODE and next(node) ~= nil and next(location) ~= nil then
189        local filter = FILTER_BEGIN
190        local filter_middle = [[
191            {
192                '$and': [
193                 {'additionalData.%s': '%s'},
194                    {'%s': '%s'},
195                    {'summary': 
196                        {
197                            '$ne': '%s'
198                        }
199                    }
200                ]
201            }
202        ]]
203
204        for i, n in pairs(node) do
205            filter = filter .. string.format(filter_middle, NODE_FIELD, n, SITE_FIELD, location[i], DEVICE_MAINTENANCE_SUMMARY)
206        end
207        return filter .. FILTER_END
208    elseif actionType == ACTION_TYPE_SITE and next(location) ~= nil then
209        local filter = FILTER_BEGIN
210        local filter_middle = [[
211            {
212                '$and': [
213                    {'%s': '%s'},
214                    {'summary': 
215                        {
216                            '$ne': '%s'
217                        }
218                    }
219                ]
220            }
221        ]]
222        for _, v in pairs(location) do
223            filter = filter .. string.format(filter_middle, SITE_FIELD, v, SITE_MAINTENANCE_SUMMARY)
224        end
225
226        return filter .. FILTER_END
227    else
228        return FILTER_EMPTY
229    end
230end
231
232function execute(alarm, data, actions)
233    local maintenanceUntil = data["until"]
234    if data["hours"] ~= nil then
235        local now = Timestamp.now()
236        now:plusHours(data["hours"])
237        maintenanceUntil = now:getValue()
238    end
239    local type = data[ACTION_TYPE]
240
241    if type == ACTION_TYPE_DEVICE then
242        store.set(MAINTENANCE_STORE_PREFIX .. "-" .. alarm.id, maintenanceUntil)
243    elseif type == ACTION_TYPE_NODE then
244        store.set(MAINTENANCE_STORE_PREFIX .. "-" .. alarm.location .. "-" .. alarm.additionalData[NODE_FIELD], maintenanceUntil)
245    elseif type == ACTION_TYPE_SITE then
246        store.set(MAINTENANCE_STORE_PREFIX .. "-" .. alarm.location, maintenanceUntil)
247    end
248
249    if alarm.tags[SITE_STATUS_TAG] ~= nil then
250        return
251    end
252
253    update_alarm(alarm, data, actions, maintenanceUntil)
254end
255
256function end_batch()
257    for k, v in pairs(batch_execution_data) do
258        if v[ACTION_TYPE] == ACTION_TYPE_NODE then
259            alarms:insert(
260                severity.get("root.alarms.severities.minor"),
261                DEVICE_MAINTENANCE_SUMMARY,
262                v[SITE_FIELD],
263                SCRIPT_NAME,
264                k,
265                {DEVICE_STATUS_TAG},
266                {node=v[NODE_FIELD], maintenance="In maintenance", monitored=true, region=v[REGION_FIELD], assigned=ASSIGNED_OCC, customer=v[ALARM_CUSTOMER]}
267        )
268        elseif v[ACTION_TYPE] == ACTION_TYPE_SITE then
269            alarms:insert(
270                severity.get("root.alarms.severities.minor"),
271                SITE_MAINTENANCE_SUMMARY,
272                v[SITE_FIELD],
273                SCRIPT_NAME,
274                k,
275                {SITE_STATUS_TAG},
276                {maintenance= "In maintenance", monitored=true, region=v[REGION_FIELD], assigned=ASSIGNED_OCC, customer=v[ALARM_CUSTOMER]}
277            )
278        end
279    end
280end

2. Create the configuration to disable the site/node maintenance automatically

This action scans the alarms in maintenance and if the maintenance has expired, performs the following actions:

  • Set the severity to clear

  • Set the maintenance field to Normal

  • Add a message in the journal

root/maintenance/auto-disable/action.alarms

{
    "moduleId": "modules.alarms.alarms-1",
    "period": {
        "unit": "SECONDS",
        "value": 5
    },
    "scriptFile": "root/maintenance/maintenance.lua",
    "criteria": "criteria_disable",
    "execute": "execute_disable"
}

root/maintenance/maintenance.lua

 77---------------------------------------------------------------------
 78-- Check regularly to disable maintenance mode when nessecary.
 79-- used by root.maintenance.disable
 80function criteria_disable(unused)
 81    local sev = 100 --Replace with the severity to use by default when the maintenance is disable.
 82    if severity.has("root.alarms.severities.clear") then
 83        sev = severity.get("root.alarms.severities.clear")
 84    end
 85
 86    local filter = [[
 87        {
 88            '$and': [
 89                {'additionalData.%s': {'$exists': true}},
 90                {
 91                    '$or': [
 92                        {'additionalData.%s': {'$ne': '%s'}},
 93                        {
 94                            '$and': [
 95                                {'summary': '%s'},
 96                                {'severity': {'$ne': %s}}
 97                            ]
 98                        },
 99                        {
100                            '$and': [
101                                {'summary': '%s'},
102                                {'severity': {'$ne': %s}}
103                            ]
104                        }
105                    ]
106                }
107            ]
108        }
109    ]]
110
111    return string.format(filter, MAINTENANCE_FIELD, MAINTENANCE_FIELD, NORMAL_MODE, SITE_MAINTENANCE_SUMMARY, sev, DEVICE_MAINTENANCE_SUMMARY, sev)
112end
113
114function execute_disable(alarm, unused, actions)
115    local now = Timestamp.now()
116    local sev = 100 --Replace with the severity to use by default when the maintenance is disable.
117    if severity.has("root.alarms.severities.clear") then
118        sev = severity.get("root.alarms.severities.clear")
119    end
120
121    if alarm.summary == SITE_MAINTENANCE_SUMMARY then
122        local locationMaintenance = nil
123        if alarm.location ~= nil then
124            locationMaintenance = getSiteMaintenance(alarm.location)
125        end
126
127        -- check if location maintenance is done
128        if locationMaintenance ~= nil and now:isNewerThan(Timestamp.from(locationMaintenance)) then
129            actions:escalate(sev)
130            actions:edit(nil, nil, nil, nil, nil, {maintenance = NORMAL_MODE})
131            actions:journal(MAINTENANCE_DISABLE_MESSAGE, SCRIPT_NAME)
132        end
133    elseif alarm.summary == DEVICE_MAINTENANCE_SUMMARY then
134        local deviceMaintenance = nil
135        if alarm.additionalData[NODE_FIELD] ~= nil and alarm.location ~= nil then
136            deviceMaintenance = getDeviceMaintenance(alarm.location, alarm.additionalData[NODE_FIELD])
137        end
138
139        -- check if device maintenance is done
140        if deviceMaintenance ~= nil and now:isNewerThan(Timestamp.from(deviceMaintenance)) then
141            actions:escalate(sev)
142            actions:edit(nil, nil, nil, nil, nil, {maintenance = NORMAL_MODE})
143            actions:journal(MAINTENANCE_DISABLE_MESSAGE, SCRIPT_NAME)
144        end
145    else
146        local maintenanceUntil = Timestamp.from(alarm.additionalData[MAINTENANCE_UNTIL_FIELD])
147
148        if now:isNewerThan(maintenanceUntil) then
149            actions:edit(nil, nil, nil, nil, nil, {maintenance = NORMAL_MODE})
150            actions:journal(MAINTENANCE_DISABLE_MESSAGE, SCRIPT_NAME)
151        end
152    end
153end

3. Create the configuration to process new alarms

When a new alarm arrives, we must check if the maintenance is enabled for it.

root/maintenance/insert/pre-insert.alarms

{
    "moduleId": "modules.alarms.alarms-1",
    "priority": 5,
    "scriptFile": "root/maintenance/maintenance.lua",
    "accessedValues": [],
    "for": "match_insertion",
    "thenExecute": "then_execute_insertion"
}

root/maintenance/maintenance.lua

52---------------------------------------------------------------------
53-- Check if a new alarm need to be set with the maintenance mode
54-- used by root.maintenance.insertion
55function match_insertion(alarm)
56    if alarm.tags[SITE_STATUS_TAG] ~= nil then
57        return false
58    end
59    return true
60end
61
62function then_execute_insertion(alarm, data, operations)
63    local olderMaintenance = getOlderMaintenanceUntil(alarm)
64    local now = Timestamp.now()
65    local newUntil = Timestamp.from(olderMaintenance)
66
67    local newAlarm = operations:forward()
68    newAlarm.additionalData = alarm.additionalData
69    if newUntil:isNewerThan(now) then
70        newAlarm.additionalData[MAINTENANCE_FIELD] = MAINTENANCE_MODE
71    else
72        newAlarm.additionalData[MAINTENANCE_FIELD] = NORMAL_MODE
73    end
74    newAlarm.additionalData[MAINTENANCE_UNTIL_FIELD] = olderMaintenance
75    newAlarm.hideUntil = olderMaintenance
76end

4. Create a menu to manage maintenance from an alarm table

The menu allows to enable/disable the maintenance with a right-click on an alarm.

root/maintenance/menu.web

{
  "moduleId": [
    "modules.web.web-1"
  ],
  "name": "Alarms maintenance menu",
  "description": "All maintenance operations for the alarms",
  "menus": [
    {
      "label": "Enable maintenance",
      "icon": "build",
      "context": [
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length === 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'enable'"
            },
            "data.type": {
              "expression": "'serial'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.serial": {
              "extract": "alarms.clicked._id"
            },
            "data.until": {
              "prompt": {
                "type": "DATE_TIME",
                "labels": {
                  "title": "Put in maintenance until",
                  "message": "Please select when the maintenance end",
                  "save": "Save",
                  "cancel": "Cancel"
                },
                "selector": {
                  "as": "timestamp"
                }
              }
            }
          }
        },
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length > 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'enable'"
            },
            "data.type": {
              "expression": "'serial'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.serial": {
              "extract": "alarms.selected[*]._id"
            },
            "data.until": {
              "prompt": {
                "type": "DATE_TIME",
                "labels": {
                  "title": "Put in maintenance until",
                  "message": "Please select when the maintenance end",
                  "save": "Save",
                  "cancel": "Cancel"
                },
                "selector": {
                  "as": "timestamp"
                }
              }
            }
          }
        }
      ]
    },
    {
      "label": "Disable maintenance",
      "icon": "build",
      "context": [
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length === 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'disable'"
            },
            "data.type": {
              "expression": "'serial'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.serial": {
              "extract": "alarms.clicked._id"
            },
            "data.until": {
              "expression": "0"
            }
          }
        },
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length > 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'disable'"
            },
            "data.type": {
              "expression": "'serial'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.serial": {
              "extract": "alarms.selected[*]._id"
            },
            "data.until": {
              "expression": "0"
            }
          }
        }
      ]
    },
    {
      "label": "Enable node maintenance",
      "icon": "build",
      "context": [
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length === 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'enable'"
            },
            "data.type": {
              "expression": "'node'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.site": {
              "extract": "alarms.clicked.location"
            },
            "data.node": {
              "extract": "alarms.clicked.additionalData.node"
            },
            "data.until": {
              "prompt": {
                "type": "DATE_TIME",
                "labels": {
                  "title": "Put in maintenance until",
                  "message": "Please select when the maintenance end",
                  "save": "Save",
                  "cancel": "Cancel"
                },
                "selector": {
                  "as": "timestamp"
                }
              }
            }
          }
        },
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length > 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'enable'"
            },
            "data.type": {
              "expression": "'node'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.site": {
              "extract": "alarms.selected[*].location"
            },
            "data.node": {
              "extract": "alarms.selected[*].additionalData.node"
            },
            "data.until": {
              "prompt": {
                "type": "DATE_TIME",
                "labels": {
                  "title": "Put in maintenance until",
                  "message": "Please select when the maintenance end",
                  "save": "Save",
                  "cancel": "Cancel"
                },
                "selector": {
                  "as": "timestamp"
                }
              }
            }
          }
        }
      ]
    },
    {
      "label": "Disable node maintenance",
      "icon": "build",
      "context": [
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length === 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'disable'"
            },
            "data.type": {
              "expression": "'node'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.site": {
              "extract": "alarms.clicked.location"
            },
            "data.node": {
              "extract": "alarms.clicked.additionalData.node"
            },
            "data.until": {
              "expression": "0"
            }
          }
        },
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length > 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'disable'"
            },
            "data.type": {
              "expression": "'node'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.site": {
              "extract": "alarms.selected[*].location"
            },
            "data.node": {
              "extract": "alarms.selected[*].additionalData.node"
            },
            "data.until": {
              "expression": "0"
            }
          }
        }
      ]
    },
    {
      "label": "Enable site maintenance",
      "icon": "build",
      "context": [
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length === 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'enable'"
            },
            "data.type": {
              "expression": "'site'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.site": {
              "extract": "alarms.clicked.location"
            },
            "data.until": {
              "prompt": {
                "type": "DATE_TIME",
                "labels": {
                  "title": "Put in maintenance until",
                  "message": "Please select when the maintenance end",
                  "save": "Save",
                  "cancel": "Cancel"
                },
                "selector": {
                  "as": "timestamp"
                }
              }
            }
          }
        },
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length > 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'enable'"
            },
            "data.type": {
              "expression": "'site'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.site": {
              "extract": "alarms.selected[*].location"
            },
            "data.until": {
              "prompt": {
                "type": "DATE_TIME",
                "labels": {
                  "title": "Put in maintenance until",
                  "message": "Please select when the maintenance end",
                  "save": "Save",
                  "cancel": "Cancel"
                },
                "selector": {
                  "as": "timestamp"
                }
              }
            }
          }
        }
      ]
    },
    {
      "label": "Disable site maintenance",
      "icon": "build",
      "context": [
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length === 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'disable'"
            },
            "data.type": {
              "expression": "'site'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.site": {
              "extract": "alarms.clicked.location"
            },
            "data.until": {
              "expression": "0"
            }
          }
        },
        {
          "type": "AlarmTable",
          "action": "root.alarms.actions.run_action_rule",
          "condition": "${alarms.selected}.length > 0",
          "input": {
            "actionRuleId": {
              "expression": "'root.maintenance.manual-enable-disable'"
            },
            "data.action": {
              "expression": "'disable'"
            },
            "data.type": {
              "expression": "'site'"
            },
            "data.user": {
              "expression": "${user.username}"
            },
            "data.site": {
              "extract": "alarms.selected[*].location"
            },
            "data.until": {
              "expression": "0"
            }
          }
        }
      ]
    }
  ]
}