blob: 8469a3f6b66f68d5bfadaae2649e45a25a51b99b [file] [log] [blame]
Chip Boling8e042f62019-02-12 16:14:34 -06001# Copyright 2018-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.
14import structlog
15from twisted.internet.defer import inlineCallbacks, returnValue, TimeoutError
16from twisted.internet import reactor
17
18from voltha.protos.device_pb2 import Image
19
20from voltha.protos.common_pb2 import OperStatus, ConnectStatus
21from voltha.extensions.omci.onu_configuration import OMCCVersion
22
23from omci_entities import onu_custom_me_entities
24from voltha.extensions.omci.omci_me import *
25
26_STARTUP_RETRY_WAIT = 5
27# abbreviations
28OP = EntityOperations
29
30
31class OMCI(object):
32 """
33 OpenOMCI Support
34 """
35 DEFAULT_UNTAGGED_VLAN = 4091 # To be equivalent to BroadCom Defaults
36
37 def __init__(self, handler, omci_agent):
38 self.log = structlog.get_logger(device_id=handler.device_id)
39 self._handler = handler
40 self._openomci_agent = omci_agent
41 self._enabled = False
42 self._connected = False
43 self._deferred = None
44 self._bridge_initialized = False
45 self._in_sync_reached = False
46 self._omcc_version = OMCCVersion.Unknown
47 self._total_tcont_count = 0 # From ANI-G ME
48 self._qos_flexibility = 0 # From ONT2_G ME
49
50 self._in_sync_subscription = None
51 self._connectivity_subscription = None
52 self._capabilities_subscription = None
53
54 # self._service_downloaded = False
55 self._mib_downloaded = False
56 self._mib_download_task = None
57 self._mib_download_deferred = None
58
59 self._onu_omci_device = omci_agent.add_device(handler.device_id,
60 handler.adapter_agent,
61 custom_me_map=onu_custom_me_entities(),
62 support_classes=handler.adapter.adtran_omci)
63
64 def __str__(self):
65 return "OMCI"
66
67 @property
68 def omci_agent(self):
69 return self._openomci_agent
70
71 @property
72 def omci_cc(self):
73 # TODO: Decrement access to Communications channel at this point? What about current PM stuff?
74 return self.onu_omci_device.omci_cc if self._onu_omci_device is not None else None
75
76 def receive_message(self, msg):
77 if self.enabled:
78 # TODO: Have OpenOMCI actually receive the messages
79 self.omci_cc.receive_message(msg)
80
81 def _start(self):
82 self._cancel_deferred()
83
84 # Subscriber to events of interest in OpenOMCI
85 self._subscribe_to_events()
86 self._onu_omci_device.start()
87
88 device = self._handler.adapter_agent.get_device(self._handler.device_id)
89 device.reason = 'Performing MIB Upload'
90 self._handler.adapter_agent.update_device(device)
91
92 if self._onu_omci_device.mib_db_in_sync:
93 self._deferred = reactor.callLater(0, self._mib_in_sync)
94
95 def _stop(self):
96 self._cancel_deferred()
97
98 # Unsubscribe to OpenOMCI Events
99 self._unsubscribe_to_events()
100 self._onu_omci_device.stop() # Will also cancel any running tasks/state-machines
101
102 self._mib_downloaded = False
103 self._mib_download_task = None
104 self._bridge_initialized = False
105 self._in_sync_reached = False
106
107 def _cancel_deferred(self):
108 d1, self._deferred = self._deferred, None
109 d2, self._mib_download_deferred = self._mib_download_deferred, None
110
111 for d in [d1, d2]:
112 try:
113 if d is not None and not d.called:
114 d.cancel()
115 except:
116 pass
117
118 def delete(self):
119 self.enabled = False
120
121 agent, self._openomci_agent = self._openomci_agent, None
122 device_id = self._handler.device_id
123 self._onu_omci_device = None
124 self._handler = None
125
126 if agent is not None:
127 agent.remove_device(device_id, cleanup=True)
128
129 @property
130 def enabled(self):
131 return self._enabled
132
133 @enabled.setter
134 def enabled(self, value):
135 if self._enabled != value:
136 self._enabled = value
137
138 if value:
139 self._start()
140 else:
141 self._stop()
142
143 @property
144 def connected(self):
145 return self._connected
146
147 @property
148 def onu_omci_device(self):
149 return self._onu_omci_device
150
151 def set_pm_config(self, pm_config):
152 """
153 Set PM interval configuration
154
155 :param pm_config: (OnuPmIntervalMetrics) PM Interval configuration
156 :return:
157 """
158 self.onu_omci_device.set_pm_config(pm_config)
159
160 def _mib_in_sync(self):
161 """
162 This method is ran whenever the ONU MIB database is in-sync. This is often after
163 the initial MIB Upload during ONU startup, or after it has gone out-of-sync and
164 then back in. This second case could be due a reboot of the ONU and a new version
165 of firmware is running on the ONU hardware.
166 """
167 self.log.info('mib-in-sync')
168
169 device = self._handler.adapter_agent.get_device(self._handler.device_id)
170 device.oper_status = OperStatus.ACTIVE
171 device.connect_status = ConnectStatus.REACHABLE
172 device.reason = ''
173 self._handler.adapter_agent.update_device(device)
174
175 omci_dev = self._onu_omci_device
176 config = omci_dev.configuration
177
178 # In Sync, we can register logical ports now. Ideally this could occur on
179 # the first time we received a successful (no timeout) OMCI Rx response.
180 try:
181 device = self._handler.adapter_agent.get_device(self._handler.device_id)
182
183 ani_g = config.ani_g_entities
184 uni_g = config.uni_g_entities
185 pon_ports = len(ani_g) if ani_g is not None else 0
186 uni_ports = len(uni_g) if uni_g is not None else 0
187
188 # For the UNI ports below, they are created after the MIB Sync event occurs
189 # and the onu handler adds the ONU
190 assert pon_ports == 1, 'Expected one PON/ANI port, got {}'.format(pon_ports)
191 assert uni_ports == len(self._handler.uni_ports), \
192 'Expected {} UNI port(s), got {}'.format(len(self._handler.uni_ports), uni_ports)
193
194 # serial_number = omci_dev.configuration.serial_number
195 # self.log.info('serial-number', serial_number=serial_number)
196
197 # Save entity_id of PON ports
198 self._handler.pon_ports[0].entity_id = ani_g.keys()[0]
199
200 self._total_tcont_count = ani_g.get('total-tcont-count')
201 self._qos_flexibility = config.qos_configuration_flexibility or 0
202 self._omcc_version = config.omcc_version or OMCCVersion.Unknown
203
204 # vendorProductCode = str(config.vendor_product_code or 'unknown').rstrip('\0')
205
206 host_info = omci_dev.query_mib(IpHostConfigData.class_id)
207 mgmt_mac_address = next((host_info[inst].get('attributes').get('mac_address')
208 for inst in host_info
209 if isinstance(inst, int)), 'unknown')
210 device.mac_address = str(mgmt_mac_address)
211 device.model = str(config.version or 'unknown').rstrip('\0')
212
213 equipment_id = config.equipment_id or " unknown unknown "
214 eqpt_boot_version = str(equipment_id).rstrip('\0')
215 # eqptId = eqpt_boot_version[:10] # ie) BVMDZ10DRA
216 boot_version = eqpt_boot_version[12:] # ie) CML.D55~
217
218 images = [Image(name='boot-code',
219 version=boot_version.rstrip('\0'),
220 is_active=False,
221 is_committed=True,
222 is_valid=True,
223 install_datetime='Not Available',
224 hash='Not Available')] + \
225 config.software_images
226
227 del (device.images.image[:]) # Clear previous entries
228 device.images.image.extend(images)
229
230 # Save our device information
231 self._handler.adapter_agent.update_device(device)
232
233 # Start MIB download TODO: This will be replaced with a MIB Download task soon
234 self._in_sync_reached = True
235
236 except Exception as e:
237 self.log.exception('device-info-load', e=e)
238 self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, self._mib_in_sync)
239
240 def _subscribe_to_events(self):
241 from voltha.extensions.omci.onu_device_entry import OnuDeviceEvents, \
242 OnuDeviceEntry
243 from voltha.extensions.omci.omci_cc import OMCI_CC, OmciCCRxEvents
244
245 # OMCI MIB Database sync status
246 bus = self._onu_omci_device.event_bus
247 topic = OnuDeviceEntry.event_bus_topic(self._handler.device_id,
248 OnuDeviceEvents.MibDatabaseSyncEvent)
249 self._in_sync_subscription = bus.subscribe(topic, self.in_sync_handler)
250
251 # OMCI Capabilities (MEs and Message Types
252 bus = self._onu_omci_device.event_bus
253 topic = OnuDeviceEntry.event_bus_topic(self._handler.device_id,
254 OnuDeviceEvents.OmciCapabilitiesEvent)
255 self._capabilities_subscription = bus.subscribe(topic, self.capabilities_handler)
256
257 # OMCI-CC Connectivity Events (for reachability/heartbeat)
258 bus = self._onu_omci_device.omci_cc.event_bus
259 topic = OMCI_CC.event_bus_topic(self._handler.device_id,
260 OmciCCRxEvents.Connectivity)
261 self._connectivity_subscription = bus.subscribe(topic, self.onu_is_reachable)
262
263 # TODO: Watch for any MIB RESET events or detection of an ONU reboot.
264 # If it occurs, set _service_downloaded and _mib_download to false
265 # and make sure that we get 'new' capabilities
266
267 def _unsubscribe_to_events(self):
268 insync, self._in_sync_subscription = self._in_sync_subscription, None
269 connect, self._connectivity_subscription = self._connectivity_subscription, None
270 caps, self._capabilities_subscription = self._capabilities_subscription, None
271
272 if insync is not None:
273 bus = self._onu_omci_device.event_bus
274 bus.unsubscribe(insync)
275
276 if connect is not None:
277 bus = self._onu_omci_device.omci_cc.event_bus
278 bus.unsubscribe(connect)
279
280 if caps is not None:
281 bus = self._onu_omci_device.event_bus
282 bus.unsubscribe(caps)
283
284 def in_sync_handler(self, _topic, msg):
285 if self._in_sync_subscription is not None:
286 try:
287 from voltha.extensions.omci.onu_device_entry import IN_SYNC_KEY
288
289 if msg[IN_SYNC_KEY]:
290 # Start up device_info load from MIB DB
291 reactor.callLater(0, self._mib_in_sync)
292 else:
293 # Cancel any running/scheduled MIB download task
294 try:
295 d, self._mib_download_deferred = self._mib_download_deferred, None
296 d.cancel()
297 except:
298 pass
299
300 except Exception as e:
301 self.log.exception('in-sync', e=e)
302
303 def capabilities_handler(self, _topic, _msg):
304 """
305 This event occurs after an ONU reaches the In-Sync state and the OMCI ME has
306 been queried for supported ME and message types.
307
308 At this point, we can act upon any download device and/or service Technology
309 profiles (when they exist). For now, just run our somewhat fixed script
310 """
311 if self._capabilities_subscription is not None:
312 from adtn_mib_download_task import AdtnMibDownloadTask
313 self._mib_download_task = None
314
315 def success(_results):
316 dev = self._handler.adapter_agent.get_device(self._handler.device_id)
317 dev.reason = ''
318 self._handler.adapter_agent.update_device(dev)
319 self._mib_downloaded = True
320 self._mib_download_task = None
321
322 def failure(reason):
323 self.log.error('mib-download-failure', reason=reason)
324 self._mib_download_task = None
325 dev = self._handler.adapter_agent.get_device(self._handler.device_id)
326 self._handler.adapter_agent.update_device(dev)
327 self._mib_download_deferred = reactor.callLater(_STARTUP_RETRY_WAIT,
328 self.capabilities_handler,
329 None, None)
330 if not self._mib_downloaded:
331 device = self._handler.adapter_agent.get_device(self._handler.device_id)
332 device.reason = 'Initial MIB Download'
333 self._handler.adapter_agent.update_device(device)
334 self._mib_download_task = AdtnMibDownloadTask(self.omci_agent,
335 self._handler)
336 if self._mib_download_task is not None:
337 self._mib_download_deferred = \
338 self._onu_omci_device.task_runner.queue_task(self._mib_download_task)
339 self._mib_download_deferred.addCallbacks(success, failure)
340
341 def onu_is_reachable(self, _topic, msg):
342 """
343 Reach-ability change event
344 :param _topic: (str) subscription topic, not used
345 :param msg: (dict) 'connected' key holds True if reachable
346 """
347 from voltha.extensions.omci.omci_cc import CONNECTED_KEY
348 if self._connectivity_subscription is not None:
349 try:
350 connected = msg[CONNECTED_KEY]
351
352 # TODO: For now, only care about the first connect occurrence.
353 # Later we could use this for a heartbeat, but may want some hysteresis
354 # Cancel any 'reachable' subscriptions
355 if connected:
356 evt_bus = self._onu_omci_device.omci_cc.event_bus
357 evt_bus.unsubscribe(self._connectivity_subscription)
358 self._connectivity_subscription = None
359 self._connected = True
360
361 device = self._handler.adapter_agent.get_device(self._handler.device_id)
362 device.oper_status = OperStatus.ACTIVE
363 device.connect_status = ConnectStatus.REACHABLE
364 self._handler.adapter_agent.update_device(device)
365
366 except Exception as e:
367 self.log.exception('onu-reachable', e=e)