blob: 7a0c439b2d88e32427c17ea89e93f5bb652d19f5 [file] [log] [blame]
Chip Boling32aab302019-01-23 10:50:18 -06001#
2# Copyright 2018 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
17import structlog
18from copy import deepcopy
19from voltha.protos.device_pb2 import ImageDownload
20from voltha.extensions.omci.omci_defs import EntityOperations, ReasonCodes
21import voltha.extensions.omci.omci_entities as omci_entities
22from voltha.extensions.omci.omci_cc import OMCI_CC
23from common.event_bus import EventBusClient
24from voltha.extensions.omci.tasks.task_runner import TaskRunner
25from voltha.extensions.omci.onu_configuration import OnuConfiguration
26from voltha.extensions.omci.tasks.reboot_task import OmciRebootRequest, RebootFlags
27from voltha.extensions.omci.tasks.omci_modify_request import OmciModifyRequest
28from voltha.extensions.omci.omci_me import OntGFrame
29from voltha.extensions.omci.state_machines.image_agent import ImageAgent
30
31from twisted.internet import reactor, defer
32from enum import IntEnum
33
34OP = EntityOperations
35RC = ReasonCodes
36
37ACTIVE_KEY = 'active'
38IN_SYNC_KEY = 'in-sync'
39LAST_IN_SYNC_KEY = 'last-in-sync-time'
40SUPPORTED_MESSAGE_ENTITY_KEY = 'managed-entities'
41SUPPORTED_MESSAGE_TYPES_KEY = 'message-type'
42
43
44class OnuDeviceEvents(IntEnum):
45 # Events of interest to Device Adapters and OpenOMCI State Machines
46 DeviceStatusEvent = 0 # OnuDeviceEntry running status changed
47 MibDatabaseSyncEvent = 1 # MIB database sync changed
48 OmciCapabilitiesEvent = 2 # OMCI ME and message type capabilities
49 AlarmDatabaseSyncEvent = 3 # Alarm database sync changed
50
51 # TODO: Add other events here as needed
52
53
54class OnuDeviceEntry(object):
55 """
56 An ONU Device entry in the MIB
57 """
58 def __init__(self, omci_agent, device_id, adapter_agent, custom_me_map,
59 mib_db, alarm_db, support_classes, clock=None):
60 """
61 Class initializer
62
63 :param omci_agent: (OpenOMCIAgent) Reference to OpenOMCI Agent
64 :param device_id: (str) ONU Device ID
65 :param adapter_agent: (AdapterAgent) Adapter agent for ONU
66 :param custom_me_map: (dict) Additional/updated ME to add to class map
67 :param mib_db: (MibDbApi) MIB Database reference
68 :param alarm_db: (MibDbApi) Alarm Table/Database reference
69 :param support_classes: (dict) State machines and tasks for this ONU
70 """
71 self.log = structlog.get_logger(device_id=device_id)
72
73 self._started = False
74 self._omci_agent = omci_agent # OMCI AdapterAgent
75 self._device_id = device_id # ONU Device ID
76 self._adapter_agent = adapter_agent
77 self._runner = TaskRunner(device_id, clock=clock) # OMCI_CC Task runner
78 self._deferred = None
79 # self._img_download_deferred = None # deferred of image file download from server
80 self._omci_upgrade_deferred = None # deferred of ONU OMCI upgrading procedure
81 self._omci_activate_deferred = None # deferred of ONU OMCI Softwre Image Activate
82 self._img_deferred = None # deferred returned to caller of do_onu_software_download
83 self._first_in_sync = False
84 self._first_capabilities = False
85 self._timestamp = None
86 # self._image_download = None # (voltha_pb2.ImageDownload)
87 self.reactor = clock if clock is not None else reactor
88
89 # OMCI related databases are on a per-agent basis. State machines and tasks
90 # are per ONU Vendor
91 #
92 self._support_classes = support_classes
93 self._configuration = None
94
95 try:
96 # MIB Synchronization state machine
97 self._mib_db_in_sync = False
98 mib_synchronizer_info = support_classes.get('mib-synchronizer')
99 advertise = mib_synchronizer_info['advertise-events']
100 self._mib_sync_sm = mib_synchronizer_info['state-machine'](self._omci_agent,
101 device_id,
102 mib_synchronizer_info['tasks'],
103 mib_db,
104 advertise_events=advertise)
105 # ONU OMCI Capabilities state machine
106 capabilities_info = support_classes.get('omci-capabilities')
107 advertise = capabilities_info['advertise-events']
108 self._capabilities_sm = capabilities_info['state-machine'](self._omci_agent,
109 device_id,
110 capabilities_info['tasks'],
111 advertise_events=advertise)
112 # ONU Performance Monitoring Intervals state machine
113 interval_info = support_classes.get('performance-intervals')
114 advertise = interval_info['advertise-events']
115 self._pm_intervals_sm = interval_info['state-machine'](self._omci_agent, device_id,
116 interval_info['tasks'],
117 advertise_events=advertise)
118
119 # ONU ALARM Synchronization state machine
120 self._alarm_db_in_sync = False
121 alarm_synchronizer_info = support_classes.get('alarm-synchronizer')
122 advertise = alarm_synchronizer_info['advertise-events']
123 self._alarm_sync_sm = alarm_synchronizer_info['state-machine'](self._omci_agent,
124 device_id,
125 alarm_synchronizer_info['tasks'],
126 alarm_db,
127 advertise_events=advertise)
128 # State machine of downloading image file from server
129 downloader_info = support_classes.get('image_downloader')
130 image_upgrader_info = support_classes.get('image_upgrader')
131 # image_activate_info = support_classes.get('image_activator')
132 advertise = downloader_info['advertise-event']
133 # self._img_download_sm = downloader_info['state-machine'](self._omci_agent, device_id,
134 # downloader_info['tasks'],
135 # advertise_events=advertise)
136 self._image_agent = ImageAgent(self._omci_agent, device_id,
137 downloader_info['state-machine'], downloader_info['tasks'],
138 image_upgrader_info['state-machine'], image_upgrader_info['tasks'],
139 # image_activate_info['state-machine'],
140 advertise_events=advertise, clock=clock)
141
142 # self._omci_upgrade_sm = image_upgrader_info['state-machine'](device_id, advertise_events=advertise)
143
144 except Exception as e:
145 self.log.exception('state-machine-create-failed', e=e)
146 raise
147
148 # Put state machines in the order you wish to start them
149
150 self._state_machines = []
151 self._on_start_state_machines = [ # Run when 'start()' called
152 self._mib_sync_sm,
153 self._capabilities_sm,
154 ]
155 self._on_sync_state_machines = [ # Run after first in_sync event
156 self._alarm_sync_sm,
157 ]
158 self._on_capabilities_state_machines = [ # Run after first capabilities events
159 self._pm_intervals_sm
160 ]
161 self._custom_me_map = custom_me_map
162 self._me_map = omci_entities.entity_id_to_class_map.copy()
163
164 if custom_me_map is not None:
165 self._me_map.update(custom_me_map)
166
167 self.event_bus = EventBusClient()
168
169 # Create OMCI communications channel
170 self._omci_cc = OMCI_CC(adapter_agent, self.device_id, self._me_map, clock=clock)
171
172 @staticmethod
173 def event_bus_topic(device_id, event):
174 """
175 Get the topic name for a given event for this ONU Device
176 :param device_id: (str) ONU Device ID
177 :param event: (OnuDeviceEvents) Type of event
178 :return: (str) Topic string
179 """
180 assert event in OnuDeviceEvents, \
181 'Event {} is not an ONU Device Event'.format(event.name)
182 return 'omci-device:{}:{}'.format(device_id, event.name)
183
184 @property
185 def device_id(self):
186 return self._device_id
187
188 @property
189 def omci_cc(self):
190 return self._omci_cc
191
192 @property
193 def adapter_agent(self):
194 return self._adapter_agent
195
196 @property
197 def task_runner(self):
198 return self._runner
199
200 @property
201 def mib_synchronizer(self):
202 """
203 Reference to the OpenOMCI MIB Synchronization state machine for this ONU
204 """
205 return self._mib_sync_sm
206
207 @property
208 def omci_capabilities(self):
209 """
210 Reference to the OpenOMCI OMCI Capabilities state machine for this ONU
211 """
212 return self._capabilities_sm
213
214 @property
215 def pm_intervals_state_machine(self):
216 """
217 Reference to the OpenOMCI PM Intervals state machine for this ONU
218 """
219 return self._pm_intervals_sm
220
221 def set_pm_config(self, pm_config):
222 """
223 Set PM interval configuration
224
225 :param pm_config: (OnuPmIntervalMetrics) PM Interval configuration
226 """
227 self._pm_intervals_sm.set_pm_config(pm_config)
228
229 @property
230 def timestamp(self):
231 """Pollable Metrics last collected timestamp"""
232 return self._timestamp
233
234 @timestamp.setter
235 def timestamp(self, value):
236 self._timestamp = value
237
238 @property
239 def alarm_synchronizer(self):
240 """
241 Reference to the OpenOMCI Alarm Synchronization state machine for this ONU
242 """
243 return self._alarm_sync_sm
244
245 @property
246 def active(self):
247 """
248 Is the ONU device currently active/running
249 """
250 return self._started
251
252 @property
253 def custom_me_map(self):
254 """ Vendor-specific Managed Entity Map for this vendor's device"""
255 return self._custom_me_map
256
257 @property
258 def me_map(self):
259 """ Combined ME and Vendor-specific Managed Entity Map for this device"""
260 return self._me_map
261
262 def _cancel_deferred(self):
263 d, self._deferred = self._deferred, None
264 try:
265 if d is not None and not d.called:
266 d.cancel()
267 except:
268 pass
269
270 @property
271 def mib_db_in_sync(self):
272 return self._mib_db_in_sync
273
274 @mib_db_in_sync.setter
275 def mib_db_in_sync(self, value):
276 if self._mib_db_in_sync != value:
277 # Save value
278 self._mib_db_in_sync = value
279
280 # Start up other state machines if needed
281 if self._first_in_sync:
282 self.first_in_sync_event()
283
284 # Notify any event listeners
285 topic = OnuDeviceEntry.event_bus_topic(self.device_id,
286 OnuDeviceEvents.MibDatabaseSyncEvent)
287 msg = {
288 IN_SYNC_KEY: self._mib_db_in_sync,
289 LAST_IN_SYNC_KEY: self.mib_synchronizer.last_mib_db_sync
290 }
291 self.event_bus.publish(topic=topic, msg=msg)
292
293 @property
294 def alarm_db_in_sync(self):
295 return self._alarm_db_in_sync
296
297 @alarm_db_in_sync.setter
298 def alarm_db_in_sync(self, value):
299 if self._alarm_db_in_sync != value:
300 # Save value
301 self._alarm_db_in_sync = value
302
303 # Start up other state machines if needed
304 if self._first_in_sync:
305 self.first_in_sync_event()
306
307 # Notify any event listeners
308 topic = OnuDeviceEntry.event_bus_topic(self.device_id,
309 OnuDeviceEvents.AlarmDatabaseSyncEvent)
310 msg = {
311 IN_SYNC_KEY: self._alarm_db_in_sync
312 }
313 self.event_bus.publish(topic=topic, msg=msg)
314
315 @property
316 def configuration(self):
317 """
318 Get the OMCI Configuration object for this ONU. This is a class that provides some
319 common database access functions for ONU capabilities and read-only configuration values.
320
321 :return: (OnuConfiguration)
322 """
323 return self._configuration
324
325 @property
326 def image_agent(self):
327 return self._image_agent
328
329 # @property
330 # def image_download(self):
331 # return self._image_download
332
333 def start(self):
334 """
335 Start the ONU Device Entry state machines
336 """
337 self.log.debug('OnuDeviceEntry.start', previous=self._started)
338 if self._started:
339 return
340
341 self._started = True
342 self._omci_cc.enabled = True
343 self._first_in_sync = True
344 self._first_capabilities = True
345 self._runner.start()
346 self._configuration = OnuConfiguration(self._omci_agent, self._device_id)
347
348 # Start MIB Sync and other state machines that can run before the first
349 # MIB Synchronization event occurs. Start 'later' so that any
350 # ONU Device, OMCI DB, OMCI Agent, and others are fully started before
351 # performing the start.
352
353 self._state_machines = []
354
355 def start_state_machines(machines):
356 for sm in machines:
357 self._state_machines.append(sm)
358 sm.start()
359
360 self._deferred = reactor.callLater(0, start_state_machines,
361 self._on_start_state_machines)
362 # Notify any event listeners
363 self._publish_device_status_event()
364
365 def stop(self):
366 """
367 Stop the ONU Device Entry state machines
368 """
369 if not self._started:
370 return
371
372 self._started = False
373 self._cancel_deferred()
374 self._omci_cc.enabled = False
375
376 # Halt MIB Sync and other state machines
377 for sm in self._state_machines:
378 sm.stop()
379
380 self._state_machines = []
381
382 # Stop task runner
383 self._runner.stop()
384
385 # Notify any event listeners
386 self._publish_device_status_event()
387
388 def first_in_sync_event(self):
389 """
390 This event is called on the first MIB synchronization event after
391 OpenOMCI has been started. It is responsible for starting any
392 other state machine and to initiate an ONU Capabilities report
393 """
394 if self._first_in_sync:
395 self._first_in_sync = False
396
397 # Start up the ONU Capabilities task
398 self._configuration.reset()
399
400 # Insure that the ONU-G Administrative lock is disabled
401 def failure(reason):
402 self.log.error('disable-admin-state-lock', reason=reason)
403
404 frame = OntGFrame(attributes={'administrative_state': 0}).set()
405 task = OmciModifyRequest(self._omci_agent, self.device_id, frame)
406 self.task_runner.queue_task(task).addErrback(failure)
407
408 # Start up any other remaining OpenOMCI state machines
409 def start_state_machines(machines):
410 for sm in machines:
411 self._state_machines.append(sm)
412 reactor.callLater(0, sm.start)
413
414 self._deferred = reactor.callLater(0, start_state_machines,
415 self._on_sync_state_machines)
416
417 # if an ongoing upgrading is not accomplished, restart it
418 if self._img_deferred is not None:
419 self._image_agent.onu_bootup()
420
421 def first_in_capabilities_event(self):
422 """
423 This event is called on the first capabilities event after
424 OpenOMCI has been started. It is responsible for starting any
425 other state machine. These are often state machines that have tasks
426 that are dependent upon knowing if various MEs are supported
427 """
428 if self._first_capabilities:
429 self._first_capabilities = False
430
431 # Start up any other remaining OpenOMCI state machines
432 def start_state_machines(machines):
433 for sm in machines:
434 self._state_machines.append(sm)
435 reactor.callLater(0, sm.start)
436
437 self._deferred = reactor.callLater(0, start_state_machines,
438 self._on_capabilities_state_machines)
439
440 # def __on_omci_download_success(self, image_download):
441 # self.log.debug("__on_omci_download_success", image=image_download)
442 # self._omci_upgrade_deferred = None
443 # # self._ret_deferred = None
444 # self._omci_activate_deferred = self._image_agent.activate_onu_image(image_download.name)
445 # self._omci_activate_deferred.addCallbacks(self.__on_omci_image_activate_success,
446 # self.__on_omci_image_activate_fail, errbackArgs=(image_name,))
447 # return image_name
448
449 # def __on_omci_download_fail(self, fail, image_name):
450 # self.log.debug("__on_omci_download_fail", failure=fail, image_name=image_name)
451 # self.reactor.callLater(0, self._img_deferred.errback, fail)
452 # self._omci_upgrade_deferred = None
453 # self._img_deferred = None
454
455 def __on_omci_image_activate_success(self, image_name):
456 self.log.debug("__on_omci_image_activate_success", image_name=image_name)
457 self._omci_activate_deferred = None
458 self._img_deferred.callback(image_name)
459 self._img_deferred = None
460 return image_name
461
462 def __on_omci_image_activate_fail(self, fail, image_name):
463 self.log.debug("__on_omci_image_activate_fail", faile=fail, image_name=image_name)
464 self._omci_activate_deferred = None
465 self._img_deferred.errback(fail)
466 self._img_deferred = None
467
468 def _publish_device_status_event(self):
469 """
470 Publish the ONU Device start/start status.
471 """
472 topic = OnuDeviceEntry.event_bus_topic(self.device_id,
473 OnuDeviceEvents.DeviceStatusEvent)
474 msg = {ACTIVE_KEY: self._started}
475 self.event_bus.publish(topic=topic, msg=msg)
476
477 def publish_omci_capabilities_event(self):
478 """
479 Publish the ONU Device start/start status.
480 """
481 if self.first_in_capabilities_event:
482 self.first_in_capabilities_event()
483
484 topic = OnuDeviceEntry.event_bus_topic(self.device_id,
485 OnuDeviceEvents.OmciCapabilitiesEvent)
486 msg = {
487 SUPPORTED_MESSAGE_ENTITY_KEY: self.omci_capabilities.supported_managed_entities,
488 SUPPORTED_MESSAGE_TYPES_KEY: self.omci_capabilities.supported_message_types
489 }
490 self.event_bus.publish(topic=topic, msg=msg)
491
492 def delete(self):
493 """
494 Stop the ONU Device's state machine and remove the ONU, and any related
495 OMCI state information from the OpenOMCI Framework
496 """
497 self.stop()
498 self.mib_synchronizer.delete()
499
500 # OpenOMCI cleanup
501 if self._omci_agent is not None:
502 self._omci_agent.remove_device(self._device_id, cleanup=True)
503
504 def query_mib(self, class_id=None, instance_id=None, attributes=None):
505 """
506 Get MIB database information.
507
508 This method can be used to request information from the database to the detailed
509 level requested
510
511 :param class_id: (int) Managed Entity class ID
512 :param instance_id: (int) Managed Entity instance
513 :param attributes: (list or str) Managed Entity instance's attributes
514
515 :return: (dict) The value(s) requested. If class/inst/attribute is
516 not found, an empty dictionary is returned
517 :raises DatabaseStateError: If the database is not enabled
518 """
519 self.log.debug('query', class_id=class_id, instance_id=instance_id,
520 attributes=attributes)
521
522 return self.mib_synchronizer.query_mib(class_id=class_id, instance_id=instance_id,
523 attributes=attributes)
524
525 def query_mib_single_attribute(self, class_id, instance_id, attribute):
526 """
527 Get MIB database information for a single specific attribute
528
529 This method can be used to request information from the database to the detailed
530 level requested
531
532 :param class_id: (int) Managed Entity class ID
533 :param instance_id: (int) Managed Entity instance
534 :param attribute: (str) Managed Entity instance's attribute
535
536 :return: (varies) The value requested. If class/inst/attribute is
537 not found, None is returned
538 :raises DatabaseStateError: If the database is not enabled
539 """
540 self.log.debug('query-single', class_id=class_id,
541 instance_id=instance_id, attributes=attribute)
542 assert isinstance(attribute, basestring), \
543 'Only a single attribute value can be retrieved'
544
545 entry = self.mib_synchronizer.query_mib(class_id=class_id,
546 instance_id=instance_id,
547 attributes=attribute)
548
549 return entry[attribute] if attribute in entry else None
550
551 def query_alarm_table(self, class_id=None, instance_id=None):
552 """
553 Get Alarm information
554
555 This method can be used to request information from the alarm database to
556 the detailed level requested
557
558 :param class_id: (int) Managed Entity class ID
559 :param instance_id: (int) Managed Entity instance
560
561 :return: (dict) The value(s) requested. If class/inst/attribute is
562 not found, an empty dictionary is returned
563 :raises DatabaseStateError: If the database is not enabled
564 """
565 self.log.debug('query', class_id=class_id, instance_id=instance_id)
566
567 return self.alarm_synchronizer.query_mib(class_id=class_id, instance_id=instance_id)
568
569 def reboot(self,
570 flags=RebootFlags.Reboot_Unconditionally,
571 timeout=OmciRebootRequest.DEFAULT_REBOOT_TIMEOUT):
572 """
573 Request a reboot of the ONU
574
575 :param flags: (RebootFlags) Reboot condition
576 :param timeout: (int) Reboot task priority
577 :return: (deferred) Fires upon completion or error
578 """
579 assert self.active, 'This device is not active'
580
581 return self.task_runner.queue_task(OmciRebootRequest(self._omci_agent,
582 self.device_id,
583 flags=flags,
584 timeout=timeout))
585
586 # def get_imagefile(self, local_name, local_dir, remote_url=None):
587 # """
588 # Return a Deferred that will be triggered if the file is locally available
589 # or downloaded successfully
590 # """
591 # self.log.info('start download from {}'.format(remote_url))
592
593 # # for debug purpose, start runner here to queue downloading task
594 # # self._runner.start()
595
596 # return self._image_agent.get_image(self._image_download)
597
598 def do_onu_software_download(self, image_dnld):
599 """
600 image_dnld: (ImageDownload)
601 : Return a Deferred that will be triggered when upgrading results in success or failure
602 """
603 self.log.debug('do_onu_software_download')
604 image_download = deepcopy(image_dnld)
605 # self._img_download_deferred = self._image_agent.get_image(self._image_download)
606 # self._img_download_deferred.addCallbacks(self.__on_download_success, self.__on_download_fail, errbackArgs=(self._image_download,))
607 # self._ret_deferred = defer.Deferred()
608 # return self._ret_deferred
609 return self._image_agent.get_image(image_download)
610
611 # def do_onu_software_switch(self):
612 def do_onu_image_activate(self, image_dnld_name):
613 """
614 Return a Deferred that will be triggered when switching software image results in success or failure
615 """
616 if self._img_deferred is None:
617 self.log.debug('do_onu_image_activate')
618 self._img_deferred = defer.Deferred()
619 self._omci_upgrade_deferred = self._image_agent.onu_omci_download(image_dnld_name)
620 self._omci_upgrade_deferred.addCallbacks(self.__on_omci_image_activate_success,
621 self.__on_omci_image_activate_fail, errbackArgs=(image_dnld_name,))
622 return self._img_deferred
623
624 def cancel_onu_software_download(self, image_name):
625 self.log.debug('cancel_onu_software_download')
626 self._image_agent.cancel_download_image(image_name)
627 self._image_agent.cancel_upgrade_onu()
628 if self._img_deferred and not self._img_deferred.called:
629 self._img_deferred.cancel()
630 self._img_deferred = None
631 # self._image_download = None
632
633 def get_image_download_status(self, image_name):
634 return self._image_agent.get_image_status(image_name)
635