EVC/EVC-MAP update and initial xPON support

Change-Id: I5bc807318ebcd0901315ffc08bb0a14e66a35688
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index 37ee403..e8a64bd 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -1,18 +1,16 @@
-#
-# Copyright 2017-present Adtran, Inc.
+# Copyright 2017-present Open Networking Foundation
 #
 # 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
+# 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.
-#
 """
 Adtran generic VOLTHA device handler
 """
@@ -49,8 +47,18 @@
 _ = third_party
 
 _PACKET_IN_VLAN = 4000
+_MULTICAST_VLAN = 4092
+_MANAGEMENT_VLAN = 4093
 _is_inband_frame = BpfProgramFilter('(ether[14:2] & 0xfff) = 0x{:03x}'.format(_PACKET_IN_VLAN))
 
+_DEFAULT_RESTCONF_USERNAME = ""
+_DEFAULT_RESTCONF_PASSWORD = ""
+_DEFAULT_RESTCONF_PORT = 8081
+
+_DEFAULT_NETCONF_USERNAME = ""
+_DEFAULT_NETCONF_PASSWORD = ""
+_DEFAULT_NETCONF_PORT = 830
+
 
 class AdtranDeviceHandler(object):
     """
@@ -84,7 +92,9 @@
     # RPC XML shortcuts
     RESTART_RPC = '<system-restart xmlns="urn:ietf:params:xml:ns:yang:ietf-system"/>'
 
-    def __init__(self, adapter, device_id, username='', password='', timeout=20):
+    def __init__(self, adapter, device_id, timeout=20):
+        from net.adtran_zmq import DEFAULT_ZEROMQ_OMCI_TCP_PORT
+
         self.adapter = adapter
         self.adapter_agent = adapter.adapter_agent
         self.device_id = device_id
@@ -111,17 +121,35 @@
         self.restart_failure_timeout = 5 * 60   # 5 Minute timeout
 
         # REST Client
-        self.rest_port = None
-        self.rest_username = username
-        self.rest_password = password
+        self.rest_port = _DEFAULT_RESTCONF_PORT
+        self.rest_username = _DEFAULT_RESTCONF_USERNAME
+        self.rest_password = _DEFAULT_RESTCONF_PASSWORD
         self._rest_client = None
 
         # NETCONF Client
-        self.netconf_port = None
-        self.netconf_username = username
-        self.netconf_password = password
+        self.netconf_port = _DEFAULT_NETCONF_PORT
+        self.netconf_username = _DEFAULT_NETCONF_USERNAME
+        self.netconf_password = _DEFAULT_NETCONF_PASSWORD
         self._netconf_client = None
 
+        # If Auto-activate is true, all PON ports (up to a limit below) will be auto-enabled
+        # and any ONU's discovered will be auto-activated.
+        #
+        # If it is set to False, then the xPON API/CLI should be used to enable any PON
+        # ports. Before enabling a PON, set it's polling interval. If the polling interval
+        # is 0, then manual ONU discovery is in effect. If >0, then every 'polling' seconds
+        # autodiscover is requested. Any discovered ONUs will need to have their serial-numbers
+        # registered (via xPON API/CLI) before they are activated.
+
+        self._autoactivate = False
+
+        # TODO Remove items below after one PON fully supported and working as expected
+        self.max_nni_ports = 1
+        self.max_pon_ports = 1
+
+        # OMCI ZMQ Channel
+        self.zmq_port = DEFAULT_ZEROMQ_OMCI_TCP_PORT
+
         # Heartbeat support
         self.heartbeat_count = 0
         self.heartbeat_miss = 0
@@ -137,10 +165,6 @@
         # Installed flows
         self.evcs = {}  # Flow ID/name -> FlowEntry
 
-        # TODO Remove items below after one PON fully supported and working as expected
-        self.max_nni_ports = 1
-        self.max_pon_ports = 1
-
     def __del__(self):
         # Kill any startup or heartbeat defers
 
@@ -177,6 +201,8 @@
         return self._rest_client
 
     def parse_provisioning_options(self, device):
+        from net.adtran_zmq import DEFAULT_ZEROMQ_OMCI_TCP_PORT
+
         if not device.ipv4_address:
             self.activate_failed(device, 'No ip_address field provided')
 
@@ -192,14 +218,22 @@
             return ivalue
 
         parser = argparse.ArgumentParser(description='Adtran Device Adapter')
-        parser.add_argument('--nc_username', '-u', action='store', default='hsvroot', help='NETCONF username')
-        parser.add_argument('--nc_password', '-p', action='store', default='BOSCO', help='NETCONF Password')
-        parser.add_argument('--nc_port', '-t', action='store', default=830, type=check_tcp_port,
+        parser.add_argument('--nc_username', '-u', action='store', default=_DEFAULT_NETCONF_USERNAME,
+                            help='NETCONF username')
+        parser.add_argument('--nc_password', '-p', action='store', default=_DEFAULT_NETCONF_PASSWORD,
+                            help='NETCONF Password')
+        parser.add_argument('--nc_port', '-t', action='store', default=_DEFAULT_NETCONF_PORT, type=check_tcp_port,
                             help='NETCONF TCP Port')
-        parser.add_argument('--rc_username', '-U', action='store', default='ADMIN', help='REST username')
-        parser.add_argument('--rc_password', '-P', action='store', default='PASSWORD', help='REST Password')
-        parser.add_argument('--rc_port', '-T', action='store', default=8081, type=check_tcp_port,
-                            help='REST TCP Port')
+        parser.add_argument('--rc_username', '-U', action='store', default=_DEFAULT_RESTCONF_USERNAME,
+                            help='REST username')
+        parser.add_argument('--rc_password', '-P', action='store', default=_DEFAULT_RESTCONF_PASSWORD,
+                            help='REST Password')
+        parser.add_argument('--rc_port', '-T', action='store', default=_DEFAULT_RESTCONF_PORT, type=check_tcp_port,
+                            help='RESTCONF TCP Port')
+        parser.add_argument('--zmq_port', '-z', action='store', default=DEFAULT_ZEROMQ_OMCI_TCP_PORT,
+                            type=check_tcp_port, help='ZeroMQ Port')
+        parser.add_argument('--autoactivate', '-a', action='store_true', default=False,
+                            help='Autoactivate / Demo mode')
 
         try:
             args = parser.parse_args(shlex.split(device.extra_args))
@@ -212,12 +246,27 @@
             self.rest_password = args.rc_password
             self.rest_port = args.rc_port
 
+            self.zmq_port = args.zmq_port
+
+            self._autoactivate = args.autoactivate
+
         except argparse.ArgumentError as e:
             self.activate_failed(device,
                                  'Invalid arguments: {}'.format(e.message),
                                  reachable=False)
         except Exception as e:
-            self.log.exception('parsing error: {}'.format(e.message))
+            self.log.exception('option_parsing_error: {}'.format(e.message))
+
+    @property
+    def autoactivate(self):
+        """
+        Flag indicating if auto-discover/enable of PON ports is enabled as
+        well as ONU auto activation. useful for demos
+
+        If autoactivate is enabled, the default startup state (first time) for a PON port is disabled
+        If autoactivate is disabled, the efault startup state for a PON port is enabled
+        """
+        return self._autoactivate
 
     @inlineCallbacks
     def activate(self, device, reconciling=False):
@@ -241,18 +290,16 @@
             try:
                 self.startup = self.make_restconf_connection()
                 results = yield self.startup
-                self.log.debug('HELLO Contents: {}'.format(pprint.PrettyPrinter().pformat(results)))
+                self.log.debug('HELLO_Contents: {}'.format(pprint.PrettyPrinter().pformat(results)))
 
                 # See if this is a virtualized OLT. If so, no NETCONF support available
 
                 self.is_virtual_olt = 'module-info' in results and\
                                       any(mod.get('module-name', None) == 'adtran-ont-mock'
                                           for mod in results['module-info'])
-                if self.is_virtual_olt:
-                    self.log.info('*** VIRTUAL OLT detected ***')
 
             except Exception as e:
-                self.log.exception('Initial RESTCONF adtran-hello failed', e=e)
+                self.log.exception('Initial_RESTCONF_hello_failed', e=e)
                 self.activate_failed(device, e.message, reachable=False)
 
             ############################################################################
@@ -263,7 +310,7 @@
                 yield self.startup
 
             except Exception as e:
-                self.log.exception('Initial NETCONF connection failed', e=e)
+                self.log.exception('NETCONF_connection_failed', e=e)
                 self.activate_failed(device, e.message, reachable=False)
 
             ############################################################################
@@ -302,7 +349,7 @@
                     self.adapter_agent.update_device(device)
 
                 except Exception as e:
-                    self.log.exception('Device Information request(s) failed', e=e)
+                    self.log.exception('Device_info_failed', e=e)
                     self.activate_failed(device, e.message, reachable=False)
 
             try:
@@ -319,7 +366,7 @@
                         self.adapter_agent.add_port(device.id, port.get_port())
 
             except Exception as e:
-                self.log.exception('Northbound port enumeration and creation failed', e=e)
+                self.log.exception('NNI_enumeration', e=e)
                 self.activate_failed(device, e.message)
 
             try:
@@ -336,7 +383,7 @@
                         self.adapter_agent.add_port(device.id, port.get_port())
 
             except Exception as e:
-                self.log.exception('Southbound port enumeration and creation failed', e=e)
+                self.log.exception('PON_enumeration', e=e)
                 self.activate_failed(device, e.message)
 
             if reconciling:
@@ -378,22 +425,22 @@
                 yield self.startup
 
             except Exception as e:
-                self.log.exception('Logical port creation failed', e=e)
+                self.log.exception('logical-port', e=e)
                 self.activate_failed(device, e.message)
 
             # Complete device specific steps
             try:
-                self.log.debug('Performing final device specific activation procedures')
+                self.log.debug('device-activation-procedures')
                 self.startup = self.complete_device_specific_activation(device, reconciling)
                 yield self.startup
 
             except Exception as e:
-                self.log.exception('Device specific activation failed', e=e)
+                self.log.exception('device-activation-procedures', e=e)
                 self.activate_failed(device, e.message)
 
             # Schedule the heartbeat for the device
 
-            self.log.debug('Starting heartbeat')
+            self.log.debug('Starting-heartbeat')
             self.start_heartbeat(delay=5)
 
             device = self.adapter_agent.get_device(device.id)
@@ -401,6 +448,7 @@
             device.oper_status = OperStatus.ACTIVE
             device.reason = ''
             self.adapter_agent.update_device(device)
+            self.logical_device_id = ld_initialized.id
 
             # finally, open the frameio port to receive in-band packet_in messages
             self._activate_io_port()
@@ -546,6 +594,21 @@
                     self.log.exception('Failed to reset ports to known good initial state', e=e)
                     self.activate_failed(device, e.message)
 
+            # Clean up all EVC and EVC maps (exceptions ok/not-fatal)
+            try:
+                from flow.evc import EVC
+                self.startup = yield EVC.remove_all(self.netconf_client)
+
+            except Exception as e:
+                self.log.exception('Failed attempting to clean up existing EVCs', e=e)
+
+            try:
+                from flow.evc_map import EVCMap
+                self.startup = yield EVCMap.remove_all(self.netconf_client)
+
+            except Exception as e:
+                self.log.exception('Failed attempting to clean up existing EVC-Maps', e=e)
+
         # Start/stop the interfaces as needed
 
         for port in self.northbound_ports.itervalues():
@@ -555,7 +618,7 @@
         if reconciling:
             start_downlinks = device.admin_state == AdminState.ENABLED
         else:
-            start_downlinks = self.initial_port_state == AdminState.ENABLED
+            start_downlinks = self.autoactivate
 
         for port in self.southbound_ports.itervalues():
             self.startup = port.start() if start_downlinks else port.stop()
@@ -737,7 +800,7 @@
             try:
                 yield self.netconf_client.close()
             except Exception as e:
-                self.log.exception('NETCONF client shutdown failed', e=e)
+                self.log.exception('NETCONF-shutdown', e=e)
 
         def _null_clients():
             self._netconf_client = None
@@ -745,6 +808,10 @@
 
         reactor.callLater(0, _null_clients)
 
+        #  Update the logice device mapping
+        if ldi in self.adapter.logical_device_id_to_root_device_id:
+            del self.adapter.logical_device_id_to_root_device_id[ldi]
+
         self.log.info('disabled', device_id=device.id)
         returnValue(results)
 
@@ -769,14 +836,14 @@
             yield self.make_restconf_connection()
 
         except Exception as e:
-            self.log.exception('RESTCONF adtran-hello reconnect failed', e=e)
+            self.log.exception('adtran-hello-reconnect', e=e)
             # TODO: What is best way to handle reenable failure?
 
         try:
             yield self.make_netconf_connection()
 
         except Exception as e:
-            self.log.exception('NETCONF re-connection failed', e=e)
+            self.log.exception('NETCONF-re-connection', e=e)
             # TODO: What is best way to handle reenable failure?
 
         # Recreate the logical device
@@ -850,7 +917,7 @@
                 yield self.netconf_client.rpc(AdtranDeviceHandler.RESTART_RPC)
 
             except Exception as e:
-                self.log.exception('NETCONF client shutdown', e=e)
+                self.log.exception('NETCONF-shutdown', e=e)
                 # TODO: On failure, what is the best thing to do?
 
             # Shutdown communications with OLT. Typically it takes about 2 seconds
@@ -861,7 +928,7 @@
                 self.log.debug('Restart response XML was: {}'.format('ok' if response.ok else 'bad'))
 
             except Exception as e:
-                self.log.exception('NETCONF client shutdown', e=e)
+                self.log.exception('NETCONF-client-shutdown', e=e)
 
         #  Clear off clients
 
@@ -878,7 +945,7 @@
             yield reactor.callLater(10, self._finish_reboot, timeout,
                                     previous_oper_status, previous_conn_status)
         except Exception as e:
-            self.log.exception('finish reboot scheduling', e=e)
+            self.log.exception('finish-reboot', e=e)
 
         returnValue('Waiting for reboot')
 
@@ -886,13 +953,13 @@
     def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
         # Now wait until REST & NETCONF are re-established or we timeout
 
-        self.log.info('Resuming OLT activity after reboot requested',
+        self.log.info('Resuming-activity',
                       remaining=timeout - time.time(), timeout=timeout, current=time.time())
 
         if self.rest_client is None:
             try:
                 response = yield self.make_restconf_connection(get_timeout=10)
-                self.log.debug('Restart RESTCONF connection JSON was: {}'.format(response))
+                # self.log.debug('Restart RESTCONF connection JSON was: {}'.format(response))
 
             except Exception:
                 self.log.debug('No RESTCONF connection yet')
@@ -901,10 +968,9 @@
         if self.netconf_client is None:
             try:
                 yield self.make_netconf_connection(connect_timeout=10)
-                self.log.debug('Restart NETCONF connection succeeded')
+                # self.log.debug('Restart NETCONF connection succeeded')
 
             except Exception as e:
-                self.log.debug('No NETCONF connection yet: {}'.format(e.message))
                 try:
                     if self.netconf_client is not None:
                         yield self.netconf_client.close()
@@ -920,16 +986,16 @@
                     yield reactor.callLater(5, self._finish_reboot, timeout,
                                             previous_oper_status, previous_conn_status)
                 except Exception:
-                    self.log.debug('Rebooted check rescheduling')
+                    self.log.debug('Rebooted-check', e=e)
 
                 returnValue('Waiting some more...')
 
             if self.netconf_client is None and not self.is_virtual_olt:
-                self.log.error('Could not restore NETCONF communications after device RESET')
+                self.log.error('NETCONF-restore-failure')
                 pass        # TODO: What is best course of action if cannot get clients back?
 
             if self.rest_client is None:
-                self.log.error('Could not restore RESTCONF communications after device RESET')
+                self.log.error('RESTCONF-restore-failure')
                 pass        # TODO: What is best course of action if cannot get clients back?
 
         # Pause additional 5 seconds to let allow OLT microservices to complete some more initialization
@@ -1003,7 +1069,7 @@
             try:
                 yield self.netconf_client.close()
             except Exception as e:
-                self.log.exception('NETCONF client shutdown', e=e)
+                self.log.exception('NETCONF-shutdown', e=e)
 
             self._netconf_client = None
 
@@ -1055,7 +1121,7 @@
             pkt = Ether(msg)
             out_pkt = (
                 Ether(src=pkt.src, dst=pkt.dst) /
-                Dot1Q(vlan=4000) /
+                Dot1Q(vlan=_PACKET_IN_VLAN) /
                 Dot1Q(vlan=egress_port, type=pkt.type) /
                 pkt.payload
             )
@@ -1114,7 +1180,7 @@
 
     def start_heartbeat(self, delay=10):
         assert delay > 1
-        self.log.info('*** Starting Device Heartbeat ***')
+        self.log.info('Starting-Device-Heartbeat ***')
         self.heartbeat = reactor.callLater(delay, self.check_pulse)
         return self.heartbeat
 
@@ -1141,7 +1207,7 @@
             assert results
             # Update device states
 
-            self.log.info('heartbeat success')
+            self.log.info('heartbeat-success')
 
             if device.connect_status != ConnectStatus.REACHABLE:
                 device.connect_status = ConnectStatus.REACHABLE