class ConfigEventBus(object): __slots__ = ( '_event_bus_client', # The event bus client used to publish events. '_topic' # the topic to publish to ) def __init__(self): self._event_bus_client = EventBusClient() self._topic = 'model-change-events' def advertise(self, type, data, hash=None): if type in IGNORED_CALLBACKS: log.info('Ignoring event {} with data {}'.format(type, data)) return if type is CallbackType.POST_ADD: kind = ConfigEventType.add elif type is CallbackType.POST_REMOVE: kind = ConfigEventType.remove else: kind = ConfigEventType.update if isinstance(data, Message): msg = dumps(MessageToDict(data, True, True)) else: msg = data event = ConfigEvent( type=kind, hash=hash, data=msg ) self._event_bus_client.publish(self._topic, event)
class OpenOmciEventBus(object): """ Event bus for publishing OpenOMCI related events. """ __slots__ = ( '_event_bus_client', # The event bus client used to publish events. '_topic' # the topic to publish to ) def __init__(self): self._event_bus_client = EventBusClient() self._topic = 'openomci-events' def message_to_dict(m): return MessageToDict(m, True, True, False) def advertise(self, event_type, data): if isinstance(data, Message): msg = dumps(MessageToDict(data, True, True)) elif isinstance(data, dict): msg = dumps(data) else: msg = str(data) event_func = AlarmOpenOmciEvent if 'AlarmSynchronizer' in msg \ else OpenOmciEvent event = event_func(type=event_type, data=msg) self._event_bus_client.publish(self._topic, event)
def test_simple_publish(self): ebc = EventBusClient(EventBus()) mock = Mock() ebc.subscribe('news', mock) ebc.publish('news', 'message') self.assertEqual(mock.call_count, 1) mock.assert_called_with('news', 'message')
def test_multiple_subscribers(self): ebc = EventBusClient(EventBus()) mock1 = Mock() ebc.subscribe('news', mock1) mock2 = Mock() ebc.subscribe('alerts', mock2) mock3 = Mock() ebc.subscribe('logs', mock3) mock4 = Mock() ebc.subscribe('logs', mock4) ebc.publish('news', 'msg1') ebc.publish('alerts', 'msg2') ebc.publish('logs', 'msg3') self.assertEqual(mock1.call_count, 1) mock1.assert_called_with('news', 'msg1') self.assertEqual(mock2.call_count, 1) mock2.assert_called_with('alerts', 'msg2') self.assertEqual(mock3.call_count, 1) mock3.assert_called_with('logs', 'msg3') self.assertEqual(mock4.call_count, 1) mock4.assert_called_with('logs', 'msg3')
def test_subscribers_that_unsubscribe_when_called(self): # VOL-943 bug fix check ebc = EventBusClient(EventBus()) class UnsubscribeWhenCalled(object): def __init__(self): self.subscription = ebc.subscribe('news', self.unsubscribe) self.called = False def unsubscribe(self, _topic, _msg): self.called = True ebc.unsubscribe(self.subscription) ebc1 = UnsubscribeWhenCalled() ebc2 = UnsubscribeWhenCalled() ebc3 = UnsubscribeWhenCalled() ebc.publish('news', 'msg1') self.assertTrue(ebc1.called) self.assertTrue(ebc2.called) self.assertTrue(ebc3.called)
def test_topic_filtering(self): ebc = EventBusClient(EventBus()) mock = Mock() ebc.subscribe('news', mock) ebc.publish('news', 'msg1') ebc.publish('alerts', 'msg2') ebc.publish('logs', 'msg3') self.assertEqual(mock.call_count, 1) mock.assert_called_with('news', 'msg1')
def test_wildcard_topic(self): ebc = EventBusClient(EventBus()) subs = [] wildcard_sub = Mock() subs.append(ebc.subscribe(re.compile(r'.*'), wildcard_sub)) prefix_sub = Mock() subs.append(ebc.subscribe(re.compile(r'ham.*'), prefix_sub)) contains_sub = Mock() subs.append(ebc.subscribe(re.compile(r'.*burg.*'), contains_sub)) ebc.publish('news', 1) ebc.publish('hamsters', 2) ebc.publish('hamburgers', 3) ebc.publish('nonsense', 4) c = call self.assertEqual(wildcard_sub.call_count, 4) wildcard_sub.assert_has_calls([ c('news', 1), c('hamsters', 2), c('hamburgers', 3), c('nonsense', 4)]) self.assertEqual(prefix_sub.call_count, 2) prefix_sub.assert_has_calls([ c('hamsters', 2), c('hamburgers', 3)]) self.assertEqual(contains_sub.call_count, 1) contains_sub.assert_has_calls([c('hamburgers', 3)]) for sub in subs: ebc.unsubscribe(sub) self.assertEqual(ebc.list_subscribers(), [])
def test_predicates(self): ebc = EventBusClient(EventBus()) get_foos = Mock() ebc.subscribe('', get_foos, lambda msg: msg.startswith('foo')) get_bars = Mock() ebc.subscribe('', get_bars, lambda msg: msg.endswith('bar')) get_all = Mock() ebc.subscribe('', get_all) get_none = Mock() ebc.subscribe('', get_none, lambda msg: msg.find('zoo') >= 0) errored = Mock() ebc.subscribe('', errored, lambda msg: 1/0) ebc.publish('', 'foo') ebc.publish('', 'foobar') ebc.publish('', 'bar') c = call self.assertEqual(get_foos.call_count, 2) get_foos.assert_has_calls([c('', 'foo'), c('', 'foobar')]) self.assertEqual(get_bars.call_count, 2) get_bars.assert_has_calls([c('', 'foobar'), c('', 'bar')]) self.assertEqual(get_all.call_count, 3) get_all.assert_has_calls([c('', 'foo'), c('', 'foobar'), c('', 'bar')]) get_none.assert_not_called() errored.assert_not_called()
class OMCI_CC(object): """ Handle OMCI Communication Channel specifics for Adtran ONUs""" MIN_OMCI_TX_ID_LOW_PRIORITY = 0x0001 # 2 Octets max MAX_OMCI_TX_ID_LOW_PRIORITY = 0x7FFF # 2 Octets max MIN_OMCI_TX_ID_HIGH_PRIORITY = 0x8000 # 2 Octets max MAX_OMCI_TX_ID_HIGH_PRIORITY = 0xFFFF # 2 Octets max LOW_PRIORITY = 0 HIGH_PRIORITY = 1 # Offset into some tuples for pending lists and tx in progress PENDING_DEFERRED = 0 PENDING_FRAME = 1 PENDING_TIMEOUT = 2 PENDING_RETRY = 3 REQUEST_TIMESTAMP = 0 REQUEST_DEFERRED = 1 REQUEST_FRAME = 2 REQUEST_TIMEOUT = 3 REQUEST_RETRY = 4 REQUEST_DELAYED_CALL = 5 _frame_to_event_type = { OmciMibResetResponse.message_id: RxEvent.MIB_Reset, OmciMibUploadResponse.message_id: RxEvent.MIB_Upload, OmciMibUploadNextResponse.message_id: RxEvent.MIB_Upload_Next, OmciCreateResponse.message_id: RxEvent.Create, OmciDeleteResponse.message_id: RxEvent.Delete, OmciSetResponse.message_id: RxEvent.Set, OmciGetAllAlarmsResponse.message_id: RxEvent.Get_ALARM_Get, OmciGetAllAlarmsNextResponse.message_id: RxEvent.Get_ALARM_Get_Next } def __init__(self, core_proxy, adapter_proxy, device_id, me_map=None, clock=None): self.log = structlog.get_logger(device_id=device_id) self._core_proxy = core_proxy self._adapter_proxy = adapter_proxy self._device_id = device_id self._proxy_address = None self._enabled = False self._extended_messaging = False self._me_map = me_map if clock is None: self.reactor = reactor else: self.reactor = clock # Support 2 levels of priority since only baseline message set supported self._tx_tid = [ OMCI_CC.MIN_OMCI_TX_ID_LOW_PRIORITY, OMCI_CC.MIN_OMCI_TX_ID_HIGH_PRIORITY ] self._tx_request = [ None, None ] # Tx in progress (timestamp, defer, frame, timeout, retry, delayedCall) self._pending = [ list(), list() ] # pending queue (deferred, tx_frame, timeout, retry) self._rx_response = [None, None] # Statistics self._tx_frames = 0 self._rx_frames = 0 self._rx_unknown_tid = 0 # Rx OMCI with no Tx TID match self._rx_onu_frames = 0 # Autonomously generated ONU frames self._rx_onu_discards = 0 # Autonomously generated ONU unknown message types self._rx_timeouts = 0 self._rx_late = 0 # Frame response received after timeout on Tx self._rx_unknown_me = 0 # Number of managed entities Rx without a decode definition self._tx_errors = 0 # Exceptions during tx request self._consecutive_errors = 0 # Rx & Tx errors in a row, a good RX resets this to 0 self._reply_min = sys.maxint # Fastest successful tx -> rx self._reply_max = 0 # Longest successful tx -> rx self._reply_sum = 0.0 # Total seconds for successful tx->rx (float for average) self._max_hp_tx_queue = 0 # Maximum size of high priority tx pending queue self._max_lp_tx_queue = 0 # Maximum size of low priority tx pending queue self.event_bus = EventBusClient() # If a list of custom ME Entities classes were provided, insert them into # main class_id to entity map. # TODO: If this class becomes hidden from the ONU DA, move this to the OMCI State Machine runner def __str__(self): return "OMCISupport: {}".format(self._device_id) def _get_priority_index(self, high_priority): """ Centralized logic to help make extended message support easier in the future""" return OMCI_CC.HIGH_PRIORITY if high_priority and not self._extended_messaging \ else OMCI_CC.LOW_PRIORITY def _tid_is_high_priority(self, tid): """ Centralized logic to help make extended message support easier in the future""" return not self._extended_messaging and \ OMCI_CC.MIN_OMCI_TX_ID_HIGH_PRIORITY <= tid <= OMCI_CC.MAX_OMCI_TX_ID_HIGH_PRIORITY @staticmethod def event_bus_topic(device_id, event): """ Get the topic name for a given event Frame Type :param device_id: (str) ONU Device ID :param event: (OmciCCRxEvents) Type of event :return: (str) Topic string """ assert event in OmciCCRxEvents, \ 'Event {} is not an OMCI-CC Rx Event'.format(event.name) return 'omci-rx:{}:{}'.format(device_id, event.name) @property def enabled(self): return self._enabled @enabled.setter def enabled(self, value): """ Enable/disable the OMCI Communications Channel :param value: (boolean) True to enable, False to disable """ assert isinstance(value, bool), 'enabled is a boolean' if self._enabled != value: self._enabled = value if self._enabled: self._start() else: self._stop() @property def tx_frames(self): return self._tx_frames @property def rx_frames(self): return self._rx_frames @property def rx_unknown_tid(self): return self._rx_unknown_tid # Tx TID not found @property def rx_unknown_me(self): return self._rx_unknown_me @property def rx_onu_frames(self): return self._rx_onu_frames @property def rx_onu_discards(self): return self._rx_onu_discards # Attribute Value change autonomous overflows @property def rx_timeouts(self): return self._rx_timeouts @property def rx_late(self): return self._rx_late @property def tx_errors(self): return self._tx_errors @property def consecutive_errors(self): return self._consecutive_errors @property def reply_min(self): return int(round(self._reply_min * 1000.0)) # Milliseconds @property def reply_max(self): return int(round(self._reply_max * 1000.0)) # Milliseconds @property def reply_average(self): avg = self._reply_sum / self._rx_frames if self._rx_frames > 0 else 0.0 return int(round(avg * 1000.0)) # Milliseconds @property def hp_tx_queue_len(self): return len(self._pending[OMCI_CC.HIGH_PRIORITY]) @property def lp_tx_queue_len(self): return len(self._pending[OMCI_CC.LOW_PRIORITY]) @property def max_hp_tx_queue(self): return self._max_hp_tx_queue @property def max_lp_tx_queue(self): return self._max_lp_tx_queue @inlineCallbacks def _start(self): """ Start the OMCI Communications Channel """ assert self._enabled, 'Start should only be called if enabled' self.flush() self._device = yield self._core_proxy.get_device(self._device_id) self._proxy_address = self._device.proxy_address def _stop(self): """ Stop the OMCI Communications Channel """ assert not self._enabled, 'Stop should only be called if disabled' self.flush() self._proxy_address = None def _receive_onu_message(self, rx_frame): """ Autonomously generated ONU frame Rx handler""" self.log.debug('rx-onu-frame', frame_type=type(rx_frame)) msg_type = rx_frame.fields['message_type'] self._rx_onu_frames += 1 msg = {TX_REQUEST_KEY: None, RX_RESPONSE_KEY: rx_frame} if msg_type == EntityOperations.AlarmNotification.value: topic = OMCI_CC.event_bus_topic(self._device_id, RxEvent.Alarm_Notification) self.reactor.callLater(0, self.event_bus.publish, topic, msg) elif msg_type == EntityOperations.AttributeValueChange.value: topic = OMCI_CC.event_bus_topic(self._device_id, RxEvent.AVC_Notification) self.reactor.callLater(0, self.event_bus.publish, topic, msg) elif msg_type == EntityOperations.TestResult.value: topic = OMCI_CC.event_bus_topic(self._device_id, RxEvent.Test_Result) self.reactor.callLater(0, self.event_bus.publish, topic, msg) else: self.log.warn('onu-unsupported-autonomous-message', type=msg_type) self._rx_onu_discards += 1 def _update_rx_tx_stats(self, now, ts): ts_diff = now - arrow.Arrow.utcfromtimestamp(ts) secs = ts_diff.total_seconds() self._reply_sum += secs if secs < self._reply_min: self._reply_min = secs if secs > self._reply_max: self._reply_max = secs return secs def receive_message(self, msg): """ Receive and OMCI message from the proxy channel to the OLT. Call this from your ONU Adapter on a new OMCI Rx on the proxy channel :param msg: (str) OMCI binary message (used as input to Scapy packet decoder) """ if not self.enabled: return try: now = arrow.utcnow() d = None # NOTE: Since we may need to do an independent ME map on a per-ONU basis # save the current value of the entity_id_to_class_map, then # replace it with our custom one before decode, and then finally # restore it later. Tried other ways but really made the code messy. saved_me_map = omci_entities.entity_id_to_class_map omci_entities.entity_id_to_class_map = self._me_map try: rx_frame = msg if isinstance(msg, OmciFrame) else OmciFrame(msg) except KeyError as e: # Unknown, Unsupported, or vendor-specific ME. Key is the unknown classID self.log.debug('frame-decode-key-error', msg=hexlify(msg), e=e) rx_frame = self._decode_unknown_me(msg) self._rx_unknown_me += 1 except Exception as e: self.log.exception('frame-decode', msg=hexlify(msg), e=e) return finally: omci_entities.entity_id_to_class_map = saved_me_map # Always restore it. rx_tid = rx_frame.fields['transaction_id'] if rx_tid == 0: return self._receive_onu_message(rx_frame) # Previously unreachable if this is the very first round-trip Rx or we # have been running consecutive errors if self._rx_frames == 0 or self._consecutive_errors != 0: self.reactor.callLater(0, self._publish_connectivity_event, True) self._rx_frames += 1 self._consecutive_errors = 0 try: high_priority = self._tid_is_high_priority(rx_tid) index = self._get_priority_index(high_priority) # (timestamp, defer, frame, timeout, retry, delayedCall) last_tx_tuple = self._tx_request[index] if last_tx_tuple is None or \ last_tx_tuple[OMCI_CC.REQUEST_FRAME].fields.get('transaction_id') != rx_tid: # Possible late Rx on a message that timed-out self._rx_unknown_tid += 1 self._rx_late += 1 return ts, d, tx_frame, timeout, retry, dc = last_tx_tuple if dc is not None and not dc.cancelled and not dc.called: dc.cancel() _secs = self._update_rx_tx_stats(now, ts) # Late arrival already serviced by a timeout? if d.called: self._rx_late += 1 return except Exception as e: self.log.exception('frame-match', msg=hexlify(msg), e=e) if d is not None: return d.errback(failure.Failure(e)) return # Publish Rx event to listeners in a different task reactor.callLater(0, self._publish_rx_frame, tx_frame, rx_frame) # begin success callback chain (will cancel timeout and queue next Tx message) self._rx_response[index] = rx_frame d.callback(rx_frame) except Exception as e: self.log.exception('rx-msg', e=e) def _decode_unknown_me(self, msg): """ Decode an ME for an unsupported class ID. This should only occur for a subset of message types (Get, Set, MIB Upload Next, ...) and they should only be responses as well. There are some times below that are commented out. For VOLTHA 2.0, it is expected that any get, set, create, delete for unique (often vendor) MEs will be coded by the ONU utilizing it and supplied to OpenOMCI as a vendor-specific ME during device initialization. :param msg: (str) Binary data :return: (OmciFrame) resulting frame """ from struct import unpack (tid, msg_type, framing) = unpack('!HBB', msg[0:4]) assert framing == 0xa, 'Only basic OMCI framing supported at this time' msg = msg[4:] # TODO: Commented out items below are future work (not expected for VOLTHA v2.0) (msg_class, kwargs) = { # OmciCreateResponse.message_id: (OmciCreateResponse, None), # OmciDeleteResponse.message_id: (OmciDeleteResponse, None), # OmciSetResponse.message_id: (OmciSetResponse, None), # OmciGetResponse.message_id: (OmciGetResponse, None), # OmciGetAllAlarmsNextResponse.message_id: (OmciGetAllAlarmsNextResponse, None), OmciMibUploadNextResponse.message_id: (OmciMibUploadNextResponse, { 'entity_class': unpack('!H', msg[0:2])[0], 'entity_id': unpack('!H', msg[2:4])[0], 'object_entity_class': unpack('!H', msg[4:6])[0], 'object_entity_id': unpack('!H', msg[6:8])[0], 'object_attributes_mask': unpack('!H', msg[8:10])[0], 'object_data': { UNKNOWN_CLASS_ATTRIBUTE_KEY: hexlify(msg[10:-4]) }, }), # OmciAlarmNotification.message_id: (OmciAlarmNotification, None), OmciAttributeValueChange.message_id: (OmciAttributeValueChange, { 'entity_class': unpack('!H', msg[0:2])[0], 'entity_id': unpack('!H', msg[2:4])[0], 'data': { UNKNOWN_CLASS_ATTRIBUTE_KEY: hexlify(msg[4:-8]) }, }), # OmciTestResult.message_id: (OmciTestResult, None), }.get(msg_type, None) if msg_class is None: raise TypeError('Unsupport Message Type for Unknown Decode: {}', msg_type) return OmciFrame(transaction_id=tid, message_type=msg_type, omci_message=msg_class(**kwargs)) def _publish_rx_frame(self, tx_frame, rx_frame): """ Notify listeners of successful response frame :param tx_frame: (OmciFrame) Original request frame :param rx_frame: (OmciFrame) Response frame """ if self._enabled and isinstance(rx_frame, OmciFrame): frame_type = rx_frame.fields['omci_message'].message_id event_type = OMCI_CC._frame_to_event_type.get(frame_type) if event_type is not None: topic = OMCI_CC.event_bus_topic(self._device_id, event_type) msg = {TX_REQUEST_KEY: tx_frame, RX_RESPONSE_KEY: rx_frame} self.event_bus.publish(topic=topic, msg=msg) def _publish_connectivity_event(self, connected): """ Notify listeners of Rx/Tx connectivity over OMCI :param connected: (bool) True if connectivity transitioned from unreachable to reachable """ if self._enabled: topic = OMCI_CC.event_bus_topic(self._device_id, RxEvent.Connectivity) msg = {CONNECTED_KEY: connected} self.event_bus.publish(topic=topic, msg=msg) def flush(self): """Flush/cancel in active or pending Tx requests""" requests = [] for priority in {OMCI_CC.HIGH_PRIORITY, OMCI_CC.LOW_PRIORITY}: next_frame, self._tx_request[priority] = self._tx_request[ priority], None if next_frame is not None: requests.append((next_frame[OMCI_CC.REQUEST_DEFERRED], next_frame[OMCI_CC.REQUEST_DELAYED_CALL])) requests += [(next_frame[OMCI_CC.PENDING_DEFERRED], None) for next_frame in self._pending[priority]] self._pending[priority] = list() # Cancel them... def cleanup_unhandled_error(_): pass # So the cancel below does not flag an unhandled error for d, dc in requests: if d is not None and not d.called: d.addErrback(cleanup_unhandled_error) d.cancel() if dc is not None and not dc.called and not dc.cancelled: dc.cancel() def _get_tx_tid(self, high_priority=False): """ Get the next Transaction ID for a tx. Note TID=0 is reserved for autonomously generated messages from an ONU :return: (int) TID """ if self._extended_messaging or not high_priority: index = OMCI_CC.LOW_PRIORITY min_tid = OMCI_CC.MIN_OMCI_TX_ID_LOW_PRIORITY max_tid = OMCI_CC.MAX_OMCI_TX_ID_LOW_PRIORITY else: index = OMCI_CC.HIGH_PRIORITY min_tid = OMCI_CC.MIN_OMCI_TX_ID_HIGH_PRIORITY max_tid = OMCI_CC.MAX_OMCI_TX_ID_HIGH_PRIORITY tx_tid, self._tx_tid[index] = self._tx_tid[ index], self._tx_tid[index] + 1 if self._tx_tid[index] > max_tid: self._tx_tid[index] = min_tid return tx_tid def _request_failure(self, value, tx_tid, high_priority): """ Handle a transmit failure. Rx Timeouts are handled on the 'dc' deferred and will call a different method that may retry if requested. This routine will be called after the final (if any) timeout or other error :param value: (Failure) Twisted failure :param tx_tid: (int) Associated Tx TID """ index = self._get_priority_index(high_priority) if self._tx_request[index] is not None: tx_frame = self._tx_request[index][OMCI_CC.REQUEST_FRAME] tx_frame_tid = tx_frame.fields['transaction_id'] if tx_frame_tid == tx_tid: timeout = self._tx_request[index][OMCI_CC.REQUEST_TIMEOUT] dc = self._tx_request[index][OMCI_CC.REQUEST_DELAYED_CALL] self._tx_request[index] = None if dc is not None and not dc.called and not dc.cancelled: dc.cancel() if isinstance(value, failure.Failure): value.trap(CancelledError) self._rx_timeouts += 1 self._consecutive_errors += 1 if self._consecutive_errors == 1: reactor.callLater(0, self._publish_connectivity_event, False) self.log.debug('timeout', tx_id=tx_tid, timeout=timeout) value = failure.Failure(TimeoutError(timeout, "Deferred")) else: # Search pending queue. This may be a cancel coming in from the original # task that requested the Tx. If found, remove # from pending queue for index, request in enumerate(self._pending[index]): req = request.get(OMCI_CC.PENDING_DEFERRED) if req is not None and req.fields[ 'transaction_id'] == tx_tid: self._pending[index].pop(index) break self._send_next_request(high_priority) return value def _request_success(self, rx_frame, high_priority): """ Handle transmit success (a matching Rx was received) :param rx_frame: (OmciFrame) OMCI response frame with matching TID :return: (OmciFrame) OMCI response frame with matching TID """ index = self._get_priority_index(high_priority) if rx_frame is None: rx_frame = self._rx_response[index] rx_tid = rx_frame.fields.get('transaction_id') if rx_tid is not None: if self._tx_request[index] is not None: tx_frame = self._tx_request[index][OMCI_CC.REQUEST_FRAME] tx_tid = tx_frame.fields['transaction_id'] if rx_tid == tx_tid: # Remove this request. Next callback in chain initiates next Tx self._tx_request[index] = None else: self._rx_late += 1 else: self._rx_late += 1 self._send_next_request(high_priority) # Return rx_frame (to next item in callback list) return rx_frame def _request_timeout(self, tx_tid, high_priority): """ Tx Request timed out. Resend immediately if there retries is non-zero. A separate deferred (dc) is used on each actual Tx which is not the deferred (d) that is returned to the caller of the 'send()' method. If the timeout if the transmitted frame was zero, this is just cleanup of that transmit request and not necessarily a transmit timeout :param tx_tid: (int) TID of frame :param high_priority: (bool) True if high-priority queue """ self.log.debug("_request_timeout", tx_tid=tx_tid) index = self._get_priority_index(high_priority) if self._tx_request[index] is not None: # (0: timestamp, 1: defer, 2: frame, 3: timeout, 4: retry, 5: delayedCall) ts, d, frame, timeout, retry, _dc = self._tx_request[index] if frame.fields.get('transaction_id', 0) == tx_tid: self._tx_request[index] = None if timeout > 0: self._rx_timeouts += 1 if retry > 0: # Push on front of TX pending queue so that it transmits next with the # original TID self._queue_frame(d, frame, timeout, retry - 1, high_priority, front=True) elif not d.called: d.errback( failure.Failure( TimeoutError( timeout, "Send OMCI TID -{}".format(tx_tid)))) else: self.log.warn('timeout-but-not-the-tx-frame' ) # Statement mainly for debugging self._send_next_request(high_priority) def _queue_frame(self, d, frame, timeout, retry, high_priority, front=False): index = self._get_priority_index(high_priority) tx_tuple = (d, frame, timeout, retry ) # Pending -> (deferred, tx_frame, timeout, retry) if front: self._pending[index].insert(0, tuple) else: self._pending[index].append(tx_tuple) # Monitor queue stats qlen = len(self._pending[index]) if high_priority: if self._max_hp_tx_queue < qlen: self._max_hp_tx_queue = qlen elif self._max_lp_tx_queue < qlen: self._max_lp_tx_queue = qlen self.log.debug("queue-size", index=index, pending_qlen=qlen) def send(self, frame, timeout=DEFAULT_OMCI_TIMEOUT, retry=0, high_priority=False): """ Queue the OMCI Frame for a transmit to the ONU via the proxy_channel :param frame: (OMCIFrame) Message to send :param timeout: (int) Rx Timeout. 0=No response needed :param retry: (int) Additional retry attempts on channel failure, default=0 :param high_priority: (bool) High Priority requests :return: (deferred) A deferred that fires when the response frame is received or if an error/timeout occurs """ if not self.enabled or self._proxy_address is None: # TODO custom exceptions throughout this code would be helpful self._tx_errors += 1 return fail( result=failure.Failure(Exception('OMCI is not enabled'))) timeout = float(timeout) if timeout > float(MAX_OMCI_REQUEST_AGE): self._tx_errors += 1 msg = 'Maximum timeout is {} seconds'.format(MAX_OMCI_REQUEST_AGE) return fail(result=failure.Failure(Exception(msg))) if not isinstance(frame, OmciFrame): self._tx_errors += 1 msg = "Invalid frame class '{}'".format(type(frame)) return fail(result=failure.Failure(Exception(msg))) try: index = self._get_priority_index(high_priority) tx_tid = frame.fields['transaction_id'] if tx_tid is None: tx_tid = self._get_tx_tid(high_priority=high_priority) frame.fields['transaction_id'] = tx_tid assert tx_tid not in self._pending[ index], 'TX TID {} is already exists'.format(tx_tid) assert tx_tid > 0, 'Invalid Tx TID: {}'.format(tx_tid) # Queue it and request next Tx if tx channel is free d = defer.Deferred() self._queue_frame(d, frame, timeout, retry, high_priority, front=False) self._send_next_request(high_priority) if timeout == 0: self.log.debug("send-timeout-zero", tx_tid=tx_tid) self.reactor.callLater(0, d.callback, 'queued') return d except Exception as e: self._tx_errors += 1 self._consecutive_errors += 1 if self._consecutive_errors == 1: self.reactor.callLater(0, self._publish_connectivity_event, False) self.log.exception('send-omci', e=e) return fail(result=failure.Failure(e)) def _ok_to_send(self, tx_request, high_priority): """ G.988 specifies not to issue a MIB upload or a Software download request when a similar action is in progress on the other channel. To keep the logic here simple, a new upload/download will not be allowed if either a upload/download is going on :param tx_request (OmciFrame) Frame to send :param high_priority: (bool) for queue selection :return: True if okay to dequeue and send frame """ other = self._get_priority_index(not high_priority) if self._tx_request[other] is None: return True this_msg_type = tx_request.fields['message_type'] & 0x1f not_allowed = { OP.MibUpload.value, OP.MibUploadNext.value, OP.StartSoftwareDownload.value, OP.DownloadSection.value, OP.EndSoftwareDownload.value } if this_msg_type not in not_allowed: return True other_msg_type = self._tx_request[other][ OMCI_CC.REQUEST_FRAME].fields['message_type'] & 0x1f return other_msg_type not in not_allowed @inlineCallbacks def _send_next_request(self, high_priority): """ Pull next tx request and send it :param high_priority: (bool) True if this was a high priority request :return: results, so callback chain continues if needed """ index = self._get_priority_index(high_priority) if self._tx_request[ index] is None: # TODO or self._tx_request[index][OMCI_CC.REQUEST_DEFERRED].called: d = None try: if len(self._pending[index]) and \ not self._ok_to_send(self._pending[index][0][OMCI_CC.PENDING_FRAME], high_priority): reactor.callLater(0.05, self._send_next_request, high_priority) return next_frame = self._pending[index].pop(0) d = next_frame[OMCI_CC.PENDING_DEFERRED] frame = next_frame[OMCI_CC.PENDING_FRAME] timeout = next_frame[OMCI_CC.PENDING_TIMEOUT] retry = next_frame[OMCI_CC.PENDING_RETRY] tx_tid = frame.fields['transaction_id'] # NOTE: Since we may need to do an independent ME map on a per-ONU basis # save the current value of the entity_id_to_class_map, then # replace it with our custom one before decode, and then finally # restore it later. Tried other ways but really made the code messy. saved_me_map = omci_entities.entity_id_to_class_map omci_entities.entity_id_to_class_map = self._me_map ts = arrow.utcnow().float_timestamp try: self._rx_response[index] = None omci_msg = InterAdapterOmciMessage( message=hexify(str(frame))) self.log.debug('inter-adapter-send-omci', omci_msg=omci_msg) yield self._adapter_proxy.send_inter_adapter_message( msg=omci_msg, type=InterAdapterMessageType.OMCI_REQUEST, from_adapter=self._device.type, to_adapter=self._proxy_address.device_type, to_device_id=self._device_id, proxy_device_id=self._proxy_address.device_id) finally: omci_entities.entity_id_to_class_map = saved_me_map self._tx_frames += 1 # Note: the 'd' deferred in the queued request we just got will # already have its success callback queued (callLater -> 0) with a # result of "queued". Here we need time it out internally so # we can call cleanup appropriately. G.988 mentions that most ONUs # will process an request in < 1 second. dc_timeout = timeout if timeout > 0 else 1.0 # Timeout on internal deferred to support internal retries if requested dc = self.reactor.callLater(dc_timeout, self._request_timeout, tx_tid, high_priority) # (timestamp, defer, frame, timeout, retry, delayedCall) self._tx_request[index] = (ts, d, frame, timeout, retry, dc) if timeout > 0: d.addCallbacks(self._request_success, self._request_failure, callbackArgs=(high_priority, ), errbackArgs=(tx_tid, high_priority)) except IndexError: pass # Nothing pending in this queue except Exception as e: self.log.exception('send-proxy-exception', e=e) self._tx_request[index] = None self.reactor.callLater(0, self._send_next_request, high_priority) if d is not None: d.errback(failure.Failure(e)) else: self.log.debug("tx-request-occupied", index=index) ################################################################################### # MIB Action shortcuts def send_mib_reset(self, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): """ Perform a MIB Reset """ self.log.debug('send-mib-reset') frame = OntDataFrame().mib_reset() return self.send(frame, timeout=timeout, high_priority=high_priority) def send_mib_upload(self, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): self.log.debug('send-mib-upload') frame = OntDataFrame().mib_upload() return self.send(frame, timeout=timeout, high_priority=high_priority) def send_mib_upload_next(self, seq_no, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): self.log.debug('send-mib-upload-next') frame = OntDataFrame(sequence_number=seq_no).mib_upload_next() return self.send(frame, timeout=timeout, high_priority=high_priority) def send_reboot(self, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): """ Send an ONU Device reboot request (ONU-G ME). NOTICE: This method is being deprecated and replaced with a tasks to preform this function """ self.log.debug('send-mib-reboot') frame = OntGFrame().reboot() return self.send(frame, timeout=timeout, high_priority=high_priority) def send_get_all_alarm(self, alarm_retrieval_mode=0, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): self.log.debug('send_get_alarm') frame = OntDataFrame().get_all_alarm(alarm_retrieval_mode) return self.send(frame, timeout=timeout, high_priority=high_priority) def send_get_all_alarm_next(self, seq_no, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): self.log.debug('send_get_alarm_next') frame = OntDataFrame().get_all_alarm_next(seq_no) return self.send(frame, timeout=timeout, high_priority=high_priority) def send_start_software_download(self, image_inst_id, image_size, window_size, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): frame = SoftwareImageFrame(image_inst_id).start_software_download( image_size, window_size - 1) return self.send(frame, timeout, 3, high_priority=high_priority) def send_download_section(self, image_inst_id, section_num, data, size=DEFAULT_OMCI_DOWNLOAD_SECTION_SIZE, timeout=0, high_priority=False): """ # timeout=0 indicates no repons needed """ # self.log.debug("send_download_section", instance_id=image_inst_id, section=section_num, timeout=timeout) if timeout > 0: frame = SoftwareImageFrame(image_inst_id).download_section( True, section_num, data) else: frame = SoftwareImageFrame(image_inst_id).download_section( False, section_num, data) return self.send(frame, timeout, high_priority=high_priority) # if timeout > 0: # self.reactor.callLater(0, self.sim_receive_download_section_resp, # frame.fields["transaction_id"], # frame.fields["omci_message"].fields["section_number"]) # return d def send_end_software_download(self, image_inst_id, crc32, image_size, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): frame = SoftwareImageFrame(image_inst_id).end_software_download( crc32, image_size) return self.send(frame, timeout, high_priority=high_priority) # self.reactor.callLater(0, self.sim_receive_end_software_download_resp, frame.fields["transaction_id"]) # return d def send_active_image(self, image_inst_id, flag=0, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): frame = SoftwareImageFrame(image_inst_id).activate_image(flag) return self.send(frame, timeout, high_priority=high_priority) def send_commit_image(self, image_inst_id, timeout=DEFAULT_OMCI_TIMEOUT, high_priority=False): frame = SoftwareImageFrame(image_inst_id).commit_image() return self.send(frame, timeout, high_priority=high_priority)
class OnuDeviceEntry(object): """ An ONU Device entry in the MIB """ def __init__(self, omci_agent, device_id, core_proxy, adapter_proxy, custom_me_map, mib_db, alarm_db, support_classes, clock=None): """ Class initializer :param omci_agent: (OpenOMCIAgent) Reference to OpenOMCI Agent :param device_id: (str) ONU Device ID :param core_proxy: (CoreProxy) Remote API to VOLTHA Core :param adapter_proxy: (AdapterProxy) Remote API to other Adapters via VOLTHA Core :param custom_me_map: (dict) Additional/updated ME to add to class map :param mib_db: (MibDbApi) MIB Database reference :param alarm_db: (MibDbApi) Alarm Table/Database reference :param support_classes: (dict) State machines and tasks for this ONU """ self.log = structlog.get_logger(device_id=device_id) self._started = False self._omci_agent = omci_agent # OMCI AdapterAgent self._device_id = device_id # ONU Device ID self._core_proxy = core_proxy self._adapter_proxy = adapter_proxy self._runner = TaskRunner(device_id, clock=clock) # OMCI_CC Task runner self._deferred = None # self._img_download_deferred = None # deferred of image file download from server self._omci_upgrade_deferred = None # deferred of ONU OMCI upgrading procedure self._omci_activate_deferred = None # deferred of ONU OMCI Softwre Image Activate self._img_deferred = None # deferred returned to caller of do_onu_software_download self._first_in_sync = False self._first_capabilities = False self._timestamp = None # self._image_download = None # (voltha_pb2.ImageDownload) self.reactor = clock if clock is not None else reactor # OMCI related databases are on a per-agent basis. State machines and tasks # are per ONU Vendor # self._support_classes = support_classes self._configuration = None try: # MIB Synchronization state machine self._mib_db_in_sync = False mib_synchronizer_info = support_classes.get('mib-synchronizer') advertise = mib_synchronizer_info['advertise-events'] self._mib_sync_sm = mib_synchronizer_info['state-machine']( self._omci_agent, device_id, mib_synchronizer_info['tasks'], mib_db, advertise_events=advertise) # ONU OMCI Capabilities state machine capabilities_info = support_classes.get('omci-capabilities') advertise = capabilities_info['advertise-events'] self._capabilities_sm = capabilities_info['state-machine']( self._omci_agent, device_id, capabilities_info['tasks'], advertise_events=advertise) # ONU Performance Monitoring Intervals state machine interval_info = support_classes.get('performance-intervals') advertise = interval_info['advertise-events'] self._pm_intervals_sm = interval_info['state-machine']( self._omci_agent, device_id, interval_info['tasks'], advertise_events=advertise) # ONU ALARM Synchronization state machine self._alarm_db_in_sync = False alarm_synchronizer_info = support_classes.get('alarm-synchronizer') advertise = alarm_synchronizer_info['advertise-events'] self._alarm_sync_sm = alarm_synchronizer_info['state-machine']( self._omci_agent, device_id, alarm_synchronizer_info['tasks'], alarm_db, advertise_events=advertise) # State machine of downloading image file from server downloader_info = support_classes.get('image_downloader') image_upgrader_info = support_classes.get('image_upgrader') # image_activate_info = support_classes.get('image_activator') advertise = downloader_info['advertise-event'] # self._img_download_sm = downloader_info['state-machine'](self._omci_agent, device_id, # downloader_info['tasks'], # advertise_events=advertise) self._image_agent = ImageAgent( self._omci_agent, device_id, downloader_info['state-machine'], downloader_info['tasks'], image_upgrader_info['state-machine'], image_upgrader_info['tasks'], # image_activate_info['state-machine'], advertise_events=advertise, clock=clock) # self._omci_upgrade_sm = image_upgrader_info['state-machine'](device_id, advertise_events=advertise) except Exception as e: self.log.exception('state-machine-create-failed', e=e) raise # Put state machines in the order you wish to start them self._state_machines = [] self._on_start_state_machines = [ # Run when 'start()' called self._mib_sync_sm, self._capabilities_sm, ] self._on_sync_state_machines = [ # Run after first in_sync event self._alarm_sync_sm, ] self._on_capabilities_state_machines = [ # Run after first capabilities events self._pm_intervals_sm ] self._custom_me_map = custom_me_map self._me_map = omci_entities.entity_id_to_class_map.copy() if custom_me_map is not None: self._me_map.update(custom_me_map) self.event_bus = EventBusClient() # Create OMCI communications channel self._omci_cc = OMCI_CC(core_proxy, adapter_proxy, self.device_id, self._me_map, clock=clock) @staticmethod def event_bus_topic(device_id, event): """ Get the topic name for a given event for this ONU Device :param device_id: (str) ONU Device ID :param event: (OnuDeviceEvents) Type of event :return: (str) Topic string """ assert event in OnuDeviceEvents, \ 'Event {} is not an ONU Device Event'.format(event.name) return 'omci-device:{}:{}'.format(device_id, event.name) @property def device_id(self): return self._device_id @property def omci_cc(self): return self._omci_cc @property def core_proxy(self): return self._core_proxy @property def task_runner(self): return self._runner @property def mib_synchronizer(self): """ Reference to the OpenOMCI MIB Synchronization state machine for this ONU """ return self._mib_sync_sm @property def omci_capabilities(self): """ Reference to the OpenOMCI OMCI Capabilities state machine for this ONU """ return self._capabilities_sm @property def pm_intervals_state_machine(self): """ Reference to the OpenOMCI PM Intervals state machine for this ONU """ return self._pm_intervals_sm def set_pm_config(self, pm_config): """ Set PM interval configuration :param pm_config: (OnuPmIntervalMetrics) PM Interval configuration """ self._pm_intervals_sm.set_pm_config(pm_config) @property def timestamp(self): """Pollable Metrics last collected timestamp""" return self._timestamp @timestamp.setter def timestamp(self, value): self._timestamp = value @property def alarm_synchronizer(self): """ Reference to the OpenOMCI Alarm Synchronization state machine for this ONU """ return self._alarm_sync_sm @property def active(self): """ Is the ONU device currently active/running """ return self._started @property def custom_me_map(self): """ Vendor-specific Managed Entity Map for this vendor's device""" return self._custom_me_map @property def me_map(self): """ Combined ME and Vendor-specific Managed Entity Map for this device""" return self._me_map def _cancel_deferred(self): d, self._deferred = self._deferred, None try: if d is not None and not d.called: d.cancel() except: pass @property def mib_db_in_sync(self): return self._mib_db_in_sync @mib_db_in_sync.setter def mib_db_in_sync(self, value): if self._mib_db_in_sync != value: # Save value self._mib_db_in_sync = value # Start up other state machines if needed if self._first_in_sync: self.first_in_sync_event() # Notify any event listeners topic = OnuDeviceEntry.event_bus_topic( self.device_id, OnuDeviceEvents.MibDatabaseSyncEvent) msg = { IN_SYNC_KEY: self._mib_db_in_sync, LAST_IN_SYNC_KEY: self.mib_synchronizer.last_mib_db_sync } self.event_bus.publish(topic=topic, msg=msg) @property def alarm_db_in_sync(self): return self._alarm_db_in_sync @alarm_db_in_sync.setter def alarm_db_in_sync(self, value): if self._alarm_db_in_sync != value: # Save value self._alarm_db_in_sync = value # Start up other state machines if needed if self._first_in_sync: self.first_in_sync_event() # Notify any event listeners topic = OnuDeviceEntry.event_bus_topic( self.device_id, OnuDeviceEvents.AlarmDatabaseSyncEvent) msg = {IN_SYNC_KEY: self._alarm_db_in_sync} self.event_bus.publish(topic=topic, msg=msg) @property def configuration(self): """ Get the OMCI Configuration object for this ONU. This is a class that provides some common database access functions for ONU capabilities and read-only configuration values. :return: (OnuConfiguration) """ return self._configuration @property def image_agent(self): return self._image_agent # @property # def image_download(self): # return self._image_download def start(self, device): """ Start the ONU Device Entry state machines """ self.log.debug('OnuDeviceEntry.start', previous=self._started) if self._started: return self._started = True self.omci_cc._device = device self.omci_cc._proxy_address = device.proxy_address self._omci_cc.enabled = True self._first_in_sync = True self._first_capabilities = True self._runner.start() self._configuration = OnuConfiguration(self._omci_agent, self._device_id) # Start MIB Sync and other state machines that can run before the first # MIB Synchronization event occurs. Start 'later' so that any # ONU Device, OMCI DB, OMCI Agent, and others are fully started before # performing the start. self._state_machines = [] def start_state_machines(machines): for sm in machines: self._state_machines.append(sm) sm.start() self._deferred = reactor.callLater(0, start_state_machines, self._on_start_state_machines) # Notify any event listeners self._publish_device_status_event() def stop(self): """ Stop the ONU Device Entry state machines """ if not self._started: return self._started = False self._cancel_deferred() self._omci_cc.enabled = False # Halt MIB Sync and other state machines for sm in self._state_machines: sm.stop() self._state_machines = [] # Stop task runner self._runner.stop() # Notify any event listeners self._publish_device_status_event() def first_in_sync_event(self): """ This event is called on the first MIB synchronization event after OpenOMCI has been started. It is responsible for starting any other state machine and to initiate an ONU Capabilities report """ if self._first_in_sync: self._first_in_sync = False # Start up the ONU Capabilities task self._configuration.reset() # Insure that the ONU-G Administrative lock is disabled def failure(reason): self.log.error('disable-admin-state-lock', reason=reason) frame = OntGFrame(attributes={'administrative_state': 0}).set() task = OmciModifyRequest(self._omci_agent, self.device_id, frame) self.task_runner.queue_task(task).addErrback(failure) # Start up any other remaining OpenOMCI state machines def start_state_machines(machines): for sm in machines: self._state_machines.append(sm) reactor.callLater(0, sm.start) self._deferred = reactor.callLater(0, start_state_machines, self._on_sync_state_machines) # if an ongoing upgrading is not accomplished, restart it if self._img_deferred is not None: self._image_agent.onu_bootup() def first_in_capabilities_event(self): """ This event is called on the first capabilities event after OpenOMCI has been started. It is responsible for starting any other state machine. These are often state machines that have tasks that are dependent upon knowing if various MEs are supported """ if self._first_capabilities: self._first_capabilities = False # Start up any other remaining OpenOMCI state machines def start_state_machines(machines): for sm in machines: self._state_machines.append(sm) reactor.callLater(0, sm.start) self._deferred = reactor.callLater( 0, start_state_machines, self._on_capabilities_state_machines) # def __on_omci_download_success(self, image_download): # self.log.debug("__on_omci_download_success", image=image_download) # self._omci_upgrade_deferred = None # # self._ret_deferred = None # self._omci_activate_deferred = self._image_agent.activate_onu_image(image_download.name) # self._omci_activate_deferred.addCallbacks(self.__on_omci_image_activate_success, # self.__on_omci_image_activate_fail, errbackArgs=(image_name,)) # return image_name # def __on_omci_download_fail(self, fail, image_name): # self.log.debug("__on_omci_download_fail", failure=fail, image_name=image_name) # self.reactor.callLater(0, self._img_deferred.errback, fail) # self._omci_upgrade_deferred = None # self._img_deferred = None def __on_omci_image_activate_success(self, image_name): self.log.debug("__on_omci_image_activate_success", image_name=image_name) self._omci_activate_deferred = None self._img_deferred.callback(image_name) self._img_deferred = None return image_name def __on_omci_image_activate_fail(self, fail, image_name): self.log.debug("__on_omci_image_activate_fail", faile=fail, image_name=image_name) self._omci_activate_deferred = None self._img_deferred.errback(fail) self._img_deferred = None def _publish_device_status_event(self): """ Publish the ONU Device start/start status. """ topic = OnuDeviceEntry.event_bus_topic( self.device_id, OnuDeviceEvents.DeviceStatusEvent) msg = {ACTIVE_KEY: self._started} self.event_bus.publish(topic=topic, msg=msg) def publish_omci_capabilities_event(self): """ Publish the ONU Device start/start status. """ if self.first_in_capabilities_event: self.first_in_capabilities_event() topic = OnuDeviceEntry.event_bus_topic( self.device_id, OnuDeviceEvents.OmciCapabilitiesEvent) msg = { SUPPORTED_MESSAGE_ENTITY_KEY: self.omci_capabilities.supported_managed_entities, SUPPORTED_MESSAGE_TYPES_KEY: self.omci_capabilities.supported_message_types } self.event_bus.publish(topic=topic, msg=msg) def delete(self): """ Stop the ONU Device's state machine and remove the ONU, and any related OMCI state information from the OpenOMCI Framework """ self.stop() self.mib_synchronizer.delete() # OpenOMCI cleanup if self._omci_agent is not None: self._omci_agent.remove_device(self._device_id, cleanup=True) def query_mib(self, class_id=None, instance_id=None, attributes=None): """ Get MIB database information. This method can be used to request information from the database to the detailed level requested :param class_id: (int) Managed Entity class ID :param instance_id: (int) Managed Entity instance :param attributes: (list or str) Managed Entity instance's attributes :return: (dict) The value(s) requested. If class/inst/attribute is not found, an empty dictionary is returned :raises DatabaseStateError: If the database is not enabled """ self.log.debug('query', class_id=class_id, instance_id=instance_id, attributes=attributes) return self.mib_synchronizer.query_mib(class_id=class_id, instance_id=instance_id, attributes=attributes) def query_mib_single_attribute(self, class_id, instance_id, attribute): """ Get MIB database information for a single specific attribute This method can be used to request information from the database to the detailed level requested :param class_id: (int) Managed Entity class ID :param instance_id: (int) Managed Entity instance :param attribute: (str) Managed Entity instance's attribute :return: (varies) The value requested. If class/inst/attribute is not found, None is returned :raises DatabaseStateError: If the database is not enabled """ self.log.debug('query-single', class_id=class_id, instance_id=instance_id, attributes=attribute) assert isinstance(attribute, basestring), \ 'Only a single attribute value can be retrieved' entry = self.mib_synchronizer.query_mib(class_id=class_id, instance_id=instance_id, attributes=attribute) return entry[attribute] if attribute in entry else None def query_alarm_table(self, class_id=None, instance_id=None): """ Get Alarm information This method can be used to request information from the alarm database to the detailed level requested :param class_id: (int) Managed Entity class ID :param instance_id: (int) Managed Entity instance :return: (dict) The value(s) requested. If class/inst/attribute is not found, an empty dictionary is returned :raises DatabaseStateError: If the database is not enabled """ self.log.debug('query', class_id=class_id, instance_id=instance_id) return self.alarm_synchronizer.query_mib(class_id=class_id, instance_id=instance_id) def reboot(self, flags=RebootFlags.Reboot_Unconditionally, timeout=OmciRebootRequest.DEFAULT_REBOOT_TIMEOUT): """ Request a reboot of the ONU :param flags: (RebootFlags) Reboot condition :param timeout: (int) Reboot task priority :return: (deferred) Fires upon completion or error """ assert self.active, 'This device is not active' return self.task_runner.queue_task( OmciRebootRequest(self._omci_agent, self.device_id, flags=flags, timeout=timeout)) # def get_imagefile(self, local_name, local_dir, remote_url=None): # """ # Return a Deferred that will be triggered if the file is locally available # or downloaded successfully # """ # self.log.info('start download from {}'.format(remote_url)) # # for debug purpose, start runner here to queue downloading task # # self._runner.start() # return self._image_agent.get_image(self._image_download) def do_onu_software_download(self, image_dnld): """ image_dnld: (ImageDownload) : Return a Deferred that will be triggered when upgrading results in success or failure """ self.log.debug('do_onu_software_download') image_download = deepcopy(image_dnld) # self._img_download_deferred = self._image_agent.get_image(self._image_download) # self._img_download_deferred.addCallbacks(self.__on_download_success, self.__on_download_fail, errbackArgs=(self._image_download,)) # self._ret_deferred = defer.Deferred() # return self._ret_deferred return self._image_agent.get_image(image_download) # def do_onu_software_switch(self): def do_onu_image_activate(self, image_dnld_name): """ Return a Deferred that will be triggered when switching software image results in success or failure """ if self._img_deferred is None: self.log.debug('do_onu_image_activate') self._img_deferred = defer.Deferred() self._omci_upgrade_deferred = self._image_agent.onu_omci_download( image_dnld_name) self._omci_upgrade_deferred.addCallbacks( self.__on_omci_image_activate_success, self.__on_omci_image_activate_fail, errbackArgs=(image_dnld_name, )) return self._img_deferred def cancel_onu_software_download(self, image_name): self.log.debug('cancel_onu_software_download') self._image_agent.cancel_download_image(image_name) self._image_agent.cancel_upgrade_onu() if self._img_deferred and not self._img_deferred.called: self._img_deferred.cancel() self._img_deferred = None # self._image_download = None def get_image_download_status(self, image_name): return self._image_agent.get_image_status(image_name)