Exemplo n.º 1
0
    def complete_device_specific_activation(self, device, results):
        """
        Perform an initial network operation to discover the device hardware
        and software version. Serial Number would be helpful as well.

        This method is called from within the base class's activate generator.

        :param device: A voltha.Device object, with possible device-type
                specific extensions. Such extensions shall be described as part of
                the device type specification returned by device_types().

        :param results: (dict) original adtran-hello RESTCONF results body
        """
        #
        # For the pizzabox OLT, periodically query the OLT state of all PONs. This
        # is simpler then having each PON port do its own poll.  From this, we can:
        #
        # o Discover any new or missing ONT/ONUs
        #
        # o TODO Discover any LOS for any ONT/ONUs
        #
        # o TODO Update some PON level statistics

        self.zmq_client = AdtranZmqClient(self.ip_address, self.rx_packet)
        # self.nc_client = manager.connect(host='',  # self.ip_address,
        #                                  username=self.rest_username,
        #                                  password=self.rest_password,
        #                                  hostkey_verify=False,
        #                                  allow_agent=False,
        #                                  look_for_keys=False)

        self.status_poll = reactor.callLater(1, self.poll_for_status)
        return None
Exemplo n.º 2
0
    def reenable(self):
        super(AdtranOltHandler, self).reenable()

        self.zmq_client = AdtranZmqClient(self.ip_address,
                                          rx_callback=self.rx_packet,
                                          port=self.zmq_port)
        self.status_poll = reactor.callLater(1, self.poll_for_status)
Exemplo n.º 3
0
    def complete_device_specific_activation(self, device, reconciling):
        """
        Perform an initial network operation to discover the device hardware
        and software version. Serial Number would be helpful as well.

        This method is called from within the base class's activate generator.

        :param device: A voltha.Device object, with possible device-type
                specific extensions. Such extensions shall be described as part of
                the device type specification returned by device_types().

        :param reconciling: (boolean) True if taking over for another VOLTHA
        """
        # For the pizzabox OLT, periodically query the OLT state of all PONs. This
        # is simpler then having each PON port do its own poll.  From this, we can:
        #
        # o Discover any new or missing ONT/ONUs
        #
        # o TODO Discover any LOS for any ONT/ONUs
        #
        # o TODO Update some PON level statistics

        self.zmq_client = AdtranZmqClient(self.ip_address, rx_callback=self.rx_packet, port=self.zmq_port)
        self.status_poll = reactor.callLater(5, self.poll_for_status)
        return succeed('Done')
Exemplo n.º 4
0
    def _finish_reboot(self, timeout, previous_oper_status,
                       previous_conn_status):
        super(AdtranOltHandler,
              self)._finish_reboot(timeout, previous_oper_status,
                                   previous_conn_status)

        self.zmq_client = AdtranZmqClient(self.ip_address,
                                          rx_callback=self.rx_packet,
                                          port=self.zmq_port)
        self.status_poll = reactor.callLater(1, self.poll_for_status)
Exemplo n.º 5
0
    def send_proxied_message(self, proxy_address, msg):
        self.log.debug('sending-proxied-message', msg=msg)

        if isinstance(msg, Packet):
            msg = str(msg)

        if self.pon_agent is not None:
            pon_id, onu_id = self._proxy_address_to_pon_onu_id(proxy_address)

            pon = self.southbound_ports.get(pon_id)

            if pon is not None and pon.enabled:
                onu = pon.onu(onu_id)

                if onu is not None and onu.enabled:
                    data = AdtranZmqClient.encode_omci_message(msg, pon_id, onu_id,
                                                               self.is_async_control)
                    try:
                        self.pon_agent.send(data)

                    except Exception as e:
                        self.log.exception('pon-agent-send', pon_id=pon_id, onu_id=onu_id, e=e)
                else:
                    self.log.debug('onu-invalid-or-disabled', pon_id=pon_id, onu_id=onu_id)
            else:
                self.log.debug('pon-invalid-or-disabled', pon_id=pon_id)
Exemplo n.º 6
0
    def complete_device_specific_activation(self, device, reconciling):
        """
        Perform an initial network operation to discover the device hardware
        and software version. Serial Number would be helpful as well.

        This method is called from within the base class's activate generator.

        :param device: A voltha.Device object, with possible device-type
                specific extensions. Such extensions shall be described as part of
                the device type specification returned by device_types().

        :param reconciling: (boolean) True if taking over for another VOLTHA
        """
        # Make sure configured for ZMQ remote access
        self._ready_zmq()

        # ZeroMQ clients
        self.pon_agent = AdtranZmqClient(self.ip_address, port=self.pon_agent_port, rx_callback=self.rx_pa_packet)
        self.pio_agent = AdtranZmqClient(self.ip_address, port=self.pio_port, rx_callback=self.rx_pio_packet)

        # Download support
        self._download_deferred = reactor.callLater(0, self._get_download_protocols)

        # Register for adapter messages
        self.adapter_agent.register_for_inter_adapter_messages()

        # PON Status
        self.status_poll = reactor.callLater(5, self.poll_for_status)
        return succeed('Done')
Exemplo n.º 7
0
    def send_proxied_message(self, proxy_address, msg):
        self.log.debug('sending-proxied-message', msg=msg)

        if isinstance(msg, Packet):
            msg = str(msg)

        if self.zmq_client is not None:
            pon_id, onu_id = self._proxy_address_to_pon_onu_id(proxy_address)

            pon = self.southbound_ports.get(pon_id)

            if pon is not None and pon.enabled:
                onu = pon.onu(onu_id)

                if onu is not None and onu.enabled:
                    data = AdtranZmqClient.encode_omci_message(
                        msg, pon_id, onu_id)

                    try:
                        self.zmq_client.send(data)

                    except Exception as e:
                        self.log.exception('zmqClient-send',
                                           pon_id=pon_id,
                                           onu_id=onu_id,
                                           e=e)
                else:
                    self.log.debug('onu-invalid-or-disabled',
                                   pon_id=pon_id,
                                   onu_id=onu_id)
            else:
                self.log.debug('pon-invalid-or-disabled', pon_id=pon_id)
Exemplo n.º 8
0
    def reenable(self, done_deferred=None):
        super(AdtranOltHandler, self).reenable(done_deferred=done_deferred)

        self._ready_zmq()
        self.pon_agent = AdtranZmqClient(self.ip_address, port=self.pon_agent_port, rx_callback=self.rx_pa_packet)
        self.pio_agent = AdtranZmqClient(self.ip_address, port=self.pio_port, rx_callback=self.rx_pio_packet)

        # Register for adapter messages
        self.adapter_agent.register_for_inter_adapter_messages()

        self.status_poll = reactor.callLater(1, self.poll_for_status)
Exemplo n.º 9
0
    def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
        super(AdtranOltHandler, self)._finish_reboot(timeout, previous_oper_status, previous_conn_status)

        self._ready_zmq()

        # Download support
        self._download_deferred = reactor.callLater(0, self._get_download_protocols)

        # Register for adapter messages
        self.adapter_agent.register_for_inter_adapter_messages()

        self.pon_agent = AdtranZmqClient(self.ip_address, port=self.pon_agent_port, rx_callback=self.rx_pa_packet)
        self.pio_agent = AdtranZmqClient(self.ip_address, port=self.pio_port, rx_callback=self.rx_pio_packet)

        self.status_poll = reactor.callLater(5, self.poll_for_status)
Exemplo n.º 10
0
    def rx_pa_packet(self, packets):
        self.log.debug('rx-pon-agent-packet')

        for packet in packets:
            try:
                pon_id, onu_id, msg_bytes, is_omci = \
                    AdtranZmqClient.decode_pon_agent_packet(packet,
                                                            self.is_async_control)
                if is_omci:
                    proxy_address = self._pon_onu_id_to_proxy_address(pon_id, onu_id)

                    if proxy_address is not None:
                        self.adapter_agent.receive_proxied_message(proxy_address, msg_bytes)

            except Exception as e:
                self.log.exception('rx-pon-agent-packet', e=e)
Exemplo n.º 11
0
    def rx_pio_packet(self, packets):
        self.log.debug('rx-packet-in', type=type(packets), data=packets)
        assert isinstance(packets, list), 'Expected a list of packets'

        if self.logical_device_id is not None:
            for packet in packets:
                try:
                    port_no, evc_map, packet = AdtranZmqClient.decode_packet_in_message(packet)
                    # packet.show()

                    logical_port_no = self._compute_logical_port_no(port_no, evc_map, packet)

                    self.adapter_agent.send_packet_in(logical_device_id=self.logical_device_id,
                                                      logical_port_no=logical_port_no,
                                                      packet=str(packet))
                except Exception as e:
                    self.log.exception('rx_pio_packet', e=e)
Exemplo n.º 12
0
    def send_proxied_message(self, proxy_address, msg):
        self.log.debug('sending-proxied-message', msg=msg)

        if isinstance(msg, Packet):
            msg = str(msg)

        if self.zmq_client is not None:
            pon_id = self._channel_id_to_pon_id(proxy_address.channel_id, proxy_address.onu_id)
            onu_id = proxy_address.onu_id

            data = AdtranZmqClient.encode_omci_message(msg, pon_id, onu_id)

            try:
                self.zmq_client.send(data)

            except Exception as e:
                self.log.exception('zmqClient.send', e=e)
                raise
Exemplo n.º 13
0
    def rx_packet(self, message):
        try:
            self.log.debug('rx_packet')

            pon_id, onu_id, msg, is_omci = AdtranZmqClient.decode_packet(message)

            if is_omci:
                proxy_address = Device.ProxyAddress(device_id=self.device_id,
                                                    channel_id=self.get_channel_id(pon_id, onu_id),
                                                    onu_id=onu_id)

                self.adapter_agent.receive_proxied_message(proxy_address, msg)
            else:
                pass  # TODO: Packet in support not yet supported
                # self.adapter_agent.send_packet_in(logical_device_id=logical_device_id,
                #                                   logical_port_no=cvid,  # C-VID encodes port no
                #                                   packet=str(msg))
        except Exception as e:
            self.log.exception('rx_packet', e=e)
Exemplo n.º 14
0
    def send_proxied_message(self, proxy_address, msg):
        self.log.info('sending-proxied-message: message type: {}'.format(
            type(msg)))

        if isinstance(msg, Packet):
            msg = str(msg)

        if self.zmq_client is not None:
            pon_id = self._channel_id_to_pon_id(proxy_address.channel_id,
                                                proxy_address.onu_id)
            onu_id = proxy_address.onu_id

            data = AdtranZmqClient.encode_omci_message(msg, pon_id, onu_id)

            try:
                self.zmq_client.send(data)

            except Exception as e:
                self.log.info('zmqClient.send exception', exc=str(e))
                raise
Exemplo n.º 15
0
class AdtranOltHandler(AdtranDeviceHandler):
    """
    The OLT Handler is used to wrap a single instance of a 10G OLT 1-U pizza-box
    """
    MIN_OLT_HW_VERSION = datetime.datetime(2017, 1, 5)

    # Full table output

    GPON_OLT_HW_URI = '/restconf/data/gpon-olt-hw'
    GPON_OLT_HW_STATE_URI = '/restconf/data/gpon-olt-hw:olt-state'
    GPON_PON_CONFIG_LIST_URI = '/restconf/data/gpon-olt-hw:olt/pon'

    # Per-PON info

    GPON_PON_PON_STATE_URI = '/restconf/data/gpon-olt-hw:olt-state/pon={}'  # .format(pon)
    GPON_PON_CONFIG_URI = '/restconf/data/gpon-olt-hw:olt/pon={}'  # .format(pon)
    GPON_PON_ONU_CONFIG_URI = '/restconf/data/gpon-olt-hw:olt/pon={}/onus/onu'  # .format(pon)

    GPON_PON_DISCOVER_ONU = '/restconf/operations/gpon-olt-hw:discover-onu'

    def __init__(self,
                 adapter,
                 device_id,
                 username="",
                 password="",
                 timeout=20,
                 initial_port_state=True):
        super(AdtranOltHandler, self).__init__(adapter,
                                               device_id,
                                               username=username,
                                               password=password,
                                               timeout=timeout)
        self.gpon_olt_hw_revision = None
        self.status_poll = None
        self.status_poll_interval = 5.0
        self.status_poll_skew = self.status_poll_interval / 10
        self.initial_port_state = AdminState.ENABLED if initial_port_state else AdminState.DISABLED
        self.initial_onu_state = AdminState.DISABLED

        self.zmq_client = None
        self.nc_client = None

    def __del__(self):
        # OLT Specific things here.
        #
        # If you receive this during 'enable' of the object, you probably threw an
        # uncaught exception which trigged an errback in the VOLTHA core.

        d, self.status_poll = self.status_poll, None

        # TODO Any OLT device specific cleanup here
        #     def get_channel(self):
        #         if self.channel is None:
        #             device = self.adapter_agent.get_device(self.device_id)
        #         return self.channel
        #
        # Clean up base class as well

        AdtranDeviceHandler.__del__(self)

    def __str__(self):
        return "AdtranOltHandler: {}:{}".format(self.ip_address,
                                                self.rest_port)

    @inlineCallbacks
    def enumerate_northbound_ports(self, device):
        """
        Enumerate all northbound ports of this device.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :return: (Deferred or None).
        """
        # TODO: For now, hard code some JSON. Eventually will be XML from NETConf

        ports = [{
            'port_no': 1,
            'admin_state': AdminState.ENABLED,
            'oper_status': OperStatus.ACTIVE,
            'ofp_state': OFPPS_LIVE,
            'ofp_capabilities': OFPPF_100GB_FD | OFPPF_FIBER,
            'current_speed': OFPPF_100GB_FD,
            'max_speed': OFPPF_100GB_FD
        }, {
            'port_no': 2,
            'admin_state': AdminState.ENABLED,
            'oper_status': OperStatus.ACTIVE,
            'ofp_state': OFPPS_LIVE,
            'ofp_capabilities': OFPPF_100GB_FD | OFPPF_FIBER,
            'current_speed': OFPPF_100GB_FD,
            'max_speed': OFPPF_100GB_FD
        }, {
            'port_no': 3,
            'admin_state': AdminState.ENABLED,
            'oper_status': OperStatus.ACTIVE,
            'ofp_state': OFPPS_LIVE,
            'ofp_capabilities': OFPPF_100GB_FD | OFPPF_FIBER,
            'current_speed': OFPPF_100GB_FD,
            'max_speed': OFPPF_100GB_FD
        }, {
            'port_no': 4,
            'admin_state': AdminState.ENABLED,
            'oper_status': OperStatus.ACTIVE,
            'ofp_state': OFPPS_LIVE,
            'ofp_capabilities': OFPPF_100GB_FD | OFPPF_FIBER,
            'current_speed': OFPPF_100GB_FD,
            'max_speed': OFPPF_100GB_FD
        }]

        yield returnValue(ports)

    def process_northbound_ports(self, device, results):
        """
        Process the results from the 'enumerate_northbound_ports' method.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :param results: Results from the 'enumerate_northbound_ports' method that
                you implemented. The type and contents are up to you to
        :return: (Deferred or None).
        """
        from nni_port import NniPort

        for port in results:
            port_no = port['port_no']
            self.log.info('Processing northbound port {}/{}'.format(
                port_no, port['port_no']))
            assert port_no
            assert port_no not in self.northbound_ports
            self.northbound_ports[port_no] = NniPort(self, **port)

        self.num_northbound_ports = len(self.northbound_ports)

    @inlineCallbacks
    def enumerate_southbound_ports(self, device):
        """
        Enumerate all southbound ports of this device.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :return: (Deferred or None).
        """
        ###############################################################################
        # Determine number of southbound ports. We know it is 16, but this keeps this
        # device adapter generic for our other OLTs up to this point.

        self.startup = self.rest_client.request('GET',
                                                self.GPON_PON_CONFIG_LIST_URI,
                                                'pon-config')
        results = yield self.startup
        returnValue(results)

    def process_southbound_ports(self, device, results):
        """
        Process the results from the 'enumerate_southbound_ports' method.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :param results: Results from the 'enumerate_southbound_ports' method that
                you implemented. The type and contents are up to you to
        :return: (Deferred or None).
        """
        from pon_port import PonPort

        for pon in results:

            # Number PON Ports after the NNI ports
            pon_id = pon['pon-id']
            log.info('Processing pon port {}'.format(pon_id))

            assert pon_id not in self.southbound_ports

            admin_state = AdminState.ENABLED if pon.get(
                'enabled', PonPort.DEFAULT_ENABLED) else AdminState.DISABLED

            self.southbound_ports[pon_id] = PonPort(
                pon_id,
                self._pon_id_to_port_number(pon_id),
                self,
                admin_state=admin_state)

            # TODO: For now, limit number of PON ports to make debugging easier

            if len(self.southbound_ports) >= self.max_ports:
                break

        self.num_southbound_ports = len(self.southbound_ports)

    def complete_device_specific_activation(self, device, results):
        """
        Perform an initial network operation to discover the device hardware
        and software version. Serial Number would be helpful as well.

        This method is called from within the base class's activate generator.

        :param device: A voltha.Device object, with possible device-type
                specific extensions. Such extensions shall be described as part of
                the device type specification returned by device_types().

        :param results: (dict) original adtran-hello RESTCONF results body
        """
        #
        # For the pizzabox OLT, periodically query the OLT state of all PONs. This
        # is simpler then having each PON port do its own poll.  From this, we can:
        #
        # o Discover any new or missing ONT/ONUs
        #
        # o TODO Discover any LOS for any ONT/ONUs
        #
        # o TODO Update some PON level statistics

        self.zmq_client = AdtranZmqClient(self.ip_address, self.rx_packet)
        # self.nc_client = manager.connect(host='',  # self.ip_address,
        #                                  username=self.rest_username,
        #                                  password=self.rest_password,
        #                                  hostkey_verify=False,
        #                                  allow_agent=False,
        #                                  look_for_keys=False)

        self.status_poll = reactor.callLater(1, self.poll_for_status)
        return None

    def rx_packet(self, message):
        try:
            self.log.info('rx_Packet: Message from ONU')

            pon_id, onu_id, msg, is_omci = AdtranZmqClient.decode_packet(
                message)

            if is_omci:
                proxy_address = Device.ProxyAddress(
                    device_id=self.device_id,
                    channel_id=self._get_channel_id(pon_id, onu_id),
                    onu_id=onu_id)

                self.adapter_agent.receive_proxied_message(proxy_address, msg)
            else:
                pass  # TODO: Packet in support not yet supported
                # self.adapter_agent.send_packet_in(logical_device_id=logical_device_id,
                #                                   logical_port_no=cvid,  # C-VID encodes port no
                #                                   packet=str(msg))
        except Exception as e:
            self.log.exception('Exception during RX Packet processing', e=e)

    def poll_for_status(self):
        self.log.debug('Initiating status poll')

        device = self.adapter_agent.get_device(self.device_id)

        if device.admin_state == AdminState.ENABLED:
            uri = AdtranOltHandler.GPON_OLT_HW_STATE_URI
            name = 'pon-status-poll'
            self.startup = self.rest_client.request('GET', uri, name=name)
            self.startup.addBoth(self.status_poll_complete)

    def status_poll_complete(self, results):
        """
        Results of the status poll
        
        :param results: 
        """
        self.log.debug('Status poll results: {}'.format(
            pprint.PrettyPrinter().pformat(results)))

        if isinstance(results, dict) and 'pon' in results:
            try:
                for pon_id, pon in OltState(results).pons.iteritems():
                    if pon_id in self.southbound_ports:
                        self.southbound_ports[pon_id].process_status_poll(pon)

            except Exception as e:
                self.log.exception(
                    'Exception during PON status poll processing', e=e)
        else:
            self.log.warning('Had some kind of polling error')

        # Reschedule

        delay = self.status_poll_interval
        delay += random.uniform(-delay / 10, delay / 10)

        self.status_poll = reactor.callLater(delay, self.poll_for_status)

    @inlineCallbacks
    def deactivate(self, device):
        # OLT Specific things here

        d, self.startup = self.startup, None
        if d is not None:
            d.cancel()

        self.pons.clear()

        # TODO: Any other? OLT specific deactivate steps

        # Call into base class and have it clean up as well
        super(AdtranOltHandler, self).deactivate(device)

    @inlineCallbacks
    def update_flow_table(self, flows, device):
        self.log.info('bulk-flow-update', device_id=device.id, flows=flows)
        raise NotImplementedError('TODO: Not yet implemented')

    @inlineCallbacks
    def send_proxied_message(self, proxy_address, msg):
        self.log.info('sending-proxied-message: message type: {}'.format(
            type(msg)))

        if isinstance(msg, Packet):
            msg = str(msg)

        if self.zmq_client is not None:
            pon_id = self._channel_id_to_pon_id(proxy_address.channel_id,
                                                proxy_address.onu_id)
            onu_id = proxy_address.onu_id

            data = AdtranZmqClient.encode_omci_message(msg, pon_id, onu_id)

            try:
                self.zmq_client.send(data)

            except Exception as e:
                self.log.info('zmqClient.send exception', exc=str(e))
                raise

    @staticmethod
    def is_gpon_olt_hw(content):
        """
        If the hello content

        :param content: (dict) Results of RESTCONF adtran-hello GET request
        :return: (string) GPON OLT H/w RESTCONF revision number or None on error/not GPON
        """
        for item in content.get('module-info', None):
            if item.get('module-name') == 'gpon-olt-hw':
                return AdtranDeviceHandler.parse_module_revision(
                    item.get('revision', None))
        return None

    def _onu_offset(self, onu_id):
        return self.num_northbound_ports + self.num_southbound_ports + onu_id

    def _get_channel_id(self, pon_id, onu_id):
        from pon_port import PonPort

        return self._onu_offset(onu_id) + (pon_id * PonPort.MAX_ONUS_SUPPORTED)

    def _channel_id_to_pon_id(self, channel_id, onu_id):
        from pon_port import PonPort

        return (channel_id -
                self._onu_offset(onu_id)) / PonPort.MAX_ONUS_SUPPORTED

    def _pon_id_to_port_number(self, pon_id):
        return pon_id + 1 + self.num_northbound_ports
Exemplo n.º 16
0
class AdtranOltHandler(AdtranDeviceHandler, AdtranOltXPON):
    """
    The OLT Handler is used to wrap a single instance of a 10G OLT 1-U pizza-box
    """
    MIN_OLT_HW_VERSION = datetime.datetime(2017, 1, 5)

    # Full table output

    GPON_OLT_HW_URI = '/restconf/data/gpon-olt-hw'
    GPON_OLT_HW_STATE_URI = GPON_OLT_HW_URI + ':olt-state'
    GPON_OLT_HW_CONFIG_URI = GPON_OLT_HW_URI + ':olt'
    GPON_PON_CONFIG_LIST_URI = GPON_OLT_HW_CONFIG_URI + '/pon'

    # Per-PON info

    GPON_PON_STATE_URI = GPON_OLT_HW_STATE_URI + '/pon={}'        # .format(pon-id)
    GPON_PON_CONFIG_URI = GPON_PON_CONFIG_LIST_URI + '={}'        # .format(pon-id)

    GPON_ONU_CONFIG_LIST_URI = GPON_PON_CONFIG_URI + '/onus/onu'  # .format(pon-id)
    GPON_ONU_CONFIG_URI = GPON_ONU_CONFIG_LIST_URI + '={}'        # .format(pon-id,onu-id)

    GPON_TCONT_CONFIG_LIST_URI = GPON_ONU_CONFIG_URI + '/t-conts/t-cont'  # .format(pon-id,onu-id)
    GPON_TCONT_CONFIG_URI = GPON_TCONT_CONFIG_LIST_URI + '={}'            # .format(pon-id,onu-id,alloc-id)

    GPON_GEM_CONFIG_LIST_URI = GPON_ONU_CONFIG_URI + '/gem-ports/gem-port'  # .format(pon-id,onu-id)
    GPON_GEM_CONFIG_URI = GPON_GEM_CONFIG_LIST_URI + '={}'                  # .format(pon-id,onu-id,gem-id)

    GPON_PON_DISCOVER_ONU = '/restconf/operations/gpon-olt-hw:discover-onu'

    BASE_ONU_OFFSET = 64

    def __init__(self, **kwargs):
        super(AdtranOltHandler, self).__init__(**kwargs)

        self.status_poll = None
        self.status_poll_interval = 5.0
        self.status_poll_skew = self.status_poll_interval / 10
        self.pon_agent = None
        self.pio_agent = None
        self.ssh_deferred = None
        self._system_id = None
        self._download_protocols = None
        self._download_deferred = None
        self._downloads = {}        # name -> Download obj

    def __del__(self):
        # OLT Specific things here.
        #
        # If you receive this during 'enable' of the object, you probably threw an
        # uncaught exception which trigged an errback in the VOLTHA core.

        d, self.status_poll = self.status_poll, None

        # TODO Any OLT device specific cleanup here
        #     def get_channel(self):
        #         if self.channel is None:
        #             device = self.adapter_agent.get_device(self.device_id)
        #         return self.channel
        #
        # Clean up base class as well

        AdtranDeviceHandler.__del__(self)

    def _cancel_deferred(self):
        d1, self.status_poll = self.status_poll, None
        d2, self.ssh_deferred = self.ssh_deferred, None
        d3, self._download_deferred = self._download_deferred, None

        for d in [d1, d2, d3]:
            try:
                if d is not None and not d.called:
                    d.cancel()
            except:
                pass

    def __str__(self):
        return "AdtranOltHandler: {}".format(self.ip_address)

    @property
    def system_id(self):
        return self._system_id

    @system_id.setter
    def system_id(self, value):
        if self._system_id != value:
            self._system_id = value

            data = json.dumps({'olt-id': str(value)})
            uri = AdtranOltHandler.GPON_OLT_HW_CONFIG_URI
            self.rest_client.request('PATCH', uri, data=data, name='olt-system-id')

    @inlineCallbacks
    def get_device_info(self, device):
        """
        Perform an initial network operation to discover the device hardware
        and software version. Serial Number would be helpful as well.

        Upon successfully retrieving the information, remember to call the
        'start_heartbeat' method to keep in contact with the device being managed

        :param device: A voltha.Device object, with possible device-type
                specific extensions. Such extensions shall be described as part of
                the device type specification returned by device_types().
        """
        from codec.physical_entities_state import PhysicalEntitiesState
        # TODO: After a CLI 'reboot' command, the device info may get messed up (prints labels and not values)  Enter device and type 'show'
        device = {
            'model': 'n/a',
            'hardware_version': 'n/a',
            'serial_number': 'n/a',
            'vendor': 'Adtran, Inc.',
            'firmware_version': 'n/a',
            'running-revision': 'n/a',
            'candidate-revision': 'n/a',
            'startup-revision': 'n/a',
        }
        if self.is_virtual_olt:
            returnValue(device)

        try:
            pe_state = PhysicalEntitiesState(self.netconf_client)
            self.startup = pe_state.get_state()
            results = yield self.startup

            if results.ok:
                modules = pe_state.get_physical_entities('adtn-phys-mod:module')

                if isinstance(modules, list):
                    module = modules[0]

                    name = str(module.get('model-name', 'n/a')).translate(None, '?')
                    model = str(module.get('model-number', 'n/a')).translate(None, '?')

                    device['model'] = '{} - {}'.format(name, model) if len(name) > 0 else \
                        module.get('parent-entity', 'n/a')
                    device['hardware_version'] = str(module.get('hardware-revision',
                                                                'n/a')).translate(None, '?')
                    device['serial_number'] = str(module.get('serial-number',
                                                             'n/a')).translate(None, '?')
                    device['firmware_version'] = str(device.get('firmware-revision',
                                                                'unknown')).translate(None, '?')
                    if 'software' in module:
                        if 'software' in module['software']:
                            software = module['software']['software']
                            device['running-revision'] = str(software.get('running-revision',
                                                                          'n/a')).translate(None, '?')
                            device['candidate-revision'] = str(software.get('candidate-revision',
                                                                            'n/a')).translate(None, '?')
                            device['startup-revision'] = str(software.get('startup-revision',
                                                                          'n/a')).translate(None, '?')
        except Exception as e:
            self.log.exception('get-pe-state', e=e)

        returnValue(device)

    @inlineCallbacks
    def enumerate_northbound_ports(self, device):
        """
        Enumerate all northbound ports of this device.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :return: (Deferred or None).
        """
        from net.rcmd import RCmd
        try:
            # Also get the MAC Address for the OLT
            command = "ip -o link | grep eth0 | sed -n -e 's/^.*ether //p' | awk '{ print $1 }'"
            rcmd = RCmd(self.ip_address, self.netconf_username, self.netconf_password,
                        command)
            self.default_mac_addr = yield rcmd.execute()
            self.log.info("mac-addr", mac_addr=self.default_mac_addr)

        except Exception as e:
            log.exception('mac-address', e=e)
            raise

        try:
            from codec.ietf_interfaces import IetfInterfacesState
            from nni_port import MockNniPort

            ietf_interfaces = IetfInterfacesState(self.netconf_client)

            if self.is_virtual_olt:
                results = MockNniPort.get_nni_port_state_results()
            else:
                self.startup = ietf_interfaces.get_state()
                results = yield self.startup

            ports = ietf_interfaces.get_nni_port_entries(results)
            yield returnValue(ports)

        except Exception as e:
            log.exception('enumerate_northbound_ports', e=e)
            raise

    def process_northbound_ports(self, device, results):
        """
        Process the results from the 'enumerate_northbound_ports' method.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :param results: Results from the 'enumerate_northbound_ports' method that
                you implemented. The type and contents are up to you to
        :return: (Deferred or None).
        """
        from nni_port import NniPort, MockNniPort

        for port in results:
            port_no = port['port_no']
            self.log.info('processing-nni', port_no=port_no, name=port['port_no'])
            assert port_no, 'Port number not found'
            assert port_no not in self.northbound_ports, 'Port number is not a northbound port'
            self.northbound_ports[port_no] = NniPort(self, **port) if not self.is_virtual_olt \
                else MockNniPort(self, **port)

            # TODO: For now, limit number of NNI ports to make debugging easier
            if len(self.northbound_ports) >= self.max_nni_ports:
                break

        self.num_northbound_ports = len(self.northbound_ports)

    @inlineCallbacks
    def enumerate_southbound_ports(self, device):
        """
        Enumerate all southbound ports of this device.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :return: (Deferred or None).
        """
        ###############################################################################
        # Determine number of southbound ports. We know it is 16, but this keeps this
        # device adapter generic for our other OLTs up to this point.

        self.startup = self.rest_client.request('GET', self.GPON_PON_CONFIG_LIST_URI, 'pon-config')
        results = yield self.startup
        returnValue(results)

    def process_southbound_ports(self, device, results):
        """
        Process the results from the 'enumerate_southbound_ports' method.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :param results: Results from the 'enumerate_southbound_ports' method that
                you implemented. The type and contents are up to you to
        :return: (Deferred or None).
        """
        from pon_port import PonPort

        for pon in results:
            # Number PON Ports after the NNI ports
            pon_id = pon['pon-id']
            log.info('processing-pon-port', pon_id=pon_id)
            assert pon_id not in self.southbound_ports,\
                'Pon ID not found in southbound ports'

            self.southbound_ports[pon_id] = PonPort(pon_id,
                                                    self._pon_id_to_port_number(pon_id),
                                                    self)
            if self.autoactivate:
                self.southbound_ports[pon_id].downstream_fec_enable = True
                self.southbound_ports[pon_id].upstream_fec_enable = True

        self.num_southbound_ports = len(self.southbound_ports)

    def pon(self, pon_id):
        return self.southbound_ports.get(pon_id)

    def complete_device_specific_activation(self, device, reconciling):
        """
        Perform an initial network operation to discover the device hardware
        and software version. Serial Number would be helpful as well.

        This method is called from within the base class's activate generator.

        :param device: A voltha.Device object, with possible device-type
                specific extensions. Such extensions shall be described as part of
                the device type specification returned by device_types().

        :param reconciling: (boolean) True if taking over for another VOLTHA
        """
        # Make sure configured for ZMQ remote access
        self._ready_zmq()

        # ZeroMQ clients
        self.pon_agent = AdtranZmqClient(self.ip_address, port=self.pon_agent_port, rx_callback=self.rx_pa_packet)
        self.pio_agent = AdtranZmqClient(self.ip_address, port=self.pio_port, rx_callback=self.rx_pio_packet)

        # Download support
        self._download_deferred = reactor.callLater(0, self._get_download_protocols)

        # Register for adapter messages
        self.adapter_agent.register_for_inter_adapter_messages()

        # PON Status
        self.status_poll = reactor.callLater(5, self.poll_for_status)
        return succeed('Done')

    def on_heatbeat_alarm(self, active):
        if not active:
            self._ready_zmq()

    @inlineCallbacks
    def _get_download_protocols(self):
        if self._download_protocols is None:
            try:
                config = '<filter>' + \
                          '<file-servers-state xmlns="http://www.adtran.com/ns/yang/adtran-file-servers">' + \
                           '<profiles>' + \
                            '<supported-protocol/>' + \
                           '</profiles>' + \
                          '</file-servers-state>' + \
                         '</filter>'

                results = yield self.netconf_client.get(config)

                result_dict = xmltodict.parse(results.data_xml)
                entries = result_dict['data']['file-servers-state']['profiles']['supported-protocol']
                self._download_protocols = [entry['#text'].split(':')[-1] for entry in entries
                                            if '#text' in entry]

            except Exception as e:
                self.log.exception('protocols', e=e)
                self._download_protocols = None
                self._download_deferred = reactor.callLater(10, self._get_download_protocols)

    @inlineCallbacks
    def _ready_zmq(self):
        from net.rcmd import RCmd
        # Check for port status
        command = 'netstat -pan | grep -i 0.0.0.0:{} |  wc -l'.format(self.pon_agent_port)
        rcmd = RCmd(self.ip_address, self.netconf_username, self.netconf_password, command)

        try:
            self.log.debug('check-request', command=command)
            results = yield rcmd.execute()
            self.log.info('check-results', results=results, result_type=type(results))
            create_it = int(results) != 1

        except Exception as e:
            self.log.exception('find', e=e)
            create_it = True

        if create_it:
            next_run = 15
            command = 'mkdir -p /etc/pon_agent; touch /etc/pon_agent/debug.conf; '
            command += 'ps -ae | grep -i ngpon2_agent; '
            command += 'service_supervisor stop ngpon2_agent; service_supervisor start ngpon2_agent; '
            command += 'ps -ae | grep -i ngpon2_agent'

            rcmd = RCmd(self.ip_address, self.netconf_username, self.netconf_password, command)

            try:
                self.log.debug('create-request', command=command)
                results = yield rcmd.execute()
                self.log.info('create-results', results=results, result_type=type(results))

            except Exception as e:
                self.log.exception('mkdir', e=e)
        else:
            next_run = 0

        if next_run > 0:
            self.ssh_deferred = reactor.callLater(next_run, self._ready_zmq)

    def disable(self):
        self._cancel_deferred()

        # Drop registration for adapter messages
        self.adapter_agent.unregister_for_inter_adapter_messages()

        c, self.pon_agent = self.pon_agent, None
        if c is not None:
            try:
                c.shutdown()
            except:
                pass

        super(AdtranOltHandler, self).disable()

    def reenable(self, done_deferred=None):
        super(AdtranOltHandler, self).reenable(done_deferred=done_deferred)

        self._ready_zmq()
        self.pon_agent = AdtranZmqClient(self.ip_address, port=self.pon_agent_port, rx_callback=self.rx_pa_packet)
        self.pio_agent = AdtranZmqClient(self.ip_address, port=self.pio_port, rx_callback=self.rx_pio_packet)

        # Register for adapter messages
        self.adapter_agent.register_for_inter_adapter_messages()

        self.status_poll = reactor.callLater(1, self.poll_for_status)

    def reboot(self):
        self._cancel_deferred()

        c, self.pon_agent = self.pon_agent, None
        if c is not None:
            c.shutdown()

        # Drop registration for adapter messages
        self.adapter_agent.unregister_for_inter_adapter_messages()

        # Download supported protocols may change (if new image gets activated)
        self._download_protocols = None

        super(AdtranOltHandler, self).reboot()

    def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
        super(AdtranOltHandler, self)._finish_reboot(timeout, previous_oper_status, previous_conn_status)

        self._ready_zmq()

        # Download support
        self._download_deferred = reactor.callLater(0, self._get_download_protocols)

        # Register for adapter messages
        self.adapter_agent.register_for_inter_adapter_messages()

        self.pon_agent = AdtranZmqClient(self.ip_address, port=self.pon_agent_port, rx_callback=self.rx_pa_packet)
        self.pio_agent = AdtranZmqClient(self.ip_address, port=self.pio_port, rx_callback=self.rx_pio_packet)

        self.status_poll = reactor.callLater(5, self.poll_for_status)

    def delete(self):
        self._cancel_deferred()

        # Drop registration for adapter messages
        self.adapter_agent.unregister_for_inter_adapter_messages()

        c, self.pon_agent = self.pon_agent, None
        if c is not None:
            c.shutdown()

        super(AdtranOltHandler, self).delete()

    def rx_pa_packet(self, packets):
        self.log.debug('rx-pon-agent-packet')

        for packet in packets:
            try:
                pon_id, onu_id, msg_bytes, is_omci = \
                    AdtranZmqClient.decode_pon_agent_packet(packet,
                                                            self.is_async_control)
                if is_omci:
                    proxy_address = self._pon_onu_id_to_proxy_address(pon_id, onu_id)

                    if proxy_address is not None:
                        self.adapter_agent.receive_proxied_message(proxy_address, msg_bytes)

            except Exception as e:
                self.log.exception('rx-pon-agent-packet', e=e)

    def _compute_logical_port_no(self, port_no, evc_map, packet):
        logical_port_no = None

        if self.is_pon_port(port_no):
            pon = self.get_southbound_port(port_no)

        elif self.is_nni_port(port_no):
            nni = self.get_northbound_port(port_no)
            logical_port = nni.get_logical_port() if nni is not None else None
            logical_port_no = logical_port.ofp_port.port_no if logical_port is not None else None

        # TODO: Need to decode base on port_no & evc_map
        return logical_port_no

    def rx_pio_packet(self, packets):
        self.log.debug('rx-packet-in', type=type(packets), data=packets)
        assert isinstance(packets, list), 'Expected a list of packets'

        if self.logical_device_id is not None:
            for packet in packets:
                try:
                    port_no, evc_map, packet = AdtranZmqClient.decode_packet_in_message(packet)
                    # packet.show()

                    logical_port_no = self._compute_logical_port_no(port_no, evc_map, packet)

                    self.adapter_agent.send_packet_in(logical_device_id=self.logical_device_id,
                                                      logical_port_no=logical_port_no,
                                                      packet=str(packet))
                except Exception as e:
                    self.log.exception('rx_pio_packet', e=e)

    def poll_for_status(self):
        self.log.debug('Initiating-status-poll')

        device = self.adapter_agent.get_device(self.device_id)

        if device.admin_state == AdminState.ENABLED and\
                device.oper_status != OperStatus.ACTIVATING and\
                self.rest_client is not None:
            uri = AdtranOltHandler.GPON_OLT_HW_STATE_URI
            name = 'pon-status-poll'
            self.status_poll = self.rest_client.request('GET', uri, name=name)
            self.status_poll.addBoth(self.status_poll_complete)
        else:
            self.status_poll = reactor.callLater(0, self.status_poll_complete, 'inactive')

    def status_poll_complete(self, results):
        """
        Results of the status poll
        :param results:
        """
        from pon_port import PonPort

        if isinstance(results, dict) and 'pon' in results:
            try:
                self.log.debug('status-success')
                for pon_id, pon in OltState(results).pons.iteritems():
                    pon_port = self.southbound_ports.get(pon_id, None)

                    if pon_port is not None and pon_port.state == PonPort.State.RUNNING:
                        pon_port.process_status_poll(pon)

            except Exception as e:
                self.log.exception('PON-status-poll', e=e)

        # Reschedule

        delay = self.status_poll_interval
        delay += random.uniform(-delay / 10, delay / 10)

        self.status_poll = reactor.callLater(delay, self.poll_for_status)

    @inlineCallbacks
    def update_flow_table(self, flows, device):
        """
        Update the flow table on the OLT.  If an existing flow is not in the list, it needs
        to be removed from the device.

        :param flows: List of flows that should be installed upon completion of this function
        :param device: A voltha.Device object, with possible device-type
                       specific extensions.
        """
        self.log.debug('bulk-flow-update', num_flows=len(flows),
                       device_id=device.id, flows=flows)

        valid_flows = []

        for flow in flows:
            try:
                # Try to create an EVC.
                #
                # The first result is the flow entry that was created. This could be a match to an
                # existing flow since it is a bulk update.  None is returned only if no match to
                # an existing entry is found and decode failed (unsupported field)
                #
                # The second result is the EVC this flow should be added to. This could be an
                # existing flow (so your adding another EVC-MAP) or a brand new EVC (no existing
                # EVC-MAPs).  None is returned if there are not a valid EVC that can be created YET.

                valid_flow, evc = FlowEntry.create(flow, self)

                if valid_flow is not None:
                    valid_flows.append(valid_flow.flow_id)

                if evc is not None:
                    try:
                        evc.schedule_install()
                        self.add_evc(evc)

                    except Exception as e:
                        evc.status = 'EVC Install Exception: {}'.format(e.message)
                        self.log.exception('EVC-install', e=e)

            except Exception as e:
                self.log.exception('bulk-flow-update-add', e=e)

        # Now drop all flows from this device that were not in this bulk update
        try:
            yield FlowEntry.drop_missing_flows(device.id, valid_flows)

        except Exception as e:
            self.log.exception('bulk-flow-update-remove', e=e)

    # @inlineCallbacks
    def send_proxied_message(self, proxy_address, msg):
        self.log.debug('sending-proxied-message', msg=msg)

        if isinstance(msg, Packet):
            msg = str(msg)

        if self.pon_agent is not None:
            pon_id, onu_id = self._proxy_address_to_pon_onu_id(proxy_address)

            pon = self.southbound_ports.get(pon_id)

            if pon is not None and pon.enabled:
                onu = pon.onu(onu_id)

                if onu is not None and onu.enabled:
                    data = AdtranZmqClient.encode_omci_message(msg, pon_id, onu_id,
                                                               self.is_async_control)
                    try:
                        self.pon_agent.send(data)

                    except Exception as e:
                        self.log.exception('pon-agent-send', pon_id=pon_id, onu_id=onu_id, e=e)
                else:
                    self.log.debug('onu-invalid-or-disabled', pon_id=pon_id, onu_id=onu_id)
            else:
                self.log.debug('pon-invalid-or-disabled', pon_id=pon_id)

    def get_channel_id(self, pon_id, onu_id):
        from pon_port import PonPort
        if ATT_NETWORK:
            if FIXED_ONU:
                return (onu_id * 120) + 2
            return 1 + onu_id + (pon_id * 120)

        if FIXED_ONU:
            return self._onu_offset(onu_id)
        return self._onu_offset(onu_id) + (pon_id * PonPort.MAX_ONUS_SUPPORTED)

    def _onu_offset(self, onu_id):
        # Start ONU's just past the southbound PON port numbers. Since ONU ID's start
        # at zero, add one
        assert AdtranOltHandler.BASE_ONU_OFFSET > (self.num_northbound_ports + self.num_southbound_ports + 1)
        return AdtranOltHandler.BASE_ONU_OFFSET + onu_id

    def _pon_onu_id_to_proxy_address(self, pon_id, onu_id):
        if pon_id in self.southbound_ports:
            pon = self.southbound_ports[pon_id]
            onu = pon.onu(onu_id)
            proxy_address = onu.proxy_address if onu is not None else None

        else:
            proxy_address = None

        return proxy_address

    def _proxy_address_to_pon_onu_id(self, proxy_address):
        """
        Convert the proxy address to the PON-ID and ONU-ID
        :param proxy_address: (ProxyAddress)
        :return: (tuple) pon-id, onu-id
        """
        onu_id = proxy_address.onu_id

        if self.autoactivate:
            # Legacy method
            pon_id = proxy_address.channel_group_id
        else:
            # xPON method
            pon_id = self._port_number_to_pon_id(proxy_address.channel_id)

        return pon_id, onu_id

    def _pon_id_to_port_number(self, pon_id):
        return pon_id + 1 + self.num_northbound_ports

    def _port_number_to_pon_id(self, port):
        return port - 1 - self.num_northbound_ports

    def is_pon_port(self, port):
        return self._port_number_to_pon_id(port) in self.southbound_ports

    def is_uni_port(self, port):
        return port >= self._onu_offset(0)  # TODO: Really need to rework this one...

    def get_southbound_port(self, port):
        pon_id = self._port_number_to_pon_id(port)
        return self.southbound_ports.get(pon_id, None)

    def get_northbound_port(self, port):
        return self.northbound_ports.get(port, None)

    def get_port_name(self, port):
        if self.is_nni_port(port):
            return self.northbound_ports[port].name

        if self.is_pon_port(port):
            return self.get_southbound_port(port).name

        if self.is_uni_port(port):
            return self.northbound_ports[port].name

        if self.is_logical_port(port):
            raise NotImplemented('TODO: Logical ports not yet supported')

    def _update_download_status(self, request, download):
        if download is not None:
            request.state = download.download_state
            request.reason = download.failure_reason
            request.image_state = download.image_state
            request.additional_info = download.additional_info
            request.downloaded_bytes = download.downloaded_bytes
        else:
            request.state = ImageDownload.DOWNLOAD_UNKNOWN
            request.reason = ImageDownload.UNKNOWN_ERROR
            request.image_state = ImageDownload.IMAGE_UNKNOWN
            request.additional_info = "Download request '{}' not found".format(request.name)
            request.downloaded_bytes = 0

        self.adapter_agent.update_image_download(request)

    def start_download(self, device, request, done):
        """
        This is called to request downloading a specified image into
        the standby partition of a device based on a NBI call.

        :param device: A Voltha.Device object.
        :param request: A Voltha.ImageDownload object.
        :param done: (Deferred) Deferred to fire when done
        :return: (Deferred) Shall be fired to acknowledge the download.
        """
        log.info('image_download', request=request)

        try:
            if request.name in self._downloads:
                raise Exception("Download request with name '{}' already exists".
                                format(request.name))
            try:
                download = Download.create(self, request, self._download_protocols)

            except Exception:
                request.additional_info = 'Download request creation failed due to exception'
                raise

            try:
                self._downloads[download.name] = download
                self._update_download_status(request, download)
                done.callback('started')
                return done

            except Exception:
                request.additional_info = 'Download request startup failed due to exception'
                del self._downloads[download.name]
                download.cancel_download(request)
                raise

        except Exception as e:
            self.log.exception('create', e=e)

            request.reason = ImageDownload.UNKNOWN_ERROR
            request.state = ImageDownload.DOWNLOAD_FAILED
            if not request.additional_info:
                request.additional_info = e.message

            self.adapter_agent.update_image_download(request)

            # restore admin state to enabled
            device.admin_state = AdminState.ENABLED
            self.adapter_agent.update_device(device)
            raise

    def download_status(self, device, request, done):
        """
        This is called to inquire about a requested image download status based
        on a NBI call.

        The adapter is expected to update the DownloadImage DB object with the
        query result

        :param device: A Voltha.Device object.
        :param request: A Voltha.ImageDownload object.
        :param done: (Deferred) Deferred to fire when done

        :return: (Deferred) Shall be fired to acknowledge
        """
        log.info('download_status', request=request)
        download = self._downloads.get(request.name)

        self._update_download_status(request, download)

        if request.state != ImageDownload.DOWNLOAD_STARTED:
            # restore admin state to enabled
            device.admin_state = AdminState.ENABLED
            self.adapter_agent.update_device(device)

        done.callback(request.state)
        return done

    def cancel_download(self, device, request, done):
        """
        This is called to cancel a requested image download based on a NBI
        call.  The admin state of the device will not change after the
        download.

        :param device: A Voltha.Device object.
        :param request: A Voltha.ImageDownload object.
        :param done: (Deferred) Deferred to fire when done

        :return: (Deferred) Shall be fired to acknowledge
        """
        log.info('cancel_download', request=request)

        download = self._downloads.get(request.name)

        if download is not None:
            del self._downloads[request.name]
            result = download.cancel_download(request)
            self._update_download_status(request, download)
            done.callback(result)
        else:
            self._update_download_status(request, download)
            done.errback(KeyError('Download request not found'))

        if device.admin_state == AdminState.DOWNLOADING_IMAGE:
            device.admin_state = AdminState.ENABLED
            self.adapter_agent.update_device(device)

        return done

    def activate_image(self, device, request, done):
        """
        This is called to activate a downloaded image from a standby partition
        into active partition.

        Depending on the device implementation, this call may or may not
        cause device reboot. If no reboot, then a reboot is required to make
        the activated image running on device

        :param device: A Voltha.Device object.
        :param request: A Voltha.ImageDownload object.
        :param done: (Deferred) Deferred to fire when done

        :return: (Deferred) OperationResponse object.
        """
        log.info('activate_image', request=request)

        download = self._downloads.get(request.name)
        if download is not None:
            del self._downloads[request.name]
            result = download.activate_image()
            self._update_download_status(request, download)
            done.callback(result)
        else:
            self._update_download_status(request, download)
            done.errback(KeyError('Download request not found'))

        # restore admin state to enabled
        device.admin_state = AdminState.ENABLED
        self.adapter_agent.update_device(device)
        return done

    def revert_image(self, device, request, done):
        """
        This is called to deactivate the specified image at active partition,
        and revert to previous image at standby partition.

        Depending on the device implementation, this call may or may not
        cause device reboot. If no reboot, then a reboot is required to
        make the previous image running on device

        :param device: A Voltha.Device object.
        :param request: A Voltha.ImageDownload object.
        :param done: (Deferred) Deferred to fire when done

        :return: (Deferred) OperationResponse object.
        """
        log.info('revert_image', request=request)

        download = self._downloads.get(request.name)
        if download is not None:
            del self._downloads[request.name]
            result = download.revert_image()
            self._update_download_status(request, download)
            done.callback(result)
        else:
            self._update_download_status(request, download)
            done.errback(KeyError('Download request not found'))

        # restore admin state to enabled
        device.admin_state = AdminState.ENABLED
        self.adapter_agent.update_device(device)
        return done

    def on_channel_group_modify(self, cgroup, update, diffs):
        valid_keys = ['enable',
                      'polling-period',
                      'system-id']  # Modify of these keys supported

        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("channel-group leaf '{}' is read-only or write-once".format(invalid_key))

        pons = self.get_related_pons(cgroup)
        keys = [k for k in diffs.keys() if k in valid_keys]

        for k in keys:
            if k == 'enabled':
                pass  # TODO: ?

            elif k == 'polling-period':
                for pon in pons:
                    pon.discovery_tick = update[k]

            elif k == 'system-id':
                self.system_id(update[k])

        return update

    def on_channel_partition_modify(self, cpartition, update, diffs):
        valid_keys = ['enabled', 'fec-downstream', 'mcast-aes', 'differential-fiber-distance']

        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("channel-partition leaf '{}' is read-only or write-once".format(invalid_key))

        pons = self.get_related_pons(cpartition)
        keys = [k for k in diffs.keys() if k in valid_keys]

        for k in keys:
            if k == 'enabled':
                pass  # TODO: ?

            elif k == 'fec-downstream':
                for pon in pons:
                    pon.downstream_fec_enable = update[k]

            elif k == 'mcast-aes':
                for pon in pons:
                    pon.mcast_aes = update[k]

            elif k == 'differential-fiber-distance':
                for pon in pons:
                    pon.deployment_range = update[k] * 1000  # pon-agent uses meters
        return update

    def on_channel_pair_modify(self, cpair, update, diffs):
        valid_keys = ['enabled', 'line-rate']  # Modify of these keys supported

        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("channel-pair leaf '{}' is read-only or write-once".format(invalid_key))

        pons = self.get_related_pons(cpair)
        keys = [k for k in diffs.keys() if k in valid_keys]

        for k in keys:
            if k == 'enabled':
                pass                        # TODO: ?

            elif k == 'line-rate':
                for pon in pons:
                    pon.line_rate = update[k]
        return update

    def on_channel_termination_create(self, ct, pon_type='xgs-ponid'):
        pons = self.get_related_pons(ct, pon_type=pon_type)
        pon_port = pons[0] if len(pons) == 1 else None

        if pon_port is None:
            raise ValueError('Unknown PON port. PON-ID: {}'.format(ct[pon_type]))

        assert ct['channel-pair'] in self.channel_pairs, \
            '{} is not a channel-pair'.format(ct['channel-pair'])
        cpair = self.channel_pairs[ct['channel-pair']]

        assert cpair['channel-group'] in self.channel_groups, \
            '{} is not a -group'.format(cpair['channel-group'])
        assert cpair['channel-partition'] in self.channel_partitions, \
            '{} is not a channel-partition'.format(cpair('channel-partition'))
        cg = self.channel_groups[cpair['channel-group']]
        cpart = self.channel_partitions[cpair['channel-partition']]

        polling_period = cg['polling-period']
        system_id = cg['system-id']
        authentication_method = cpart['authentication-method']
        # line_rate = cpair['line-rate']
        downstream_fec = cpart['fec-downstream']
        deployment_range = cpart['differential-fiber-distance']
        mcast_aes = cpart['mcast-aes']
        # TODO: Support BER calculation period

        pon_port.xpon_name = ct['name']
        pon_port.discovery_tick = polling_period
        pon_port.authentication_method = authentication_method
        pon_port.deployment_range = deployment_range * 1000  # pon-agent uses meters
        pon_port.downstream_fec_enable = downstream_fec
        pon_port.mcast_aes = mcast_aes
        # pon_port.line_rate = line_rate            # TODO: support once 64-bits
        self.system_id = system_id

        # Enabled 'should' be a logical 'and' of all referenced items but
        # there is no easy way to detected changes in referenced items.
        # enabled = ct['enabled'] and cpair['enabled'] and cg['enabled'] and cpart['enabled']
        enabled = ct['enabled']
        pon_port.admin_state = AdminState.ENABLED if enabled else AdminState.DISABLED
        return ct

    def on_channel_termination_modify(self, ct, update, diffs, pon_type='xgs-ponid'):
        valid_keys = ['enabled']  # Modify of these keys supported

        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("channel-termination leaf '{}' is read-only or write-once".format(invalid_key))

        pons = self.get_related_pons(ct, pon_type=pon_type)
        pon_port = pons[0] if len(pons) == 1 else None

        if pon_port is None:
            raise ValueError('Unknown PON port. PON-ID: {}'.format(ct[pon_type]))

        keys = [k for k in diffs.keys() if k in valid_keys]

        for k in keys:
            if k == 'enabled':
                enabled = update[k]
                pon_port.admin_state = AdminState.ENABLED if enabled else AdminState.DISABLED
        return update

    def on_channel_termination_delete(self, ct, pon_type='xgs-ponid'):
        pons = self.get_related_pons(ct, pon_type=pon_type)
        pon_port = pons[0] if len(pons) == 1 else None

        if pon_port is None:
            raise ValueError('Unknown PON port. PON-ID: {}'.format(ct[pon_type]))

        pon_port.admin_state = AdminState.DISABLED
        return None

    def on_ont_ani_modify(self, ont_ani, update, diffs):
        valid_keys = ['enabled', 'upstream-fec']  # Modify of these keys supported

        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("ont-ani leaf '{}' is read-only or write-once".format(invalid_key))

        onus = self.get_related_onus(ont_ani)
        keys = [k for k in diffs.keys() if k in valid_keys]

        for k in keys:
            if k == 'enabled':
                pass      # TODO: Have only ONT use this value?

            elif k == 'upstream-fec':
                for onu in onus:
                    onu.upstream_fec_enable = update[k]
        return update

    def on_vont_ani_modify(self, vont_ani, update, diffs):
        valid_keys = ['enabled',
                      'expected-serial-number',
                      'upstream-channel-speed'
                      ]  # Modify of these keys supported

        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("vont-ani leaf '{}' is read-only or write-once".format(invalid_key))

        onus = self.get_related_onus(vont_ani)
        keys = [k for k in diffs.keys() if k in valid_keys]

        for k in keys:
            if k == 'enabled':
                for onu in onus:
                    onu.enabled = update[k]
            elif k == 'expected-serial-number':
                for onu in onus:
                    if onu.serial_number != update[k]:
                        onu.pon.delete_onu(onu.onu_id)
            elif k == 'upstream-channel-speed':
                for onu in onus:
                    onu.upstream_channel_speed = update[k]
        return update

    def on_vont_ani_delete(self, vont_ani):
        onus = self.get_related_onus(vont_ani)

        for onu in onus:
            try:
                onu.pon.delete_onu(onu.onu_id)

            except Exception as e:
                self.log.exception('onu', onu=onu, e=e)

        return None

    def _get_tcont_onu(self, vont_ani):
        onu = None
        try:
            vont_ani = self.v_ont_anis.get(vont_ani)
            ch_pair = self.channel_pairs.get(vont_ani['preferred-channel-pair'])
            ch_term = next((term for term in self.channel_terminations.itervalues()
                            if term['channel-pair'] == ch_pair['name']), None)

            pon = self.pon(ch_term['xgs-ponid'])
            onu = pon.onu(vont_ani['onu-id'])

        except Exception:
            pass

        return onu

    def on_tcont_create(self, tcont):
        from xpon.olt_tcont import OltTCont

        td = self.traffic_descriptors.get(tcont.get('td-ref'))
        traffic_descriptor = td['object'] if td is not None else None

        tcont['object'] = OltTCont.create(tcont, traffic_descriptor)

        # Look up any ONU associated with this TCONT (should be only one if any)
        onu = self._get_tcont_onu(tcont['vont-ani'])

        if onu is not None:                 # Has it been discovered yet?
            onu.add_tcont(tcont['object'])

        return tcont

    def on_tcont_modify(self, tcont, update, diffs):
        valid_keys = ['td-ref']  # Modify of these keys supported

        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("TCONT leaf '{}' is read-only or write-once".format(invalid_key))

        tc = tcont.get('object')
        assert tc is not None, 'TCONT not found'

        update['object'] = tc

        # Look up any ONU associated with this TCONT (should be only one if any)
        onu = self._get_tcont_onu(tcont['vont-ani'])

        if onu is not None:                 # Has it been discovered yet?
            keys = [k for k in diffs.keys() if k in valid_keys]

            for k in keys:
                if k == 'td-ref':
                    td = self.traffic_descriptors.get(update['td-ref'])
                    if td is not None:
                        onu.update_tcont_td(tcont['alloc-id'], td)

        return update

    def on_tcont_delete(self, tcont):
        onu = self._get_tcont_onu(tcont['vont-ani'])

        if onu is not None:
            onu.remove_tcont(tcont['alloc-id'])

        return None

    def on_td_create(self, traffic_disc):
        from xpon.olt_traffic_descriptor import OltTrafficDescriptor
        traffic_disc['object'] = OltTrafficDescriptor.create(traffic_disc)
        return traffic_disc

    def on_td_modify(self, traffic_disc, update, diffs):
        from xpon.olt_traffic_descriptor import OltTrafficDescriptor

        valid_keys = ['fixed-bandwidth',
                      'assured-bandwidth',
                      'maximum-bandwidth',
                      'priority',
                      'weight',
                      'additional-bw-eligibility-indicator']
        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("traffic-descriptor leaf '{}' is read-only or write-once".format(invalid_key))

        # New traffic descriptor
        update['object'] = OltTrafficDescriptor.create(update)

        td_name = traffic_disc['name']
        tconts = {key: val for key, val in self.tconts.iteritems()
                  if val['td-ref'] == td_name and td_name is not None}

        for tcont in tconts.itervalues():
            # Look up any ONU associated with this TCONT (should be only one if any)
            onu = self._get_tcont_onu(tcont['vont-ani'])
            if onu is not None:
                onu.update_tcont_td(tcont['alloc-id'], update['object'])

        return update

    def on_td_delete(self, traffic_desc):
        # TD may be used by more than one TCONT. Only delete if the last one
        td_name = traffic_desc['name']
        num_tconts = len([val for val in self.tconts.itervalues()
                          if val['td-ref'] == td_name and td_name is not None])
        return None if num_tconts <= 1 else traffic_desc

    def on_gemport_create(self, gem_port):
        from xpon.olt_gem_port import OltGemPort
        # Create an GemPort object to wrap the dictionary
        gem_port['object'] = OltGemPort.create(self, gem_port)

        onus = self.get_related_onus(gem_port)
        assert len(onus) <= 1, 'Too many ONUs: {}'.format(len(onus))

        if len(onus) == 1:
            onus[0].add_gem_port(gem_port['object'])

        return gem_port

    def on_gemport_modify(self, gem_port, update, diffs):
        valid_keys = ['encryption',
                      'traffic-class']  # Modify of these keys supported

        invalid_key = next((key for key in diffs.keys() if key not in valid_keys), None)
        if invalid_key is not None:
            raise KeyError("GEM Port leaf '{}' is read-only or write-once".format(invalid_key))

        port = gem_port.get('object')
        assert port is not None, 'GemPort not found'

        keys = [k for k in diffs.keys() if k in valid_keys]
        update['object'] = port

        for k in keys:
            if k == 'encryption':
                port.encryption = update[k]
            elif k == 'traffic-class':
                pass                    # TODO: Implement

        return update

    def on_gemport_delete(self, gem_port):
        onus = self.get_related_onus(gem_port)
        assert len(onus) <= 1, 'Too many ONUs: {}'.format(len(onus))
        if len(onus) == 1:
            onus[0].remove_gem_id(gem_port['gemport-id'])
        return None
Exemplo n.º 17
0
class AdtranOltHandler(AdtranDeviceHandler):
    """
    The OLT Handler is used to wrap a single instance of a 10G OLT 1-U pizza-box
    """
    MIN_OLT_HW_VERSION = datetime.datetime(2017, 1, 5)

    # Full table output

    GPON_OLT_HW_URI = '/restconf/data/gpon-olt-hw'
    GPON_OLT_HW_STATE_URI = GPON_OLT_HW_URI + ':olt-state'
    GPON_PON_CONFIG_LIST_URI = GPON_OLT_HW_URI + ':olt/pon'

    # Per-PON info

    GPON_PON_STATE_URI = GPON_OLT_HW_STATE_URI + '/pon={}'        # .format(pon-id)
    GPON_PON_CONFIG_URI = GPON_PON_CONFIG_LIST_URI + '={}'        # .format(pon-id)

    GPON_ONU_CONFIG_LIST_URI = GPON_PON_CONFIG_URI + '/onus/onu'  # .format(pon-id)
    GPON_ONU_CONFIG_URI = GPON_ONU_CONFIG_LIST_URI + '={}'        # .format(pon-id,onu-id)

    GPON_TCONT_CONFIG_LIST_URI = GPON_ONU_CONFIG_URI + '/t-conts/t-cont'  # .format(pon-id,onu-id)
    GPON_TCONT_CONFIG_URI = GPON_TCONT_CONFIG_LIST_URI + '={}'            # .format(pon-id,onu-id,alloc-id)

    GPON_GEM_CONFIG_LIST_URI = GPON_ONU_CONFIG_URI + '/gem-ports/gem-port'  # .format(pon-id,onu-id)
    GPON_GEM_CONFIG_URI = GPON_GEM_CONFIG_LIST_URI + '={}'                  # .format(pon-id,onu-id,gem-id)

    GPON_PON_DISCOVER_ONU = '/restconf/operations/gpon-olt-hw:discover-onu'

    BASE_ONU_OFFSET = 64

    def __init__(self, adapter, device_id, timeout=20):
        super(AdtranOltHandler, self).__init__(adapter, device_id, timeout=timeout)
        self.gpon_olt_hw_revision = None
        self.status_poll = None
        self.status_poll_interval = 5.0
        self.status_poll_skew = self.status_poll_interval / 10

        self.zmq_client = None

        # xPON config dictionaries

        self._channel_groups = {}         #  Name -> dict
        self._channel_partitions = {}     #  Name -> dict
        self._channel_pairs = {}          #  Name -> dict
        self._channel_terminations = {}   #  Name -> dict
        self._v_ont_anis = {}             #  Name -> dict
        self._ont_anis = {}               #  Name -> dict
        self._v_enets = {}                #  Name -> dict

    def __del__(self):
        # OLT Specific things here.
        #
        # If you receive this during 'enable' of the object, you probably threw an
        # uncaught exception which trigged an errback in the VOLTHA core.

        d, self.status_poll = self.status_poll, None

        # TODO Any OLT device specific cleanup here
        #     def get_channel(self):
        #         if self.channel is None:
        #             device = self.adapter_agent.get_device(self.device_id)
        #         return self.channel
        #
        # Clean up base class as well

        AdtranDeviceHandler.__del__(self)

    def __str__(self):
        return "AdtranOltHandler: {}".format(self.ip_address)

    @inlineCallbacks
    def get_device_info(self, device):
        """
        Perform an initial network operation to discover the device hardware
        and software version. Serial Number would be helpful as well.

        Upon successfully retrieving the information, remember to call the
        'start_heartbeat' method to keep in contact with the device being managed

        :param device: A voltha.Device object, with possible device-type
                specific extensions. Such extensions shall be described as part of
                the device type specification returned by device_types().
        """
        from codec.physical_entities_state import PhysicalEntitiesState

        device = {}

        if self.is_virtual_olt:
            returnValue(device)

        pe_state = PhysicalEntitiesState(self.netconf_client)
        self.startup = pe_state.get_state()
        results = yield self.startup

        if results.ok:
            modules = pe_state.get_physical_entities('adtn-phys-mod:module')
            if isinstance(modules, list):
                module = modules[0]
                name = str(module['model-name']).translate(None, '?')
                model = str(module['model-number']).translate(None, '?')

                device['model'] = '{} - {}'.format(name, model) if len(name) > 0 else \
                    module['parent-entity']
                device['hardware_version'] = str(module['hardware-revision']).translate(None, '?')
                device['serial_number'] = str(module['serial-number']).translate(None, '?')
                device['vendor'] = 'Adtran, Inc.'
                device['firmware_version'] = str(device.get('firmware-revision', 'unknown')).translate(None, '?')
                software = module['software']['software']
                device['running-revision'] = str(software['running-revision']).translate(None, '?')
                device['candidate-revision'] = str(software['candidate-revision']).translate(None, '?')
                device['startup-revision'] = str(software['startup-revision']).translate(None, '?')

        returnValue(device)

    @inlineCallbacks
    def enumerate_northbound_ports(self, device):
        """
        Enumerate all northbound ports of this device.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :return: (Deferred or None).
        """
        try:
            from codec.ietf_interfaces import IetfInterfacesState
            from nni_port import MockNniPort

            ietf_interfaces = IetfInterfacesState(self.netconf_client)

            if self.is_virtual_olt:
                results = MockNniPort.get_nni_port_state_results()
            else:
                self.startup = ietf_interfaces.get_state()
                results = yield self.startup

            ports = ietf_interfaces.get_nni_port_entries(results)
            yield returnValue(ports)

        except Exception as e:
            log.exception('enumerate_northbound_ports', e=e)
            raise

    def process_northbound_ports(self, device, results):
        """
        Process the results from the 'enumerate_northbound_ports' method.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :param results: Results from the 'enumerate_northbound_ports' method that
                you implemented. The type and contents are up to you to
        :return: (Deferred or None).
        """
        from nni_port import NniPort, MockNniPort

        for port in results:
            port_no = port['port_no']
            self.log.info('processing-nni', port_no=port_no, name=port['port_no'])
            assert port_no
            assert port_no not in self.northbound_ports
            self.northbound_ports[port_no] = NniPort(self, **port) if not self.is_virtual_olt \
                else MockNniPort(self, **port)

            # TODO: For now, limit number of NNI ports to make debugging easier
            if len(self.northbound_ports) >= self.max_nni_ports:
                break

        self.num_northbound_ports = len(self.northbound_ports)

    @inlineCallbacks
    def enumerate_southbound_ports(self, device):
        """
        Enumerate all southbound ports of this device.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :return: (Deferred or None).
        """
        ###############################################################################
        # Determine number of southbound ports. We know it is 16, but this keeps this
        # device adapter generic for our other OLTs up to this point.

        self.startup = self.rest_client.request('GET', self.GPON_PON_CONFIG_LIST_URI, 'pon-config')
        results = yield self.startup
        returnValue(results)

    def process_southbound_ports(self, device, results):
        """
        Process the results from the 'enumerate_southbound_ports' method.

        :param device: A voltha.Device object, with possible device-type
                specific extensions.
        :param results: Results from the 'enumerate_southbound_ports' method that
                you implemented. The type and contents are up to you to
        :return: (Deferred or None).
        """
        from pon_port import PonPort

        for pon in results:
            # Number PON Ports after the NNI ports
            pon_id = pon['pon-id']
            log.info('Processing-pon-port', pon_id=pon_id)
            assert pon_id not in self.southbound_ports

            admin_state = AdminState.ENABLED if pon.get('enabled',
                                                        PonPort.DEFAULT_ENABLED) else AdminState.DISABLED

            self.southbound_ports[pon_id] = PonPort(pon_id,
                                                    self._pon_id_to_port_number(pon_id),
                                                    self,
                                                    admin_state=admin_state)

            # TODO: For now, limit number of PON ports to make debugging easier
            if self.autoactivate and len(self.southbound_ports) >= self.max_pon_ports:
                break

        self.num_southbound_ports = len(self.southbound_ports)

    def complete_device_specific_activation(self, device, reconciling):
        """
        Perform an initial network operation to discover the device hardware
        and software version. Serial Number would be helpful as well.

        This method is called from within the base class's activate generator.

        :param device: A voltha.Device object, with possible device-type
                specific extensions. Such extensions shall be described as part of
                the device type specification returned by device_types().

        :param reconciling: (boolean) True if taking over for another VOLTHA
        """
        # For the pizzabox OLT, periodically query the OLT state of all PONs. This
        # is simpler then having each PON port do its own poll.  From this, we can:
        #
        # o Discover any new or missing ONT/ONUs
        #
        # o TODO Discover any LOS for any ONT/ONUs
        #
        # o TODO Update some PON level statistics

        self.zmq_client = AdtranZmqClient(self.ip_address, rx_callback=self.rx_packet, port=self.zmq_port)
        self.status_poll = reactor.callLater(5, self.poll_for_status)
        return succeed('Done')

    def disable(self):
        c, self.zmq_client = self.zmq_client, None
        if c is not None:
            try:
                c.shutdown()
            except:
                pass

        d, self.status_poll = self.status_poll, None
        if d is not None and not d.called:
            try:
                d.cancel()
            except:
                pass

        super(AdtranOltHandler, self).disable()

    def reenable(self):
        super(AdtranOltHandler, self).reenable()

        self.zmq_client = AdtranZmqClient(self.ip_address, rx_callback=self.rx_packet, port=self.zmq_port)
        self.status_poll = reactor.callLater(1, self.poll_for_status)

    def reboot(self):
        c, self.zmq_client = self.zmq_client, None
        if c is not None:
            c.shutdown()

        d, self.status_poll = self.status_poll, None
        if d is not None and not d.called:
            d.cancel()

        super(AdtranOltHandler, self).reboot()

    def _finish_reboot(self, timeout, previous_oper_status, previous_conn_status):
        super(AdtranOltHandler, self)._finish_reboot(timeout, previous_oper_status, previous_conn_status)

        self.zmq_client = AdtranZmqClient(self.ip_address, rx_callback=self.rx_packet, port=self.zmq_port)
        self.status_poll = reactor.callLater(1, self.poll_for_status)

    def delete(self):
        c, self.zmq_client = self.zmq_client, None
        if c is not None:
            c.shutdown()

        d, self.status_poll = self.status_poll, None
        if d is not None and not d.called:
            d.cancel()

        super(AdtranOltHandler, self).delete()

    def rx_packet(self, message):
        try:
            self.log.debug('rx_packet')

            pon_id, onu_id, msg, is_omci = AdtranZmqClient.decode_packet(message)

            if is_omci:
                proxy_address = Device.ProxyAddress(device_id=self.device_id,
                                                    channel_id=self.get_channel_id(pon_id, onu_id),
                                                    onu_id=onu_id)

                self.adapter_agent.receive_proxied_message(proxy_address, msg)
            else:
                pass  # TODO: Packet in support not yet supported
                # self.adapter_agent.send_packet_in(logical_device_id=logical_device_id,
                #                                   logical_port_no=cvid,  # C-VID encodes port no
                #                                   packet=str(msg))
        except Exception as e:
            self.log.exception('rx_packet', e=e)

    def poll_for_status(self):
        self.log.debug('Initiating-status-poll')

        device = self.adapter_agent.get_device(self.device_id)

        if device.admin_state == AdminState.ENABLED and\
                device.oper_status != OperStatus.ACTIVATING and\
                self.rest_client is not None:
            uri = AdtranOltHandler.GPON_OLT_HW_STATE_URI
            name = 'pon-status-poll'
            self.status_poll = self.rest_client.request('GET', uri, name=name)
            self.status_poll.addBoth(self.status_poll_complete)
        else:
            self.status_poll = reactor.callLater(0, self.status_poll_complete, 'inactive')

    def status_poll_complete(self, results):
        """
        Results of the status poll
        :param results:
        """
        from pon_port import PonPort

        if isinstance(results, dict) and 'pon' in results:
            try:
                self.log.debug('status-success')
                for pon_id, pon in OltState(results).pons.iteritems():
                    pon_port = self.southbound_ports.get(pon_id, None)

                    if pon_port is not None and pon_port.state == PonPort.State.RUNNING:
                        pon_port.process_status_poll(pon)

            except Exception as e:
                self.log.exception('PON-status-poll', e=e)

        # Reschedule

        delay = self.status_poll_interval
        delay += random.uniform(-delay / 10, delay / 10)

        self.status_poll = reactor.callLater(delay, self.poll_for_status)

    @inlineCallbacks
    def deactivate(self, device):
        # OLT Specific things here

        d, self.startup = self.startup, None
        if d is not None and not d.called:
            d.cancel()

        # self.pons.clear()

        # TODO: Any other? OLT specific deactivate steps

        # Call into base class and have it clean up as well
        super(AdtranOltHandler, self).deactivate(device)

    @inlineCallbacks
    def update_flow_table(self, flows, device):
        """
        Update the flow table on the OLT.  If an existing flow is not in the list, it needs
        to be removed from the device.

        :param flows: List of flows that should be installed upon completion of this function
        :param device: A voltha.Device object, with possible device-type
                       specific extensions.
        """
        self.log.debug('bulk-flow-update', num_flows=len(flows),
                       device_id=device.id, flows=flows)

        valid_flows = []

        for flow in flows:
            try:
                # Try to create an EVC.
                #
                # The first result is the flow entry that was created. This could be a match to an
                # existing flow since it is a bulk update.  None is returned only if no match to
                # an existing entry is found and decode failed (unsupported field)
                #
                # The second result is the EVC this flow should be added to. This could be an
                # existing flow (so your adding another EVC-MAP) or a brand new EVC (no existing
                # EVC-MAPs).  None is returned if there are not a valid EVC that can be created YET.

                valid_flow, evc = FlowEntry.create(flow, self)

                if valid_flow is not None:
                    valid_flows.append(valid_flow.flow_id)

                if evc is not None:
                    try:
                        evc.schedule_install()
                        self.add_evc(evc)

                    except Exception as e:
                        evc.status = 'EVC Install Exception: {}'.format(e.message)
                        self.log.exception('EVC-install', e=e)

            except Exception as e:
                self.log.exception('bulk-flow-update-add', e=e)

        # Now drop all flows from this device that were not in this bulk update
        try:
            FlowEntry.drop_missing_flows(device.id, valid_flows)

        except Exception as e:
            self.log.exception('bulk-flow-update-remove', e=e)

    # @inlineCallbacks
    def send_proxied_message(self, proxy_address, msg):
        self.log.debug('sending-proxied-message', msg=msg)

        if isinstance(msg, Packet):
            msg = str(msg)

        if self.zmq_client is not None:
            pon_id = self._channel_id_to_pon_id(proxy_address.channel_id, proxy_address.onu_id)
            onu_id = proxy_address.onu_id

            data = AdtranZmqClient.encode_omci_message(msg, pon_id, onu_id)

            try:
                self.zmq_client.send(data)

            except Exception as e:
                self.log.exception('zmqClient.send', e=e)
                raise

    @staticmethod
    def is_gpon_olt_hw(content):
        """
        If the hello content

        :param content: (dict) Results of RESTCONF adtran-hello GET request
        :return: (string) GPON OLT H/w RESTCONF revision number or None on error/not GPON
        """
        for item in content.get('module-info', None):
            if item.get('module-name') == 'gpon-olt-hw':
                return AdtranDeviceHandler.parse_module_revision(item.get('revision', None))
        return None

    def get_channel_id(self, pon_id, onu_id):
        from pon_port import PonPort
        return self._onu_offset(onu_id) + (pon_id * PonPort.MAX_ONUS_SUPPORTED)

    def _onu_offset(self, onu_id):
        # Start ONU's just past the southbound PON port numbers. Since ONU ID's start
        # at zero, add one
        assert AdtranOltHandler.BASE_ONU_OFFSET > (self.num_northbound_ports + self.num_southbound_ports + 1)
        return AdtranOltHandler.BASE_ONU_OFFSET + onu_id

    def _channel_id_to_pon_id(self, channel_id, onu_id):
        from pon_port import PonPort
        return (channel_id - self._onu_offset(onu_id)) / PonPort.MAX_ONUS_SUPPORTED

    def _pon_id_to_port_number(self, pon_id):
        return pon_id + 1 + self.num_northbound_ports

    def _port_number_to_pon_id(self, port):
        return port - 1 - self.num_northbound_ports

    def is_pon_port(self, port):
        return self._port_number_to_pon_id(port) in self.southbound_ports

    def is_uni_port(self, port):
        return port >= self._onu_offset(0)  # TODO: Really need to rework this one...

    def get_southbound_port(self, port):
        pon_id = self._port_number_to_pon_id(port)
        return self.southbound_ports.get(pon_id, None)

    def get_port_name(self, port):
        if self.is_nni_port(port):
            return self.northbound_ports[port].name

        if self.is_pon_port(port):
            return self.get_southbound_port(port).name

        if self.is_uni_port(port):
            return self.northbound_ports[port].name

        if self.is_logical_port(port):
            raise NotImplemented('TODO: Logical ports not yet supported')

    def get_xpon_info(self, pon_id, pon_id_type='xgs-ponid'):
        """
        Lookup all xPON configuraiton data for a specific pon-id / channel-termination
        :param pon_id: (int) PON Identifier
        :return: (dict) reduced xPON information for the specific PON port
        """
        terminations = {key: val for key, val in self._channel_terminations.iteritems()
                        if val[pon_id_type] == pon_id}

        pair_names = set([term['channel-pair'] for term in terminations.itervalues()])

        pairs = {key: val for key, val in self._channel_pairs.iteritems()
                 if key in pair_names}

        partition_names = set([pair['channel-partition'] for pair in pairs.itervalues()])

        partitions = {key: val for key, val in self._channel_partitions.iteritems()
                      if key in partition_names}

        v_ont_anis = {key: val for key, val in self._v_ont_anis.iteritems()
                      if val['preferred-channel-pair'] in pair_names}

        return {
            'channel-terminations': terminations,
            'channel-pairs': pairs,
            'channel-partitions': partitions,
            'v_ont_anis': v_ont_anis
        }

    def create_interface(self, device, data):
        """
        Create XPON interfaces
        :param device: (Device)
        :param data: (ChannelgroupConfig) Channel Group configuration
        """
        name = data.name
        interface = data.interface
        inst_data = data.data

        if isinstance(data, ChannelgroupConfig):
            self.log.debug('create_interface-channel-group', interface=interface, data=inst_data)
            self._channel_groups[name] = {
                'name': name,
                'enabled': interface.enabled,
                'system-id': inst_data.system_id,
                'polling-period': inst_data.polling_period
            }

        elif isinstance(data, ChannelpartitionConfig):
            self.log.debug('create_interface-channel-partition', interface=interface, data=inst_data)

            def _auth_method_enum_to_string(value):
                from voltha.protos.bbf_fiber_types_pb2 import SERIAL_NUMBER, LOID, \
                    REGISTRATION_ID, OMCI, DOT1X
                return {
                    SERIAL_NUMBER: 'serial-number',
                    LOID: 'loid',
                    REGISTRATION_ID: 'registation-id',
                    OMCI: 'omci',
                    DOT1X: 'don1x'
                }.get(value, 'unknown')

            self._channel_partitions[name] = {
                'name': name,
                'enabled': interface.enabled,
                'authentication-method': _auth_method_enum_to_string(inst_data.authentication_method),
                'channel-group': inst_data.channelgroup_ref,
                'fec-downstream': inst_data.fec_downstream,
                'mcast-aes': inst_data.multicast_aes_indicator,
                'differential-fiber-distance': inst_data.differential_fiber_distance
            }

        elif isinstance(data, ChannelpairConfig):
            self.log.debug('create_interface-channel-pair', interface=interface, data=inst_data)
            self._channel_pairs[name] = {
                'name': name,
                'enabled': interface.enabled,
                'channel-group': inst_data.channelgroup_ref,
                'channel-partition': inst_data.channelpartition_ref,
                'line-rate': inst_data.channelpair_linerate
            }

        elif isinstance(data, ChannelterminationConfig):
            self.log.debug('create_interface-channel-termination', interface=interface, data=inst_data)
            self._channel_terminations[name] = {
                'name': name,
                'enabled': interface.enabled,
                'xgs-ponid': inst_data.xgs_ponid,
                'xgpon-ponid': inst_data.xgpon_ponid,
                'channel-pair': inst_data.channelpair_ref,
                'ber-calc-period': inst_data.ber_calc_period
            }
            self.on_channel_termination_config(name, 'create')

        elif isinstance(data, OntaniConfig):
            self.log.debug('create_interface-ont-ani', interface=interface, data=inst_data)
            self._ont_anis[name] = {
                'name': name,
                'enabled': interface.enabled,
                'upstream-fec': inst_data.upstream_fec_indicator,
                'mgnt-gemport-aes': inst_data.mgnt_gemport_aes_indicator
            }

        elif isinstance(data, VOntaniConfig):
            self.log.debug('create_interface-v-ont-ani', interface=interface, data=inst_data)
            self._v_ont_anis[name] = {
                'name': name,
                'enabled': interface.enabled,
                'onu-id': inst_data.onu_id,
                'expected-serial-number': inst_data.expected_serial_number,
                'preferred-channel-pair': inst_data.preferred_chanpair,
                'channel-partition': inst_data.parent_ref,
                'upstream-channel-speed': inst_data.upstream_channel_speed
            }

        elif isinstance(data, VEnetConfig):
            self.log.debug('create_interface-v-enet', interface=interface, data=inst_data)
            self._v_enets[name] = {
                'name': name,
                'enabled': interface.enabled,
                'v-ont-ani': inst_data.v_ontani_ref
            }

        else:
            raise NotImplementedError('Unknown data type')

    def on_channel_termination_config(self, name, operation, pon_type='xgs-ponid'):
        supported_operations = ['create']

        assert operation in supported_operations
        assert name in self._channel_terminations
        ct = self._channel_terminations[name]

        pon_id = ct[pon_type]
        # Look up the southbound PON port

        pon_port = self.southbound_ports.get(pon_id, None)
        if pon_port is None:
            raise ValueError('Unknown PON port. PON-ID: {}'.format(pon_id))

        assert ct['channel-pair'] in self._channel_pairs
        cpair = self._channel_pairs[ct['channel-pair']]

        assert cpair['channel-group'] in self._channel_groups
        assert cpair['channel-partition'] in self._channel_partitions
        cg = self._channel_groups[cpair['channel-group']]
        cpart = self._channel_partitions[cpair['channel-partition']]

        enabled = ct['enabled']
        
        polling_period = cg['polling-period']
        authentication_method = cpart['authentication-method']
        # line_rate = cpair['line-rate']
        # downstream_fec = cpart['fec-downstream']
        # deployment_range = cpart['differential-fiber-distance']
        # mcast_aes = cpart['mcast-aes']

        # TODO: Support BER calculation period
        # TODO support FEC, and MCAST AES settings
        # TODO Support setting of line rate

        if operation == 'create':
            pon_port.xpon_name = name
            pon_port.discovery_tick = polling_period
            pon_port.authentication_method = authentication_method
            # pon_port.deployment_range = deployment_range
            # pon_port.fec_enable = downstream_fec
            # pon_port.mcast_aes = mcast_aes

            if enabled:
                pon_port.start()
            else:
                pon_port.stop()