VOL-1395: Common shared libraries needed for Python based device adapters.
This is an initial check-in of code from the master branch. Additional work
is expected on a few items to work with the new go-core and will be covered
by separate JIRAs and commits.
Change-Id: I0856ec6b79b8d3e49082c609eb9c7eedd75b1708
diff --git a/python/adapters/extensions/omci/tasks/omci_get_request.py b/python/adapters/extensions/omci/tasks/omci_get_request.py
new file mode 100644
index 0000000..c325278
--- /dev/null
+++ b/python/adapters/extensions/omci/tasks/omci_get_request.py
@@ -0,0 +1,356 @@
+#
+# 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)