blob: 386d0ab48498092438c26e234d789d745da70b0e [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
Matt Jeanneret2e3cb8d2019-11-16 09:22:41 -050015from __future__ import absolute_import
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -050016import structlog
17from twisted.internet import reactor
Yongjie Zhang8f891ad2019-07-03 15:32:38 -040018from twisted.internet.defer import inlineCallbacks
William Kurkian8235c1e2019-03-05 12:58:28 -050019from voltha_protos.common_pb2 import OperStatus, ConnectStatus
Matt Jeanneret72f96fc2019-02-11 10:53:05 -050020from pyvoltha.adapters.extensions.omci.omci_me import OntGFrame
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -050021
22
23class HeartBeat(object):
24 """Wraps health-check support for ONU"""
25 INITIAL_DELAY = 60 # Delay after start until first check
26 TICK_DELAY = 2 # Heartbeat interval
27
28 def __init__(self, handler, device_id):
29 self.log = structlog.get_logger(device_id=device_id)
30 self._enabled = False
31 self._handler = handler
32 self._device_id = device_id
33 self._defer = None
34 self._alarm_active = False
35 self._heartbeat_count = 0
36 self._heartbeat_miss = 0
37 self._alarms_raised_count = 0
38 self.heartbeat_failed_limit = 5
39 self.heartbeat_last_reason = ''
40 self.heartbeat_interval = self.TICK_DELAY
41
42 def __str__(self):
43 return "HeartBeat: count:{}, miss: {}".format(self._heartbeat_count,
44 self._heartbeat_miss)
45
46 @staticmethod
47 def create(handler, device_id):
48 return HeartBeat(handler, device_id)
49
50 def _start(self, delay=INITIAL_DELAY):
51 self._defer = reactor.callLater(delay, self.check_pulse)
52
53 def _stop(self):
Matt Jeanneret18949ae2019-02-09 15:00:14 -050054 d, self._defer = self._defer, None
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -050055 if d is not None and not d.called():
56 d.cancel()
57
58 @property
59 def enabled(self):
60 return self._enabled
61
62 @enabled.setter
63 def enabled(self, value):
64 if self._enabled != value:
65 self._enabled = value
66
67 # if value:
68 # self._start()
69 # else:
70 # self._stop()
71
72 @property
73 def check_item(self):
74 return 'vendor_id'
75
76 @property
77 def check_value(self):
William Kurkian3a206332019-04-29 11:05:47 -040078 # device = self._handler.core_proxy.get_device(self._device_id)
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -050079 # return device.serial_number
80 return 'ADTN'
81
82 @property
83 def alarm_active(self):
84 return self._alarm_active
85
86 @property
87 def heartbeat_count(self):
88 return self._heartbeat_count
89
90 @property
91 def heartbeat_miss(self):
92 return self._heartbeat_miss
93
94 @property
95 def alarms_raised_count(self):
96 return self._alarms_raised_count
97
98 def check_pulse(self):
99 if self.enabled:
100 try:
101 self._defer = self._handler.openomci.omci_cc.send(OntGFrame(self.check_item).get())
102 self._defer.addCallbacks(self._heartbeat_success, self._heartbeat_fail)
103
104 except Exception as e:
105 self._defer = reactor.callLater(5, self._heartbeat_fail, e)
106
107 def _heartbeat_success(self, results):
108 self.log.debug('heartbeat-success')
109
110 try:
111 omci_response = results.getfieldval("omci_message")
112 data = omci_response.getfieldval("data")
113 value = data[self.check_item]
114
115 if value != self.check_value:
116 self._heartbeat_miss = self.heartbeat_failed_limit
117 self.heartbeat_last_reason = "Invalid {}, got '{}' but expected '{}'".\
118 format(self.check_item, value, self.check_value)
119 else:
120 self._heartbeat_miss = 0
121 self.heartbeat_last_reason = ''
122
123 except Exception as e:
124 self._heartbeat_miss = self.heartbeat_failed_limit
125 self.heartbeat_last_reason = e.message
126
127 self.heartbeat_check_status(results)
128
129 def _heartbeat_fail(self, failure):
130 self._heartbeat_miss += 1
131 self.log.info('heartbeat-miss', failure=failure,
132 count=self._heartbeat_count,
133 miss=self._heartbeat_miss)
134 self.heartbeat_last_reason = 'OMCI connectivity error'
135 self.heartbeat_check_status(None)
136
137 def on_heartbeat_alarm(self, active):
138 # TODO: Do something here ?
139 #
140 # TODO: If failed (active = true) due to bad serial-number shut off the UNI port?
141 pass
142
Yongjie Zhang8f891ad2019-07-03 15:32:38 -0400143 @inlineCallbacks
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500144 def heartbeat_check_status(self, results):
145 """
146 Check the number of heartbeat failures against the limit and emit an alarm if needed
147 """
William Kurkian3a206332019-04-29 11:05:47 -0400148 device = self._handler.core_proxy.get_device(self._device_id)
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500149
150 try:
Matt Jeanneret72f96fc2019-02-11 10:53:05 -0500151 from pyvoltha.adapters.extensions.alarms.heartbeat_alarm import HeartbeatAlarm
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500152
153 if self._heartbeat_miss >= self.heartbeat_failed_limit:
154 if device.connect_status == ConnectStatus.REACHABLE:
155 self.log.warning('heartbeat-failed', count=self._heartbeat_miss)
156 device.connect_status = ConnectStatus.UNREACHABLE
157 device.oper_status = OperStatus.FAILED
158 device.reason = self.heartbeat_last_reason
Yongjie Zhang8f891ad2019-07-03 15:32:38 -0400159 yield self._handler.core_proxy.device_update(device)
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500160 HeartbeatAlarm(self._handler.alarms, 'onu', self._heartbeat_miss).raise_alarm()
161 self._alarm_active = True
162 self.on_heartbeat_alarm(True)
163 else:
164 # Update device states
165 if device.connect_status != ConnectStatus.REACHABLE and self._alarm_active:
166 device.connect_status = ConnectStatus.REACHABLE
167 device.oper_status = OperStatus.ACTIVE
168 device.reason = ''
Yongjie Zhang8f891ad2019-07-03 15:32:38 -0400169 yield self._handler.core_proxy.device_update(device)
Matt Jeanneretf1e9c5d2019-02-08 07:41:29 -0500170 HeartbeatAlarm(self._handler.alarms, 'onu').clear_alarm()
171
172 self._alarm_active = False
173 self._alarms_raised_count += 1
174 self.on_heartbeat_alarm(False)
175
176 except Exception as e:
177 self.log.exception('heartbeat-check', e=e)
178
179 # Reschedule next heartbeat
180 if self.enabled:
181 self._heartbeat_count += 1
182 self._defer = reactor.callLater(self.heartbeat_interval, self.check_pulse)