blob: ffe4b69f698e1ae257317b6cfd8856b2bc23c903 [file] [log] [blame]
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -05001# Copyright 2017-present Adtran, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import structlog
16from twisted.internet import reactor
Yongjie Zhang8f891ad2019-07-03 15:32:38 -040017from twisted.internet.defer import inlineCallbacks
William Kurkian8235c1e2019-03-05 12:58:28 -050018from voltha_protos.common_pb2 import OperStatus, ConnectStatus
Matt Jeanneret72f96fc2019-02-11 10:53:05 -050019from pyvoltha.adapters.extensions.omci.omci_me import OntGFrame
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -050020
21
22class HeartBeat(object):
23 """Wraps health-check support for ONU"""
24 INITIAL_DELAY = 60 # Delay after start until first check
25 TICK_DELAY = 2 # Heartbeat interval
26
27 def __init__(self, handler, device_id):
28 self.log = structlog.get_logger(device_id=device_id)
29 self._enabled = False
30 self._handler = handler
31 self._device_id = device_id
32 self._defer = None
33 self._alarm_active = False
34 self._heartbeat_count = 0
35 self._heartbeat_miss = 0
36 self._alarms_raised_count = 0
37 self.heartbeat_failed_limit = 5
38 self.heartbeat_last_reason = ''
39 self.heartbeat_interval = self.TICK_DELAY
40
41 def __str__(self):
42 return "HeartBeat: count:{}, miss: {}".format(self._heartbeat_count,
43 self._heartbeat_miss)
44
45 @staticmethod
46 def create(handler, device_id):
47 return HeartBeat(handler, device_id)
48
49 def _start(self, delay=INITIAL_DELAY):
50 self._defer = reactor.callLater(delay, self.check_pulse)
51
52 def _stop(self):
Matt Jeanneret18949ae2019-02-09 15:00:14 -050053 d, self._defer = self._defer, None
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -050054 if d is not None and not d.called():
55 d.cancel()
56
57 @property
58 def enabled(self):
59 return self._enabled
60
61 @enabled.setter
62 def enabled(self, value):
63 if self._enabled != value:
64 self._enabled = value
65
66 # if value:
67 # self._start()
68 # else:
69 # self._stop()
70
71 @property
72 def check_item(self):
73 return 'vendor_id'
74
75 @property
76 def check_value(self):
William Kurkian3a206332019-04-29 11:05:47 -040077 # device = self._handler.core_proxy.get_device(self._device_id)
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -050078 # return device.serial_number
79 return 'ADTN'
80
81 @property
82 def alarm_active(self):
83 return self._alarm_active
84
85 @property
86 def heartbeat_count(self):
87 return self._heartbeat_count
88
89 @property
90 def heartbeat_miss(self):
91 return self._heartbeat_miss
92
93 @property
94 def alarms_raised_count(self):
95 return self._alarms_raised_count
96
97 def check_pulse(self):
98 if self.enabled:
99 try:
100 self._defer = self._handler.openomci.omci_cc.send(OntGFrame(self.check_item).get())
101 self._defer.addCallbacks(self._heartbeat_success, self._heartbeat_fail)
102
103 except Exception as e:
104 self._defer = reactor.callLater(5, self._heartbeat_fail, e)
105
106 def _heartbeat_success(self, results):
107 self.log.debug('heartbeat-success')
108
109 try:
110 omci_response = results.getfieldval("omci_message")
111 data = omci_response.getfieldval("data")
112 value = data[self.check_item]
113
114 if value != self.check_value:
115 self._heartbeat_miss = self.heartbeat_failed_limit
116 self.heartbeat_last_reason = "Invalid {}, got '{}' but expected '{}'".\
117 format(self.check_item, value, self.check_value)
118 else:
119 self._heartbeat_miss = 0
120 self.heartbeat_last_reason = ''
121
122 except Exception as e:
123 self._heartbeat_miss = self.heartbeat_failed_limit
124 self.heartbeat_last_reason = e.message
125
126 self.heartbeat_check_status(results)
127
128 def _heartbeat_fail(self, failure):
129 self._heartbeat_miss += 1
130 self.log.info('heartbeat-miss', failure=failure,
131 count=self._heartbeat_count,
132 miss=self._heartbeat_miss)
133 self.heartbeat_last_reason = 'OMCI connectivity error'
134 self.heartbeat_check_status(None)
135
136 def on_heartbeat_alarm(self, active):
137 # TODO: Do something here ?
138 #
139 # TODO: If failed (active = true) due to bad serial-number shut off the UNI port?
140 pass
141
Yongjie Zhang8f891ad2019-07-03 15:32:38 -0400142 @inlineCallbacks
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500143 def heartbeat_check_status(self, results):
144 """
145 Check the number of heartbeat failures against the limit and emit an alarm if needed
146 """
William Kurkian3a206332019-04-29 11:05:47 -0400147 device = self._handler.core_proxy.get_device(self._device_id)
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500148
149 try:
Matt Jeanneret72f96fc2019-02-11 10:53:05 -0500150 from pyvoltha.adapters.extensions.alarms.heartbeat_alarm import HeartbeatAlarm
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500151
152 if self._heartbeat_miss >= self.heartbeat_failed_limit:
153 if device.connect_status == ConnectStatus.REACHABLE:
154 self.log.warning('heartbeat-failed', count=self._heartbeat_miss)
155 device.connect_status = ConnectStatus.UNREACHABLE
156 device.oper_status = OperStatus.FAILED
157 device.reason = self.heartbeat_last_reason
Yongjie Zhang8f891ad2019-07-03 15:32:38 -0400158 yield self._handler.core_proxy.device_update(device)
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500159 HeartbeatAlarm(self._handler.alarms, 'onu', self._heartbeat_miss).raise_alarm()
160 self._alarm_active = True
161 self.on_heartbeat_alarm(True)
162 else:
163 # Update device states
164 if device.connect_status != ConnectStatus.REACHABLE and self._alarm_active:
165 device.connect_status = ConnectStatus.REACHABLE
166 device.oper_status = OperStatus.ACTIVE
167 device.reason = ''
Yongjie Zhang8f891ad2019-07-03 15:32:38 -0400168 yield self._handler.core_proxy.device_update(device)
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500169 HeartbeatAlarm(self._handler.alarms, 'onu').clear_alarm()
170
171 self._alarm_active = False
172 self._alarms_raised_count += 1
173 self.on_heartbeat_alarm(False)
174
175 except Exception as e:
176 self.log.exception('heartbeat-check', e=e)
177
178 # Reschedule next heartbeat
179 if self.enabled:
180 self._heartbeat_count += 1
181 self._defer = reactor.callLater(self.heartbeat_interval, self.check_pulse)