VOL-3616: Support for API to retrieve information about UNI of an ONU

Change-Id: Iaf6f8147278cc0cbb084b66d7400ce84a0b18ae4
diff --git a/python/adapters/brcm_openomci_onu/brcm_openomci_onu_adapter.py b/python/adapters/brcm_openomci_onu/brcm_openomci_onu_adapter.py
index c261501..d68e72b 100644
--- a/python/adapters/brcm_openomci_onu/brcm_openomci_onu_adapter.py
+++ b/python/adapters/brcm_openomci_onu/brcm_openomci_onu_adapter.py
@@ -23,7 +23,7 @@
 from __future__ import absolute_import
 import structlog
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, returnValue
 
 from zope.interface import implementer
 
@@ -41,7 +41,7 @@
 from brcm_openomci_onu_handler import BrcmOpenomciOnuHandler
 from omci.brcm_capabilities_task import BrcmCapabilitiesTask
 from copy import deepcopy
-
+from voltha_protos.extensions_pb2 import SingleGetValueResponse, GetValueResponse
 
 @implementer(IAdapterInterface)
 class BrcmOpenomciOnuAdapter(object):
@@ -305,3 +305,23 @@
         handler = self.devices_handlers[device.id]
         result = handler.start_omci_test_action(device, uuid)
         return result
+
+    @inlineCallbacks
+    def single_get_value_request(self, request):
+        """
+        :param request: A request to get a specific attribute of a device, of type SingleGetValueRequest
+        :return:
+        """
+        self.log.info('single-get-value-request', request=request)
+        handler = self.devices_handlers[request.targetId]
+        get_value_req = request.request
+        if get_value_req.HasField("uniInfo"):
+            result = yield handler.get_uni_status(get_value_req)
+            self.log.debug('single-get-value-result', res=result)
+            returnValue(result)
+        else:
+            self.log.debug('invalid-request')
+            errresult = SingleGetValueResponse()
+            errresult.response.status = GetValueResponse.ERROR
+            errresult.response.errReason = GetValueResponse.UNSUPPORTED
+            returnValue(errresult)
diff --git a/python/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py b/python/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
index abb73a8..07d967e 100755
--- a/python/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
+++ b/python/adapters/brcm_openomci_onu/brcm_openomci_onu_handler.py
@@ -34,6 +34,7 @@
 from omci.brcm_tp_delete_task import BrcmTpDeleteTask
 from omci.brcm_tp_setup_task import BrcmTpSetupTask
 from omci.brcm_uni_lock_task import BrcmUniLockTask
+from omci.brcm_uni_status import BrcmUniStatusTask
 from omci.brcm_vlan_filter_task import BrcmVlanFilterTask
 from onu_gem_port import OnuGemPort
 from onu_tcont import OnuTCont
@@ -55,7 +56,7 @@
 from pyvoltha.common.tech_profile.tech_profile import TechProfile
 from pyvoltha.common.utils.registry import registry
 from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, DeferredQueue
 from uni_port import RESERVED_TRANSPARENT_VLAN
 from uni_port import UniPort, UniType
 from voltha_protos.common_pb2 import OperStatus, ConnectStatus, AdminState
@@ -66,6 +67,7 @@
 from voltha_protos.openflow_13_pb2 import OFPXMC_OPENFLOW_BASIC
 from voltha_protos.openolt_pb2 import OnuIndication
 from voltha_protos.voltha_pb2 import TestResponse
+from voltha_protos.extensions_pb2 import SingleGetValueResponse, GetValueResponse
 
 OP = EntityOperations
 RC = ReasonCodes
@@ -97,6 +99,8 @@
         self._tp = dict()  # tp_id -> technology profile definition in KV Store.
         self._reconciling = False
         self.olt_serial_number = ""
+        self.uni_status_response_queue = DeferredQueue()
+        self._results = SingleGetValueResponse()
 
         # Persisted onu configuration needed in case of reconciliation.
         self._onu_persisted_state = {
@@ -2069,7 +2073,6 @@
     def onu_deleted_event(self):
         self.log.debug('onu-deleted-event')
         try:
-            device = yield self.core_proxy.get_device(self.device_id)
             olt_serial_number = self.olt_serial_number
             raised_ts = arrow.utcnow().timestamp
             intf_id = self._onu_persisted_state.get('intf_id')
@@ -2137,3 +2140,29 @@
                                        **kwargs_omci_test_action)
         test_request.perform_test_omci()
         return (TestResponse(result=TestResponse.SUCCESS))
+
+    @inlineCallbacks
+    def get_uni_status(self, request):
+        """
+        :param request:
+        :return:
+        """
+        for uni in self.uni_ports:
+           self.log.debug('uni-id-and-uni-index',uni_id = uni.uni_id, uni_idx=request.uniInfo.uniIndex)
+           if uni.uni_id == request.uniInfo.uniIndex:
+               task = BrcmUniStatusTask(self.omci_agent, self.device_id, request, uni.entity_id, self.uni_status_response_queue)
+               self._deferred = self._onu_omci_device.task_runner.queue_task(task)
+               try:
+                   self._results = yield self.uni_status_response_queue.get()
+                   self.log.debug('uni-status-response',res=self._results)
+               except Exception as e:
+                   self.log.exception("failed-dequeueing-received-message", e=e)
+                   self._results.response.status = GetValueResponse.ERROR
+                   self._results.response.errReason = GetValueResponse.UNSUPPORTED
+               finally:
+                   task.stop()
+                   returnValue(self._results)
+        self.log.error('uni-not-found', uni_idx=request.uniInfo.uniIndex)
+        self._results.response.status = GetValueResponse.ERROR
+        self._results.response.errReason = GetValueResponse.UNSUPPORTED
+        returnValue(self._results)
diff --git a/python/adapters/brcm_openomci_onu/omci/brcm_uni_status.py b/python/adapters/brcm_openomci_onu/omci/brcm_uni_status.py
new file mode 100644
index 0000000..dd61b7e
--- /dev/null
+++ b/python/adapters/brcm_openomci_onu/omci/brcm_uni_status.py
@@ -0,0 +1,188 @@
+#
+# 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 __future__ import absolute_import
+import structlog
+from pyvoltha.adapters.extensions.omci.tasks.task import Task
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, failure, returnValue, DeferredQueue
+from pyvoltha.adapters.extensions.omci.omci_defs import ReasonCodes, EntityOperations
+from pyvoltha.adapters.extensions.omci.omci_me import OntGFrame
+from pyvoltha.adapters.extensions.omci.omci_me import PptpEthernetUniFrame, VeipUniFrame
+from voltha_protos.extensions_pb2 import SingleGetValueResponse, GetValueResponse, GetOnuUniInfoResponse
+
+RC = ReasonCodes
+OP = EntityOperations
+
+
+class BrcmUniStatusTask(Task):
+    """
+    Get the status of the UNI Port
+    """
+    task_priority = Task.DEFAULT_PRIORITY + 20
+    name = "Broadcom UNI Status Task"
+
+    def __init__(self, omci_agent, device_id, get_val_req, entity_id, msg_queue, priority=task_priority):
+        """
+        Class initialization
+
+        :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+        :param device_id: (str) ONU Device ID
+        :param get_val_req: (voltha_pb2.SingleGetValueResponse)
+        :param priority: (int) OpenOMCI Task priority (0..255) 255 is the highest
+        """
+        super(BrcmUniStatusTask, self).__init__(BrcmUniStatusTask.name,
+                                              omci_agent,
+                                              device_id,
+                                              priority=priority,
+                                              exclusive=True)
+
+        self.log = structlog.get_logger(device_id=device_id,
+                                        name=BrcmUniStatusTask.name,
+                                        task_id=self._task_id)
+
+        self._device = omci_agent.get_device(device_id)
+        self._req = get_val_req
+        self._results = SingleGetValueResponse()
+        self._local_deferred = None
+        self._config = self._device.configuration
+        self._entity_id = entity_id
+        self.uni_status_response_queue = msg_queue
+
+        self.log.info("get-uni-staus-deviceid",deviceid=device_id)
+
+    def cancel_deferred(self):
+        super(BrcmUniStatusTask, 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
+
+    def start(self):
+        """
+        Start UNI/PPTP Get Status Task
+        """
+        super(BrcmUniStatusTask, self).start()
+        self._local_deferred = reactor.callLater(0, self.perform_get_uni_status)
+    def stop(self):
+         self.cancel_deferred()
+         super(BrcmUniStatusTask, self).stop()
+
+
+    @inlineCallbacks
+    def perform_get_uni_status(self):
+        """
+        Perform the Get UNI Status
+        """
+        self.log.info('get-uni-status-uni-index-is  ',uni_index=self._req.uniInfo.uniIndex)
+
+
+        try:
+            pptp_list = sorted(self._config.pptp_entities) if self._config.pptp_entities else []
+            pptp_items = ['administrative_state', 'operational_state', 'config_ind', 'max_frame_size', 'sensed_type', 'bridged_ip_ind']
+            for entity_id in pptp_list:
+                self.log.info('entity-id',entity_id)
+            msg = PptpEthernetUniFrame(self._entity_id, attributes=pptp_items)
+            yield self._send_omci_msg(msg)
+        except Exception as e:
+            self._results.response.status = GetValueResponse.ERROR
+            self._results.response.errReason = GetValueResponse.REASON_UNDEFINED
+            self.log.exception('get-uni-status', e=e)
+        finally:
+            self.log.info('uni-status-response',self._results)
+            yield self.uni_status_response_queue.put(self._results)
+
+    @inlineCallbacks
+    def _send_omci_msg(self, me_message):
+        frame = me_message.get()
+        results = yield self._device.omci_cc.send(frame)
+        if self._check_status_and_state(results, 'get-uni-status'):
+           omci_msg = results.fields['omci_message'].fields
+           self._results.response.status = GetValueResponse.OK
+           self._collect_uni_admin_state(results)
+           self._collect_uni_operational_state(results)
+           self._collect_uni_config_ind(results)
+        else:
+           self._results.response.status = GetValueResponse.ERROR
+           self._results.response.errReason = GetValueResponse.UNSUPPORTED
+
+    def _collect_uni_admin_state(self, results):
+        omci_msg = results.fields['omci_message'].fields
+        admin_state = omci_msg.get("data").get("administrative_state")
+        if admin_state == 0 :
+           self._results.response.uniInfo.admState  = GetOnuUniInfoResponse.UNLOCKED
+        elif admin_state == 1 :
+           self._results.response.uniInfo.admState  = GetOnuUniInfoResponse.LOCKED
+        else:
+           self._results.response.uniInfo.admState  = GetOnuUniInfoResponse.ADMSTATE_UNDEFINED
+
+    def _collect_uni_operational_state(self, results):
+        self.log.info('collect-uni-oper-state')
+        omci_msg = results.fields['omci_message'].fields
+        oper_state=omci_msg.get("data").get("operational_state")
+        if oper_state == 0 :
+           self._results.response.uniInfo.operState = GetOnuUniInfoResponse.ENABLED
+        elif oper_state == 1 :
+           self._results.response.uniInfo.operState = GetOnuUniInfoResponse.DISABLED
+        else:
+           self._results.response.uniInfo.operState = GetOnuUniInfoResponse.OPERSTATE_UNDEFINED
+
+    def _collect_uni_config_ind(self, results):
+        config_ind_map = {0:GetOnuUniInfoResponse.UNKOWN,
+                          1:GetOnuUniInfoResponse.TEN_BASE_T_FDX,
+                          2:GetOnuUniInfoResponse.HUNDRED_BASE_T_FDX,
+                          3:GetOnuUniInfoResponse.GIGABIT_ETHERNET_FDX,
+                          4:GetOnuUniInfoResponse.TEN_G_ETHERNET_FDX,
+                          17:GetOnuUniInfoResponse.TEN_BASE_T_HDX,
+                          18:GetOnuUniInfoResponse.HUNDRED_BASE_T_HDX,
+                          19:GetOnuUniInfoResponse.GIGABIT_ETHERNET_HDX,
+                          }
+        self.log.info('collect-config-ind')
+        omci_msg = results.fields['omci_message'].fields
+        config_ind =omci_msg.get("data").get("config_ind", GetOnuUniInfoResponse.UNKOWN)
+        self._results.response.uniInfo.configInd  = config_ind_map.get(config_ind, GetOnuUniInfoResponse.UNKOWN)
+
+    def _check_status_and_state(self, results, operation=''):
+        """
+        Check the results of an OMCI response.  An exception is thrown
+        if the task was cancelled or an error was detected.
+
+        :param results: (OmciFrame) OMCI Response frame
+        :param operation: (str) what operation was being performed
+        :return: True if successful, False if the entity existed (already created)
+        """
+        omci_msg = results.fields['omci_message'].fields
+        status = omci_msg['success_code']
+        error_mask = omci_msg.get('parameter_error_attributes_mask', 'n/a')
+        failed_mask = omci_msg.get('failed_attributes_mask', 'n/a')
+        unsupported_mask = omci_msg.get('unsupported_attributes_mask', 'n/a')
+
+        self.log.debug("omci-response", operation=operation,
+                       omci_msg=omci_msg, status=status,
+                       error_mask=error_mask, failed_mask=failed_mask,
+                       unsupported_mask=unsupported_mask)
+
+        self.strobe_watchdog()
+        if status == RC.Success:
+            return True
+        else:
+           self.log.info("omci-reponse-failed",  status, "error-mask-is",
+           error_mask, "failed-mask-is ", failed_mask, "unsupported-mask-is ", unsupported_mask)
+           return False
+