blob: b4f3865fbbdce825fbd6b2322f8f0fc16753f7dd [file] [log] [blame]
Stephane Barbarie980a0912017-05-11 11:27:06 -04001#
2# Copyright 2017 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Singleton agent responsible for managing alarm filters
19"""
20import structlog
21from twisted.internet.defer import inlineCallbacks
22
23from voltha.core.config.config_proxy import CallbackType
24from voltha.protos.voltha_pb2 import \
25 AlarmFilter, AlarmFilterRuleKey
26
27
28class AlarmFilterAgent(object):
29 __instance = None
30
31 def __new__(cls, core):
32 if cls.__instance == None:
33 cls.__instance = object.__new__(cls, core)
34 return cls.__instance
35
36 def __init__(self, core):
37 self.core = core
38 self.root_proxy = self.core.get_proxy('/')
39 self.self_proxies = {}
40
41 self.log = structlog.get_logger()
42
Stephane Barbarie980a0912017-05-11 11:27:06 -040043 def add_filter(self, alarm_filter):
44 self.log.debug('starting')
45
46 self.self_proxies[alarm_filter.id] = self.core.get_proxy(
47 '/alarm_filters/{}'.format(alarm_filter.id))
48
49 self.self_proxies[alarm_filter.id].register_callback(
50 CallbackType.PRE_UPDATE, self._review_filter)
51 self.self_proxies[alarm_filter.id].register_callback(
52 CallbackType.POST_UPDATE, self._process_filter)
53
Stephane Barbarie88d29b92017-06-21 16:43:37 -040054 self._process_filter(alarm_filter)
Stephane Barbarie980a0912017-05-11 11:27:06 -040055
56 self.log.info('started')
57
Stephane Barbarie88d29b92017-06-21 16:43:37 -040058 return self
59
Stephane Barbarie980a0912017-05-11 11:27:06 -040060 def remove_filter(self, alarm_filter):
61 self.log.debug('stopping')
62
Stephane Barbarie88d29b92017-06-21 16:43:37 -040063 self._review_filter(alarm_filter, teardown=True)
Stephane Barbarie980a0912017-05-11 11:27:06 -040064
65 self.self_proxies[alarm_filter.id].unregister_callback(
66 CallbackType.PRE_UPDATE, self._review_filter)
67 self.self_proxies[alarm_filter.id].unregister_callback(
68 CallbackType.POST_UPDATE, self._process_filter)
69
70 del self.self_proxies[alarm_filter.id]
71
72 self.log.info('stopped', alarm_filter=alarm_filter.id)
73
74 # Remove filters referencing the specified device
75 def remove_device_filters(self, device):
76 self.log.debug('cleaning')
77 all_filters = self.root_proxy.get('/alarm_filters')
78 for filter in all_filters:
79 for r in filter.rules:
80 if r.key == AlarmFilterRuleKey.device_id and r.value == device.id:
81 self.root_proxy.remove('/alarm_filters/{}'.format(filter.id))
82 break
83
84 self.log.debug('cleaned')
85
Stephane Barbarie88d29b92017-06-21 16:43:37 -040086 def _review_filter(self, alarm_filter=None, teardown=False):
Stephane Barbarie980a0912017-05-11 11:27:06 -040087 # UPDATE scenario:
88 #
89 # When a filter is updated, we need to review the content of the previous filter
Stephane Barbarie88d29b92017-06-21 16:43:37 -040090 # to ensure that nothing is left un-managed and not cleaned up. If the filter
91 # actually changed, it will be re-applied (including suppression)
Stephane Barbarie980a0912017-05-11 11:27:06 -040092 #
93 # TEAR DOWN scenario:
94 # When a filter is deleted, we need to go through the rules
95 # and revert anything that was configured when the filter was first created.
96
97 if alarm_filter is not None:
98 current_filter = self.self_proxies[alarm_filter.id].get()
99
Stephane Barbarie88d29b92017-06-21 16:43:37 -0400100 # Find any rules contained in the filter that might have changed
101 rules_to_clean = [r for r in current_filter.rules if r not in alarm_filter.rules]
Stephane Barbarie980a0912017-05-11 11:27:06 -0400102
Stephane Barbarie88d29b92017-06-21 16:43:37 -0400103 if not rules_to_clean and not teardown:
Stephane Barbarie980a0912017-05-11 11:27:06 -0400104 # There are no rules from the current filter that require a clean up
105 return
106
107 # Un-suppress devices that are no longer referenced
108 self._manage_suppression(current_filter)
109
110 def _process_filter(self, alarm_filter):
111 assert isinstance(alarm_filter, AlarmFilter)
112 self._manage_suppression(alarm_filter, True)
113
114 @inlineCallbacks
115 def _manage_suppression(self, alarm_filter, suppress=False):
116 # Pull out all filters
117 all_filters = self.root_proxy.get('/alarm_filters')
118
119 # Build a list of all devices that have filters against them
120 all_device_ids = set()
121 for filter in all_filters:
122 if filter.id != alarm_filter.id:
123 ids = [r.value for r in filter.rules if r.key == AlarmFilterRuleKey.device_id]
124 all_device_ids.update(ids)
125
126 for rule in alarm_filter.rules:
127 # Check if it's a device_id rule and that it wasn't already processed by another filter
128 if rule.key == AlarmFilterRuleKey.device_id and rule.value not in all_device_ids:
129 # A suppression call will occur only for device ids that exist on the system
130 assert rule.value in self.core.device_agents
131
132 if suppress:
133 yield self.core.device_agents[rule.value].suppress_alarm(alarm_filter)
134 else:
135 yield self.core.device_agents[rule.value].unsuppress_alarm(alarm_filter)