VOL-791: Support for OLT disable/enable on ASFvOLT16

Change-Id: I2d5c12a8da0ef28eb53f918a5724596618446dcc
diff --git a/cli/main.py b/cli/main.py
index 6060435..a1c7226 100755
--- a/cli/main.py
+++ b/cli/main.py
@@ -334,7 +334,7 @@
                     break
                 self.poutput('waiting for device to be enabled...')
                 sleep(.5)
-        except Exception, e:
+        except Exception as  e:
             self.poutput('Error enabling {}.  Error:{}'.format(device_id, e))
 
     complete_activate_olt = complete_device
@@ -349,7 +349,7 @@
             stub = self.get_stub()
             stub.RebootDevice(voltha_pb2.ID(id=device_id))
             self.poutput('rebooted {}'.format(device_id))
-        except Exception, e:
+        except Exception as e:
             self.poutput('Error rebooting {}.  Error:{}'.format(device_id, e))
 
     def do_self_test(self, line):
@@ -363,7 +363,7 @@
             res = stub.SelfTest(voltha_pb2.ID(id=device_id))
             self.poutput('Self Tested {}'.format(device_id))
             self.poutput(dumps(pb2dict(res), indent=4))
-        except Exception, e:
+        except Exception as e:
             self.poutput('Error in self test {}.  Error:{}'.format(device_id, e))
 
     def do_delete(self, line):
@@ -376,7 +376,7 @@
             stub = self.get_stub()
             stub.DeleteDevice(voltha_pb2.ID(id=device_id))
             self.poutput('deleted {}'.format(device_id))
-        except Exception, e:
+        except Exception as e:
             self.poutput('Error deleting {}.  Error:{}'.format(device_id, e))
 
     def do_disable(self, line):
@@ -392,15 +392,14 @@
             # Do device query and verify that the device admin status is
             # DISABLED and Operational Status is unknown
             device = stub.GetDevice(voltha_pb2.ID(id=device_id))
-            if device.oper_status == voltha_pb2.OperStatus.UNKNOWN and \
-                            device.admin_state == voltha_pb2.AdminState.DISABLED:
+            if device.admin_state == voltha_pb2.AdminState.DISABLED:
                 self.poutput('disabled successfully {}'.format(device_id))
             else:
                 self.poutput('disabling failed {}.  Admin State:{} '
                              'Operation State: {}'.format(device_id,
                                                           device.admin_state,
                                                           device.oper_status))
-        except Exception, e:
+        except Exception as e:
             self.poutput('Error disabling {}.  Error:{}'.format(device_id, e))
 
     def do_test(self, line):
diff --git a/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py b/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
index ee1a45f..4075899 100644
--- a/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
+++ b/voltha/adapters/asfvolt16_olt/asfvolt16_device_handler.py
@@ -19,7 +19,7 @@
 """
 
 import arrow
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, DeferredQueue, QueueOverflow, QueueUnderflow
 from itertools import count, ifilterfalse
 from voltha.protos.events_pb2 import KpiEvent, MetricValuePairs
 from voltha.protos.events_pb2 import KpiEventType
@@ -219,6 +219,7 @@
         self.transceiver_type = bal_model_types_pb2.BAL_TRX_TYPE_XGPON_LTH_7226_PC
         self.asfvolt_device_info = Asfvolt16DeviceInfo(self.bal,
                                                        self.log, self.device_id)
+        self.pon_port_config_resp = DeferredQueue(size=1)
 
     def __del__(self):
         super(Asfvolt16Handler, self).__del__()
@@ -259,10 +260,17 @@
                         return v_enet
         return None
 
-    def get_v_ont_ani(self, name):
-        for key, v_ont_ani in self.v_ont_anis.items():
-            if key == name:
-                return v_ont_ani
+    def get_v_ont_ani(self, **kwargs):
+        name = kwargs.pop('name', None)
+        onu_id = kwargs.pop('onu_id', None)
+        if name is not None:
+            for key, v_ont_ani in self.v_ont_anis.items():
+                if key == name:
+                    return v_ont_ani
+        if onu_id is not None:
+            for key, v_ont_ani in self.v_ont_anis.items():
+                if onu_id == v_ont_ani.v_ont_ani.data.onu_id:
+                    return v_ont_ani
         return None
 
     def get_gem_port_info(self, v_enet, **kwargs):
@@ -375,7 +383,7 @@
             self.log.error('Failed-to-get-v-enet', gem_port_id=gem_port_id)
             return
 
-        v_ont_ani = self.get_v_ont_ani(v_enet.v_enet.data.v_ontani_ref)
+        v_ont_ani = self.get_v_ont_ani(name=v_enet.v_enet.data.v_ontani_ref)
         if v_ont_ani is None:
             self.log.info('Failed-to-get-v_ont_ani',
                           v_ont_ani=v_enet.v_enet.data.v_ontani_ref)
@@ -596,6 +604,14 @@
             self.adapter_agent.update_device(device)
             heartbeat_alarm(device, 1, self.heartbeat_miss)
 
+            child_devices = self.adapter_agent.get_child_devices(self.device_id)
+            for child in child_devices:
+                msg = {'proxy_address': child.proxy_address,
+                       'event': 'olt-reboot'}
+                # Send the event message to the ONU adapter
+                self.adapter_agent.publish_inter_adapter_message(child.id,
+                                                                 msg)
+
         self.heartbeat_count += 1
         reactor.callLater(self.heartbeat_interval, self.heartbeat)
 
@@ -757,6 +773,12 @@
 
     def BalIfaceIndication(self, device_id, Iface_ID):
         self.log.info('Interface-Indication')
+
+        try:
+            self.pon_port_config_resp.get()
+        except QueueUnderflow:
+            self.log.error("no-data-in-queue")
+
         device = self.adapter_agent.get_device(self.device_id)
         self._handle_pon_pm_counter_req_towards_device(device,Iface_ID)
 
@@ -995,10 +1017,13 @@
             device.reason = 'OLT activated successfully'
             self.adapter_agent.update_device(device)
 
-        else:
+        elif ind_info['deactivation_successful'] is True:
             device.oper_status = OperStatus.FAILED
-            device.reason = 'Failed to Intialize OLT'
+            device.reason = 'device deactivated successfully'
             self.adapter_agent.update_device(device)
+        else:
+            device.oper_status = OperStatus.UNKNOWN
+            device.reason = 'operation status unknown'
             reactor.callLater(15, self.activate, device)
         return
 
@@ -1217,6 +1242,7 @@
                           onu_id=child_device.proxy_address.onu_id,
                           serial_number=serial_number)
 
+    @inlineCallbacks
     def create_interface(self, data):
         try:
             if isinstance(data, ChannelgroupConfig):
@@ -1254,6 +1280,18 @@
 
                 self.log.info('Activating-PON-port-at-OLT',
                               pon_id=data.data.xgs_ponid)
+
+                # We put the transaction in a deferredQueue
+                # Only one transaction can exist in the queue at a given time.
+                # We do this we enable the pon ports sequentially.
+                while True:
+                    try:
+                        self.pon_port_config_resp.put(data)
+                        break
+                    except QueueOverflow:
+                        self.log.info('another-pon-port-enable-pending')
+                        yield asleep(0.3)
+
                 self.add_port(port_no=data.data.xgs_ponid,
                               port_type=Port.PON_OLT,
                               label=data.name)
@@ -1451,8 +1489,111 @@
             self.log.info('VEnet-is-not-configured-yet.',
                           gem_port_info=data)
 
+    @inlineCallbacks
     def disable(self):
-        super(Asfvolt16Handler, self).disable()
+        self.log.info("disable")
+        device = self.adapter_agent.get_device(self.device_id)
+        for channel_termination in self.channel_terminations.itervalues():
+            pon_port = channel_termination.data.xgs_ponid
+
+            while True:
+                try:
+                    self.pon_port_config_resp.put(channel_termination)
+                    break
+                except QueueOverflow:
+                    self.log.info('another-pon-port-disable-pending')
+                    yield asleep(0.3)
+
+            yield self.bal.deactivate_pon_port(olt_no=self.olt_id,
+                                               pon_port=pon_port,
+                                               transceiver_type=
+                                               self.transceiver_type)
+
+        # deactivate olt
+        yield self.bal.deactivate_olt()
+        device.admin_state = AdminState.DISABLED
+        device.oper_status = OperStatus.FAILED
+        device.connect_status = ConnectStatus.UNREACHABLE
+        self.adapter_agent.update_device(device)
+        # deactivate nni port
+        for port in self.asfvolt_device_info.sfp_device_presence_map.iterkeys():
+            # ignore any sfp other than the NNI.
+            if not self._valid_nni_port(port):
+                continue
+
+            # Since OLT is reachable after reboot, OLT should configurable with
+            # all the old existing flows. NNI ports should be mark it as down for
+            # ONOS to push the old flows
+            nni_intf_id = (port -
+                           self.asfvolt_device_info.
+                           asfvolt16_device_topology.num_of_pon_ports)
+            self.update_logical_port(nni_intf_id + MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM,
+                                     Port.ETHERNET_NNI,
+                                     OFPPS_LINK_DOWN)
+
+        # send event to child devices(onus)
+        for child_device in self.adapter_agent.\
+                get_child_devices(self.device_id):
+           msg = {'proxy_address': child_device.proxy_address,
+                  'event': 'olt-disabled'}
+           # Send evnt to such child devices which were REACHABLE,
+           # so that those devices can be marked UNREACHABLE
+           if child_device.connect_status == ConnectStatus.REACHABLE:
+               self.adapter_agent.publish_inter_adapter_message(child_device.id,
+                                                                msg)
+
+    @inlineCallbacks
+    def reenable(self):
+        self.log.info("enable")
+        device = self.adapter_agent.get_device(self.device_id)
+        yield self.bal.activate_olt()
+        # activate pon ports
+        for channel_termination in self.channel_terminations.itervalues():
+            pon_port = channel_termination.data.xgs_ponid
+
+            while True:
+                try:
+                    self.pon_port_config_resp.put(channel_termination)
+                    break
+                except QueueOverflow:
+                    self.log.info('another-pon-port-enable-pending')
+                    yield asleep(0.3)
+
+            yield self.bal.activate_pon_port(olt_no=self.olt_id,
+                                             pon_port=pon_port,
+                                             transceiver_type=
+                                             self.transceiver_type)
+
+        device.oper_status = OperStatus.ACTIVE
+        device.connect_status = ConnectStatus.REACHABLE
+        self.adapter_agent.update_device(device)
+
+        # activate nni port
+        for port in self.asfvolt_device_info.sfp_device_presence_map.iterkeys():
+            # ignore any sfp other than the NNI.
+            if not self._valid_nni_port(port):
+                continue
+
+            # Since OLT is reachable after reboot, OLT should configurable with
+            # all the old existing flows. NNI ports should be mark it as down for
+            # ONOS to push the old flows
+            nni_intf_id = (port -
+                           self.asfvolt_device_info.
+                           asfvolt16_device_topology.num_of_pon_ports)
+            self.update_logical_port(nni_intf_id + MIN_ASFVOLT_NNI_LOGICAL_PORT_NUM,
+                                     Port.ETHERNET_NNI,
+                                     OFPPS_LIVE)
+
+        # send event to child devices(onus)
+        for child_device in self.adapter_agent.\
+                get_child_devices(self.device_id):
+           msg = {'proxy_address': child_device.proxy_address,
+                  'event': 'olt-enabled'}
+           # Send evnt to such child devices which were UNREACHABLE,
+           # so that those devices can be marked REACHABLE
+           if child_device.connect_status == ConnectStatus.UNREACHABLE:
+               self.adapter_agent.publish_inter_adapter_message(child_device.id,
+                                                                msg)
 
     def delete(self):
         super(Asfvolt16Handler, self).delete()
@@ -1743,7 +1884,7 @@
             self.store_flows(uplink_classifier, uplink_action,
                              v_enet, traffic_class=2)
             return
-        v_ont_ani = self.get_v_ont_ani(v_enet.v_enet.data.v_ontani_ref)
+        v_ont_ani = self.get_v_ont_ani(name=v_enet.v_enet.data.v_ontani_ref)
         if v_ont_ani is None:
             self.log.info('Failed-to-get-v_ont_ani',
                           v_ont_ani=v_enet.v_enet.data.v_ontani_ref)
@@ -1849,7 +1990,7 @@
             self.store_flows(uplink_classifier, uplink_action,
                              v_enet, traffic_class=2)
             return
-        v_ont_ani = self.get_v_ont_ani(v_enet.v_enet.data.v_ontani_ref)
+        v_ont_ani = self.get_v_ont_ani(name=v_enet.v_enet.data.v_ontani_ref)
         if v_ont_ani is None:
             self.log.error('Failed-to-get-v_ont_ani',
                            v_ont_ani=v_enet.v_enet.data.v_ontani_ref)
@@ -1988,7 +2129,7 @@
             self.store_flows(uplink_classifier, uplink_action,
                              v_enet, traffic_class=2)
             return
-        v_ont_ani = self.get_v_ont_ani(v_enet.v_enet.data.v_ontani_ref)
+        v_ont_ani = self.get_v_ont_ani(name=v_enet.v_enet.data.v_ontani_ref)
         if v_ont_ani is None:
             self.log.info('Failed-to-get-v_ont_ani',
                           v_ont_ani=v_enet.v_enet.data.v_ontani_ref)
diff --git a/voltha/adapters/asfvolt16_olt/asfvolt16_ind_handler.py b/voltha/adapters/asfvolt16_olt/asfvolt16_ind_handler.py
index 0ba2a21..0369528 100644
--- a/voltha/adapters/asfvolt16_olt/asfvolt16_ind_handler.py
+++ b/voltha/adapters/asfvolt16_olt/asfvolt16_ind_handler.py
@@ -39,16 +39,22 @@
     def bal_acc_term_ind(self, indication, device_handler):
         #     ind_info: {'_object_type': <str>
         #                'actv_status': <str>}
+        self.log.info("access-term-ind",indication=indication)
         ind_info = dict()
         ind_info['_object_type'] = 'access_terminal_indication'
         ind_info['_sub_group_type'] = 'access_terminal_indication'
-        if ((indication.access_term_ind.data.admin_state == \
-                bal_model_types_pb2.BAL_STATE_UP) and \
-                (indication.access_term_ind.data.oper_status == \
-                bal_model_types_pb2.BAL_STATUS_UP)):
-            ind_info['activation_successful'] = True
-        else:
-            ind_info['activation_successful'] = False
+        ind_info['activation_successful'] = False
+        ind_info['deactivation_successful'] = False
+        if indication.access_term_ind.data.admin_state == \
+           bal_model_types_pb2.BAL_STATE_UP:
+            if indication.access_term_ind.data.oper_status == \
+               bal_model_types_pb2.BAL_STATUS_UP:
+                ind_info['activation_successful'] = True
+        elif indication.access_term_ind.data.admin_state == \
+             bal_model_types_pb2.BAL_STATE_DOWN:
+            if indication.access_term_ind.data.oper_status == \
+               bal_model_types_pb2.BAL_STATUS_DOWN:
+                ind_info['deactivation_successful'] = True
 
         reactor.callLater(0,
                           device_handler.handle_access_term_ind,
diff --git a/voltha/adapters/asfvolt16_olt/bal.py b/voltha/adapters/asfvolt16_olt/bal.py
index 7d93b66..409c223 100644
--- a/voltha/adapters/asfvolt16_olt/bal.py
+++ b/voltha/adapters/asfvolt16_olt/bal.py
@@ -79,6 +79,10 @@
         self.log.info('activating-olt')
         self.set_access_terminal_admin_state(bal_model_types_pb2.BAL_STATE_UP)
 
+    def deactivate_olt(self):
+        self.log.info('deactivating-olt')
+        self.set_access_terminal_admin_state(bal_model_types_pb2.BAL_STATE_DOWN)
+
     @inlineCallbacks
     def set_access_terminal_admin_state(self, admin_state):
         obj = bal_pb2.BalCfg()
@@ -86,7 +90,7 @@
         obj.hdr.obj_type = bal_model_ids_pb2.BAL_OBJ_ID_ACCESS_TERMINAL
         obj.cfg.key.access_term_id = 0
         obj.cfg.data.admin_state = admin_state
-        self.log.info('Activating-Access-Terminal-Device',
+        self.log.info('Admin-stage-change-Access-Terminal-Device',
                       admin_state=admin_state, device_id=self.device_id,
                       access_terminal_details=obj)
         yield self.stub.BalCfgSet(obj, timeout=GRPC_TIMEOUT)
@@ -128,6 +132,7 @@
                           olt=olt_no, pon_port=pon_port,
                           pon_port_details=obj,
                           transceiver_type=transceiver_type)
+            yield self.stub.BalCfgSet(obj, timeout=GRPC_TIMEOUT)
         except Exception as e:
             self.log.info('deactivating-pon-port in olt-exception', exc=str(e))
         return
diff --git a/voltha/adapters/broadcom_onu/broadcom_onu.py b/voltha/adapters/broadcom_onu/broadcom_onu.py
index d5c966f..2811eb6 100644
--- a/voltha/adapters/broadcom_onu/broadcom_onu.py
+++ b/voltha/adapters/broadcom_onu/broadcom_onu.py
@@ -356,10 +356,15 @@
 
         elif event_msg['event'] == 'deactivate-onu':
             device = self.adapter_agent.get_device(self.device_id)
+            self.disable_ports(device)
             device.connect_status = ConnectStatus.UNREACHABLE
             device.oper_status = OperStatus.DISCOVERED
             self.adapter_agent.update_device(device)
-            self.disable_ports(device)
+
+        elif (event_msg['event'] == 'olt-reboot'):
+            device = self.adapter_agent.get_device(self.device_id)
+            device.connect_status = ConnectStatus.UNREACHABLE
+            self.adapter_agent.update_device(device)
 
         elif event_msg['event'] == 'ranging-completed':
 
@@ -387,6 +392,15 @@
             gem_port = GemportsConfigData()
             gem_port.gemport_id = event_msg['event_data']['gemport_id']
             self.create_gemport(gem_port)
+        elif event_msg['event'] == 'olt-disabled':
+            device = self.adapter_agent.get_device(self.device_id)
+            device.connect_status = ConnectStatus.UNREACHABLE
+            self.adapter_agent.update_device(device)
+
+        elif event_msg['event'] == 'olt-enabled':
+            device = self.adapter_agent.get_device(self.device_id)
+            device.connect_status = ConnectStatus.REACHABLE
+            self.adapter_agent.update_device(device)
 
         # Handle next event
         reactor.callLater(0, self.handle_onu_events)
@@ -1770,3 +1784,18 @@
         for port in ports:
             port_id = 'uni-{}'.format(port.port_no)
             self.update_logical_port(logical_device_id, port_id, OFPPS_LINK_DOWN)
+
+    def enable_ports(self, onu_device):
+        self.log.info('enable-ports', device_id=self.device_id)
+
+        # Disable all ports on that device
+        self.adapter_agent.enable_all_ports(self.device_id)
+
+        parent_device = self.adapter_agent.get_device(onu_device.parent_id)
+        assert parent_device
+        logical_device_id = parent_device.parent_id
+        assert logical_device_id
+        ports = self.adapter_agent.get_ports(onu_device.id, Port.ETHERNET_UNI)
+        for port in ports:
+            port_id = 'uni-{}'.format(port.port_no)
+            self.update_logical_port(logical_device_id, port_id, OFPPS_LIVE)
diff --git a/voltha/core/core.py b/voltha/core/core.py
index 47cb4d1..c7dccea 100644
--- a/voltha/core/core.py
+++ b/voltha/core/core.py
@@ -213,6 +213,8 @@
         self.device_agents[device.id] = \
             yield DeviceAgent(self, device).start(device=device,
                                                   reconcile=reconcile)
+        path = '/devices/{}'.format(device.id)
+        self.xpon_agent.register_interface(device.id, path, update=False)
 
     @inlineCallbacks
     def _handle_remove_device(self, device):
diff --git a/voltha/core/device_agent.py b/voltha/core/device_agent.py
index 129390b..ba27c1a 100644
--- a/voltha/core/device_agent.py
+++ b/voltha/core/device_agent.py
@@ -307,7 +307,9 @@
     def update_device(self, device):
         self.last_data = device  # so that we don't propagate back
         self.proxy.update('/', device)
-        if device.oper_status == OperStatus.ACTIVE and device.connect_status == ConnectStatus.REACHABLE:
+        if device.admin_state == AdminState.ENABLED and \
+           device.oper_status == OperStatus.ACTIVE and \
+           device.connect_status == ConnectStatus.REACHABLE:
             self.log.info('replay-create-interfaces ', device=device.id)
             self.core.xpon_agent.replay_interface(device.id)
             # if device accepts bulk flow update, lets just call that
@@ -320,7 +322,6 @@
                     flows=flows,
                     groups=groups)
 
-
     def update_device_pm_config(self, device_pm_config, init=False):
         self.callback_data = init# so that we don't push init data
         self.pm_config_proxy.update('/', device_pm_config)