| # |
| # Copyright 2018 the original author or authors. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| from task import Task |
| from twisted.internet import reactor |
| from twisted.internet.defer import failure, inlineCallbacks, TimeoutError, returnValue |
| from voltha.extensions.omci.omci_defs import ReasonCodes, EntityOperations |
| from voltha.extensions.omci.omci_me import MEFrame |
| from voltha.extensions.omci.omci_frame import OmciFrame |
| from voltha.extensions.omci.omci_cc import DEFAULT_OMCI_TIMEOUT |
| from voltha.extensions.omci.omci_messages import OmciGet |
| from voltha.extensions.omci.omci_fields import OmciTableField |
| |
| RC = ReasonCodes |
| OP = EntityOperations |
| |
| |
| class GetException(Exception): |
| pass |
| |
| |
| class OmciGetRequest(Task): |
| """ |
| OpenOMCI Get an OMCI ME Instance Attributes |
| |
| Upon completion, the Task deferred callback is invoked with a reference of |
| this Task object. |
| |
| The Task has an initializer option (allow_failure) that will retry all |
| requested attributes if the original request fails with a status code of |
| 9 (Attributes failed or unknown). This result means that an attribute |
| is not supported by the ONU or that a mandatory/optional attribute could |
| not be executed by the ONU, even if it is supported, for example, |
| because of a range or type violation. |
| """ |
| task_priority = 128 |
| name = "ONU OMCI Get Task" |
| |
| def __init__(self, omci_agent, device_id, entity_class, entity_id, attributes, |
| exclusive=False, allow_failure=False): |
| """ |
| Class initialization |
| |
| :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent |
| :param device_id: (str) ONU Device ID |
| :param entity_class: (EntityClass) ME Class to retrieve |
| :param entity_id: (int) ME Class instance ID to retrieve |
| :param attributes: (list or set) Name of attributes to retrieve |
| :param exclusive: (bool) True if this GET request Task exclusively own the |
| OMCI-CC while running. Default: False |
| :param allow_failure: (bool) If true, attempt to get all valid attributes |
| if the original request receives an error |
| code of 9 (Attributes failed or unknown). |
| """ |
| super(OmciGetRequest, self).__init__(OmciGetRequest.name, |
| omci_agent, |
| device_id, |
| priority=OmciGetRequest.task_priority, |
| exclusive=exclusive) |
| self._device = omci_agent.get_device(device_id) |
| self._entity_class = entity_class |
| self._entity_id = entity_id |
| self._attributes = attributes |
| self._allow_failure = allow_failure |
| self._failed_or_unknown_attributes = set() |
| self._results = None |
| self._local_deferred = None |
| |
| def cancel_deferred(self): |
| super(OmciGetRequest, self).cancel_deferred() |
| |
| d, self._local_deferred = self._local_deferred, None |
| try: |
| if d is not None and not d.called: |
| d.cancel() |
| except: |
| pass |
| |
| @property |
| def me_class(self): |
| """The OMCI Managed Entity Class associated with this request""" |
| return self._entity_class |
| |
| @property |
| def entity_id(self): |
| """The ME Entity ID associated with this request""" |
| return self._entity_id |
| |
| @property |
| def attributes(self): |
| """ |
| Return a dictionary of attributes for the request if the Get was |
| successfully completed. None otherwise |
| """ |
| if self._results is None: |
| return None |
| |
| omci_msg = self._results.fields['omci_message'].fields |
| return omci_msg['data'] if 'data' in omci_msg else None |
| |
| @property |
| def success_code(self): |
| """ |
| Return the OMCI success/reason code for the Get Response. |
| """ |
| if self._results is None: |
| return None |
| |
| return self._results.fields['omci_message'].fields['success_code'] |
| |
| @property |
| def raw_results(self): |
| """ |
| Return the raw Get Response OMCIFrame |
| """ |
| return self._results |
| |
| def start(self): |
| """ |
| Start MIB Capabilities task |
| """ |
| super(OmciGetRequest, self).start() |
| self._local_deferred = reactor.callLater(0, self.perform_get_omci) |
| |
| @property |
| def failed_or_unknown_attributes(self): |
| """ |
| Returns a set attributes that failed or unknown in the original get |
| request that resulted in an initial status code of 9 (Attributes |
| failed or unknown). |
| |
| :return: (set of str) attributes |
| """ |
| return self._failed_or_unknown_attributes |
| |
| @inlineCallbacks |
| def perform_get_omci(self): |
| """ |
| Perform the initial get request |
| """ |
| self.log.info('perform-get', entity_class=self._entity_class, |
| entity_id=self._entity_id, attributes=self._attributes) |
| try: |
| # If one or more attributes is a table attribute, get it separately |
| def is_table_attr(attr): |
| index = self._entity_class.attribute_name_to_index_map[attr] |
| attr_def = self._entity_class.attributes[index] |
| return isinstance(attr_def.field, OmciTableField) |
| |
| first_attributes = {attr for attr in self._attributes if not is_table_attr(attr)} |
| table_attributes = {attr for attr in self._attributes if is_table_attr(attr)} |
| |
| frame = MEFrame(self._entity_class, self._entity_id, first_attributes).get() |
| self.strobe_watchdog() |
| results = yield self._device.omci_cc.send(frame) |
| |
| status = results.fields['omci_message'].fields['success_code'] |
| self.log.debug('perform-get-status', status=status) |
| |
| # Success? |
| if status == RC.Success.value: |
| self._results = results |
| results_omci = results.fields['omci_message'].fields |
| |
| # Were all attributes fetched? |
| missing_attr = frame.fields['omci_message'].fields['attributes_mask'] ^ \ |
| results_omci['attributes_mask'] |
| |
| if missing_attr > 0 or len(table_attributes) > 0: |
| self.log.info('perform-get-missing', num_missing=missing_attr, |
| table_attr=table_attributes) |
| self.strobe_watchdog() |
| self._local_deferred = reactor.callLater(0, |
| self.perform_get_missing_attributes, |
| missing_attr, |
| table_attributes) |
| returnValue(self._local_deferred) |
| |
| elif status == RC.AttributeFailure.value: |
| # What failed? Note if only one attribute was attempted, then |
| # that is an overall failure |
| |
| if not self._allow_failure or len(self._attributes) <= 1: |
| raise GetException('Get failed with status code: {}'. |
| format(RC.AttributeFailure.value)) |
| |
| self.strobe_watchdog() |
| self._local_deferred = reactor.callLater(0, |
| self.perform_get_failed_attributes, |
| results, |
| self._attributes) |
| returnValue(self._local_deferred) |
| |
| else: |
| raise GetException('Get failed with status code: {}'.format(status)) |
| |
| self.log.debug('get-completed') |
| self.deferred.callback(self) |
| |
| except TimeoutError as e: |
| self.deferred.errback(failure.Failure(e)) |
| |
| except Exception as e: |
| self.log.exception('perform-get', e=e, class_id=self._entity_class, |
| entity_id=self._entity_id, attributes=self._attributes) |
| self.deferred.errback(failure.Failure(e)) |
| |
| @inlineCallbacks |
| def perform_get_missing_attributes(self, missing_attr, table_attributes): |
| """ |
| This method is called when the original Get requests completes with success |
| but not all attributes were returned. This can happen if one or more of the |
| attributes would have exceeded the space available in the OMCI frame. |
| |
| This routine iterates through the missing attributes and attempts to retrieve |
| the ones that were missing. |
| |
| :param missing_attr: (int) Missing attributes bitmask |
| :param table_attributes: (set) Attributes that need table get/get-next support |
| """ |
| self.log.debug('perform-get-missing', attrs=missing_attr, tbl=table_attributes) |
| |
| # Retrieve missing attributes first (if any) |
| results_omci = self._results.fields['omci_message'].fields |
| |
| for index in xrange(16): |
| attr_mask = 1 << index |
| |
| if attr_mask & missing_attr: |
| # Get this attribute |
| frame = OmciFrame( |
| transaction_id=None, # OMCI-CC will set |
| message_type=OmciGet.message_id, |
| omci_message=OmciGet( |
| entity_class=self._entity_class.class_id, |
| entity_id=self._entity_id, |
| attributes_mask=attr_mask |
| ) |
| ) |
| try: |
| self.strobe_watchdog() |
| get_results = yield self._device.omci_cc.send(frame) |
| |
| get_omci = get_results.fields['omci_message'].fields |
| if get_omci['success_code'] != RC.Success.value: |
| continue |
| |
| assert attr_mask == get_omci['attributes_mask'], 'wrong attribute' |
| results_omci['attributes_mask'] |= attr_mask |
| |
| if results_omci.get('data') is None: |
| results_omci['data'] = dict() |
| |
| results_omci['data'].update(get_omci['data']) |
| |
| except TimeoutError: |
| self.log.debug('missing-timeout') |
| |
| except Exception as e: |
| self.log.exception('missing-failure', e=e) |
| |
| # Now any table attributes. OMCI_CC handles background get/get-next sequencing |
| for tbl_attr in table_attributes: |
| attr_mask = self._entity_class.mask_for(tbl_attr) |
| frame = OmciFrame( |
| transaction_id=None, # OMCI-CC will set |
| message_type=OmciGet.message_id, |
| omci_message=OmciGet( |
| entity_class=self._entity_class.class_id, |
| entity_id=self._entity_id, |
| attributes_mask=attr_mask |
| ) |
| ) |
| try: |
| timeout = 2 * DEFAULT_OMCI_TIMEOUT # Multiple frames expected |
| self.strobe_watchdog() |
| get_results = yield self._device.omci_cc.send(frame, |
| timeout=timeout) |
| self.strobe_watchdog() |
| get_omci = get_results.fields['omci_message'].fields |
| if get_omci['success_code'] != RC.Success.value: |
| continue |
| |
| if results_omci.get('data') is None: |
| results_omci['data'] = dict() |
| |
| results_omci['data'].update(get_omci['data']) |
| |
| except TimeoutError: |
| self.log.debug('tbl-attr-timeout') |
| |
| except Exception as e: |
| self.log.exception('tbl-attr-timeout', e=e) |
| |
| self.deferred.callback(self) |
| |
| @inlineCallbacks |
| def perform_get_failed_attributes(self, tmp_results, attributes): |
| """ |
| |
| :param tmp_results: |
| :param attributes: |
| :return: |
| """ |
| self.log.debug('perform-get-failed', attrs=attributes) |
| |
| for attr in attributes: |
| try: |
| frame = MEFrame(self._entity_class, self._entity_id, {attr}).get() |
| |
| self.strobe_watchdog() |
| results = yield self._device.omci_cc.send(frame) |
| |
| status = results.fields['omci_message'].fields['success_code'] |
| |
| if status == RC.AttributeFailure.value: |
| self.log.debug('unknown-or-invalid-attribute', attr=attr, status=status) |
| self._failed_or_unknown_attributes.add(attr) |
| |
| elif status != RC.Success.value: |
| self.log.warn('invalid-get', class_id=self._entity_class, |
| attribute=attr, status=status) |
| self._failed_or_unknown_attributes.add(attr) |
| |
| else: |
| # Add to partial results and correct the status |
| tmp_results.fields['omci_message'].fields['success_code'] = status |
| tmp_results.fields['omci_message'].fields['attributes_mask'] |= \ |
| results.fields['omci_message'].fields['attributes_mask'] |
| |
| if tmp_results.fields['omci_message'].fields.get('data') is None: |
| tmp_results.fields['omci_message'].fields['data'] = dict() |
| |
| tmp_results.fields['omci_message'].fields['data'][attr] = \ |
| results.fields['omci_message'].fields['data'][attr] |
| |
| except TimeoutError as e: |
| self.log.debug('attr-timeout') |
| |
| except Exception as e: |
| self.log.exception('attr-failure', e=e) |
| |
| self._results = tmp_results |
| self.deferred.callback(self) |