Ejemplo n.º 1
0
    def __init__(self, adapter, device_id):
        kwargs = dict()
        super(AdtranOnuHandler, self).__init__(**kwargs)
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.log = structlog.get_logger(device_id=device_id)
        self.logical_device_id = None
        self.proxy_address = None
        self._enabled = False
        self.pm_metrics = None
        self.alarms = None
        self._mgmt_gemport_aes = False
        self._upstream_channel_speed = 0

        self._openomci = OMCI(self, adapter.omci_agent)
        self._in_sync_subscription = None

        self._onu_port_number = 0
        self._pon_port_number = 1
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 0)

        self._unis = dict()         # Port # -> UniPort
        self._pon = PonPort.create(self, self._pon_port_number)
        self._heartbeat = HeartBeat.create(self, device_id)
        self._deferred = None

        # Flow entries
        self._flows = dict()

        # OMCI resources
        # TODO: Some of these could be dynamically chosen
        self.vlan_tcis_1 = 0x900
        self.mac_bridge_service_profile_entity_id = self.vlan_tcis_1
        self.gal_enet_profile_entity_id = 0     # Was 0x100, but ONU seems to overwrite and use zero
Ejemplo n.º 2
0
    def __init__(self, adapter, device_id):
        self.log = structlog.get_logger(device_id=device_id)
        self.log.debug('function-entry')
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.incoming_messages = DeferredQueue()
        self.event_messages = DeferredQueue()
        self.proxy_address = None
        self.tx_id = 0
        self._enabled = False
        self._omcc_version = OMCCVersion.Unknown
        self._total_tcont_count = 0  # From ANI-G ME
        self._qos_flexibility = 0  # From ONT2_G ME

        self._onu_indication = None
        self._unis = dict()  # Port # -> UniPort
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 0)

        self._pon = None
        #TODO: probably shouldnt be hardcoded, determine from olt maybe?
        self._pon_port_number = 100
        self.logical_device_id = None

        # Set up OpenOMCI environment
        self._onu_omci_device = None
        self._dev_info_loaded = False
        self._deferred = None

        self._in_sync_subscription = None
        self._connectivity_subscription = None
        self._capabilities_subscription = None
Ejemplo n.º 3
0
    def __init__(self, adapter, device_id):
        kwargs = dict()
        super(AdtranOnuHandler, self).__init__(**kwargs)
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.log = structlog.get_logger(device_id=device_id)
        self.logical_device_id = None
        self.proxy_address = None
        self._event_messages = None
        self._enabled = False
        self.pm_metrics = None
        self.alarms = None
        self._mgmt_gemport_aes = False
        self._upstream_channel_speed = 0

        self._unis = dict()  # Port # -> UniPort
        self._pons = dict()  # Port # -> PonPort
        self._heartbeat = HeartBeat.create(self, device_id)

        self._deferred = None
        self._event_deferred = None
        self._omci = None
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 1)

        self._olt_created = False  # True if deprecated method of OLT creating DA is used
        self._is_mock = False
Ejemplo n.º 4
0
class TestIndexPool(TestCase):
    pool = IndexPool(8, 0)

    def test_01_get_next(self):
        self.assertEqual(self.pool.indices.bin, '00000000')
        for i in range(8):
            self.assertEqual(self.pool.get_next(), i)
        #to check if there's any bit left after using all 8 bits
        self.assertIsNone(self.pool.get_next())

    def test_02_pre_allocate(self):
        _pool2 = IndexPool(8, 0)
        self.assertEqual(_pool2.indices.bin, '00000000')
        _pool2.pre_allocate((
            0,
            1,
            2,
        ))
        self.assertEqual(_pool2.indices.bin, '11100000')

    def test_03_release(self):
        self.pool.release(5)
        self.assertEqual(self.pool.indices.bin, '11111011')
        self.pool.release(10)
        self.assertEqual(self.pool.indices.bin, '11111011')
        self.pool.release(0)
        self.assertEqual(self.pool.indices.bin, '01111011')

    def test_04_check_offset(self):
        _offset = 5
        self.pool = IndexPool(8, _offset)
        for i in range(8):
            self.assertEqual(self.pool.get_next(), _offset + i)
Ejemplo n.º 5
0
    def __init__(self, adapter, device_id):
        kwargs = dict()
        super(AdtranOnuHandler, self).__init__(**kwargs)
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.log = structlog.get_logger(device_id=device_id)
        self.logical_device_id = None
        self.proxy_address = None
        self._event_messages = None
        self._enabled = False
        self.pm_metrics = None
        self.alarms = None
        self._mgmt_gemport_aes = False
        self._upstream_channel_speed = 0

        self._unis = dict()  # Port # -> UniPort
        self._pon = None
        self._heartbeat = HeartBeat.create(self, device_id)

        self._deferred = None
        self._event_deferred = None

        # TODO: Remove next two lines if/when OpenOMCI is in the core or a container
        #       in order to support multiple ONUs per instance
        self._omci_agent = OpenOMCIAgent(self.adapter_agent.core)
        self._omci_agent.start()

        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 1)

        self._olt_created = False  # True if deprecated method of OLT creating DA is used
        self._is_mock = False
Ejemplo n.º 6
0
 def test_02_pre_allocate(self):
     _pool2 = IndexPool(8, 0)
     self.assertEqual(_pool2.indices.bin, '00000000')
     _pool2.pre_allocate((
         0,
         1,
         2,
     ))
     self.assertEqual(_pool2.indices.bin, '11100000')
Ejemplo n.º 7
0
class TestIndexPool(TestCase):
    pool = IndexPool(8, 0)
    def test_01_get_next(self):
        self.assertEqual(self.pool.indices.bin, '00000000')
        for i in range(8):
            self.assertEqual(self.pool.get_next(), i)
        #to check if there's any bit left after using all 8 bits
        self.assertIsNone(self.pool.get_next())

    def test_02_pre_allocate(self):
        _pool2 = IndexPool(8, 0)
        self.assertEqual(_pool2.indices.bin, '00000000')
        _pool2.pre_allocate((0,1,2,))
        self.assertEqual(_pool2.indices.bin, '11100000')

    def test_03_release(self):
        self.pool.release(5)
        self.assertEqual(self.pool.indices.bin, '11111011')
        self.pool.release(10)
        self.assertEqual(self.pool.indices.bin, '11111011')
        self.pool.release(0)
        self.assertEqual(self.pool.indices.bin, '01111011')

    def test_04_check_offset(self):
        _offset = 5
        self.pool = IndexPool(8, _offset)
        for i in range(8):
            self.assertEqual(self.pool.get_next(), _offset + i)
Ejemplo n.º 8
0
    def __init__(self, adapter, device_id):
        kwargs = dict()
        super(AdtranOnuHandler, self).__init__(**kwargs)
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.log = structlog.get_logger(device_id=device_id)
        self.logical_device_id = None
        self.proxy_address = None
        self._event_messages = None
        self._enabled = False
        self.pm_metrics = None
        self.alarms = None
        self._mgmt_gemport_aes = False
        self._upstream_channel_speed = 0

        self._unis = dict()         # Port # -> UniPort
        self._pons = dict()         # Port # -> PonPort
        self._heartbeat = HeartBeat.create(self, device_id)

        self._deferred = None
        self._event_deferred = None
        self._omci = None
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 1)

        self._olt_created = False   # True if deprecated method of OLT creating DA is used
        self._is_mock = False
Ejemplo n.º 9
0
 def test_04_check_offset(self):
     _offset = 5
     self.pool = IndexPool(8, _offset)
     for i in range(8):
         self.assertEqual(self.pool.get_next(), _offset + i)
Ejemplo n.º 10
0
 def test_04_check_offset(self):
     _offset = 5
     self.pool = IndexPool(8, _offset)
     for i in range(8):
         self.assertEqual(self.pool.get_next(), _offset + i)
Ejemplo n.º 11
0
 def test_02_pre_allocate(self):
     _pool2 = IndexPool(8, 0)
     self.assertEqual(_pool2.indices.bin, '00000000')
     _pool2.pre_allocate((0,1,2,))
     self.assertEqual(_pool2.indices.bin, '11100000')
Ejemplo n.º 12
0
class AdtranOnuHandler(AdtranXPON):
    def __init__(self, adapter, device_id):
        kwargs = dict()
        super(AdtranOnuHandler, self).__init__(**kwargs)
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.log = structlog.get_logger(device_id=device_id)
        self.logical_device_id = None
        self.proxy_address = None
        self._event_messages = None
        self._enabled = False
        self.pm_metrics = None
        self.alarms = None
        self._mgmt_gemport_aes = False
        self._upstream_channel_speed = 0

        self._unis = dict()         # Port # -> UniPort
        self._pons = dict()         # Port # -> PonPort
        self._heartbeat = HeartBeat.create(self, device_id)

        self._deferred = None
        self._event_deferred = None
        self._omci = None
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 1)

        self._olt_created = False   # True if deprecated method of OLT creating DA is used
        self._is_mock = False

    def __str__(self):
        return "AdtranOnuHandler: {}".format(self.device_id)

    def _cancel_deferred(self):
        d1, self._deferred = self._deferred, None
        d2, self._event_deferred = self._event_deferred, None

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

    @property
    def enabled(self):
        return self._enabled

    @enabled.setter
    def enabled(self, value):
        assert isinstance(value, bool), 'enabled is a boolean'
        if self._enabled != value:
            self._enabled = value
            if self._enabled:
                self.start()
            else:
                self.stop()

    @property
    def mgmt_gemport_aes(self):
        return self._mgmt_gemport_aes

    @mgmt_gemport_aes.setter
    def mgmt_gemport_aes(self, value):
        if self._mgmt_gemport_aes != value:
            self._mgmt_gemport_aes = value
            # TODO: Anything else

    @property
    def upstream_channel_speed(self):
        return self._upstream_channel_speed

    @upstream_channel_speed.setter
    def upstream_channel_speed(self, value):
        if self._upstream_channel_speed != value:
            self._upstream_channel_speed = value
            # TODO: Anything else

    @property
    def is_mock(self):
        return self._is_mock        # Not pointing to real hardware

    @property
    def olt_created(self):
        return self._olt_created    # ONU was created with deprecated 'child_device_detected' call

    @property
    def omci(self):
        return self._omci

    @property
    def heartbeat(self):
        return self._heartbeat

    @property
    def uni_ports(self):
        return self._unis.values()

    def uni_port(self, port_no_or_name):
        if isinstance(port_no_or_name, (str, unicode)):
            return next((uni for uni in self.uni_ports
                         if uni.name == port_no_or_name), None)

        assert isinstance(port_no_or_name, int), 'Invalid parameter type'
        return self._unis.get(port_no_or_name)

    @property
    def pon_ports(self):
        return self._pons.values()

    def pon_port(self, port_no):
        return self._pons.get(port_no)

    @property
    def _next_port_number(self):
        return self._port_number_pool.get_next()

    def _release_port_number(self, number):
        self._port_number_pool.release(number)

    def start(self):
        assert self._enabled, 'Start should only be called if enabled'
        #
        # TODO: Perform common startup tasks here
        #
        self._cancel_deferred()

        self._omci = OMCI_CC(self.adapter_agent,
                             self.device_id,
                             custom_me_entries=onu_custom_entity_classes)
        self._omci.enabled = True

        # Handle received ONU event messages
        self._event_messages = DeferredQueue()
        self._event_deferred = reactor.callLater(0, self._handle_onu_events)

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

        # Port startup
        for port in self.uni_ports:
            port.enabled = True

        for port in self.pon_ports:
            port.enabled = True

        # Heartbeat
        self._heartbeat.enabled = True

    def stop(self):
        assert not self._enabled, 'Stop should only be called if disabled'
        #
        # TODO: Perform common shutdown tasks here
        #
        self._cancel_deferred()

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

        # Heartbeat
        self._heartbeat.stop()

        # Port shutdown
        for port in self.uni_ports:
            port.enabled = False

        for port in self.pon_ports:
            port.enabled = False

        omci, self._omci = self._omci, None
        if omci is not None:
            omci.enabled = False

        queue, self._event_deferred = self._event_deferred, None
        if queue is not None:
            while queue.pending:
                _ = yield queue.get()

    def receive_message(self, msg):
        if self._omci is not None and self.enabled:
            self._omci.receive_message(msg)

    def activate(self, device):
        self.log.info('activating')

        # first we verify that we got parent reference and proxy info
        assert device.parent_id, 'Invalid Parent ID'
        assert device.proxy_address.device_id, 'Invalid Device ID'

        if device.vlan:
            # vlan non-zero if created via legacy method (not xPON). Also
            # Set a random serial number since not xPON based
            self._olt_created = True

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

        # initialize device info
        device.root = True
        device.vendor = 'Adtran Inc.'
        device.model = 'n/a'
        device.hardware_version = 'n/a'
        device.firmware_version = 'n/a'
        device.reason = ''

        # TODO: Support more versions as needed
        images = Image(version='NOT AVAILABLE')
        device.images.image.extend([images])

        device.connect_status = ConnectStatus.UNKNOWN

        ############################################################################
        # Setup PM configuration for this device

        self.pm_metrics = OnuPmMetrics(self, device, grouped=True, freq_override=False)
        pm_config = self.pm_metrics.make_proto()
        self.log.info("initial-pm-config", pm_config=pm_config)
        self.adapter_agent.update_device_pm_config(pm_config, init=True)

        ############################################################################
        # Setup Alarm handler

        self.alarms = AdapterAlarms(self.adapter, device.id)

        # reference of uni_port is required when re-enabling the device if
        # it was disabled previously
        # Need to query ONU for number of supported uni ports
        # For now, temporarily set number of ports to 1 - port #2

        # Register physical ports.  Should have at least one of each

        pon_port = PonPort.create(self, self._next_port_number)

        self._pons[pon_port.port_number] = pon_port
        self.adapter_agent.add_port(device.id, pon_port.get_port())

        parent_device = self.adapter_agent.get_device(device.parent_id)
        self.logical_device_id = parent_device.parent_id
        assert self.logical_device_id, 'Invalid logical device ID'

        if self._olt_created:
            # vlan non-zero if created via legacy method (not xPON). Also
            # Set a random serial number since not xPON based

            uni_port = UniPort.create(self, self._next_port_number,
                                      'deprecated', device.vlan)
            self._unis[uni_port.port_number] = uni_port
            self.adapter_agent.add_port(device.id, uni_port.get_port())

            device.serial_number = uuid4().hex
            uni_port.add_logical_port(device.vlan, control_vlan=device.vlan)

            # Start things up for this ONU Handler.
            self.enabled = True

        # Start collecting stats from the device after a brief pause
        reactor.callLater(30, self.start_kpi_collection, device.id)

        self.adapter_agent.update_device(device)

    def reconcile(self, device):
        self.log.info('reconciling-ONU-device-starts')

        # first we verify that we got parent reference and proxy info
        assert device.parent_id
        assert device.proxy_address.device_id
        # assert device.proxy_address.channel_id
        self._cancel_deferred()

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

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

        # Set the connection status to REACHABLE
        device.connect_status = ConnectStatus.REACHABLE
        self.adapter_agent.update_device(device)
        self.enabled = True

        # TODO: Verify that the uni, pon and logical ports exists

        # Mark the device as REACHABLE and ACTIVE
        device = self.adapter_agent.get_device(device.id)
        device.connect_status = ConnectStatus.REACHABLE
        device.oper_status = OperStatus.ACTIVE
        device.reason = ''
        self.adapter_agent.update_device(device)

        self.log.info('reconciling-ONU-device-ends')

    def update_pm_config(self, device, pm_config):
        # TODO: This has not been tested
        self.log.info('update_pm_config', pm_config=pm_config)
        self.pm_metrics.update(pm_config)

    def start_kpi_collection(self, device_id):
        # TODO: This has not been tested
        def _collect(device_id, prefix):
            from voltha.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs

            if self.enabled:
                try:
                    # Step 1: gather metrics from device
                    port_metrics = self.pm_metrics.collect_port_metrics()

                    # Step 2: prepare the KpiEvent for submission
                    # we can time-stamp them here or could use time derived from OLT
                    ts = arrow.utcnow().timestamp
                    kpi_event = KpiEvent(
                        type=KpiEventType.slice,
                        ts=ts,
                        prefixes={
                            prefix + '.{}'.format(k): MetricValuePairs(metrics=port_metrics[k])
                            for k in port_metrics.keys()}
                    )
                    # Step 3: submit
                    self.adapter_agent.submit_kpis(kpi_event)

                except Exception as e:
                    self.log.exception('failed-to-submit-kpis', e=e)

        self.pm_metrics.start_collector(_collect)

    @inlineCallbacks
    def update_flow_table(self, device, flows):
        #
        # We need to proxy through the OLT to get to the ONU
        # Configuration from here should be using OMCI
        #
        # self.log.info('bulk-flow-update', device_id=device.id, flows=flows)

        import voltha.core.flow_decomposer as fd
        from voltha.protos.openflow_13_pb2 import OFPXMC_OPENFLOW_BASIC, ofp_port

        def is_downstream(port):
            return port == 100  # Need a better way

        def is_upstream(port):
            return not is_downstream(port)

        omci = self._omci

        for flow in flows:
            _type = None
            _port = None
            _vlan_vid = None
            _udp_dst = None
            _udp_src = None
            _ipv4_dst = None
            _ipv4_src = None
            _metadata = None
            _output = None
            _push_tpid = None
            _field = None
            _set_vlan_vid = None
            self.log.info('bulk-flow-update', device_id=device.id, flow=flow)
            try:
                _in_port = fd.get_in_port(flow)
                assert _in_port is not None

                if is_downstream(_in_port):
                    self.log.info('downstream-flow')
                elif is_upstream(_in_port):
                    self.log.info('upstream-flow')
                else:
                    raise Exception('port should be 1 or 2 by our convention')

                _out_port = fd.get_out_port(flow)  # may be None
                self.log.info('out-port', out_port=_out_port)

                for field in fd.get_ofb_fields(flow):
                    if field.type == fd.ETH_TYPE:
                        _type = field.eth_type
                        self.log.info('field-type-eth-type',
                                      eth_type=_type)

                    elif field.type == fd.IP_PROTO:
                        _proto = field.ip_proto
                        self.log.info('field-type-ip-proto',
                                      ip_proto=_proto)

                    elif field.type == fd.IN_PORT:
                        _port = field.port
                        self.log.info('field-type-in-port',
                                      in_port=_port)

                    elif field.type == fd.VLAN_VID:
                        _vlan_vid = field.vlan_vid & 0xfff
                        self.log.info('field-type-vlan-vid',
                                      vlan=_vlan_vid)

                    elif field.type == fd.VLAN_PCP:
                        _vlan_pcp = field.vlan_pcp
                        self.log.info('field-type-vlan-pcp',
                                      pcp=_vlan_pcp)

                    elif field.type == fd.UDP_DST:
                        _udp_dst = field.udp_dst
                        self.log.info('field-type-udp-dst',
                                      udp_dst=_udp_dst)

                    elif field.type == fd.UDP_SRC:
                        _udp_src = field.udp_src
                        self.log.info('field-type-udp-src',
                                      udp_src=_udp_src)

                    elif field.type == fd.IPV4_DST:
                        _ipv4_dst = field.ipv4_dst
                        self.log.info('field-type-ipv4-dst',
                                      ipv4_dst=_ipv4_dst)

                    elif field.type == fd.IPV4_SRC:
                        _ipv4_src = field.ipv4_src
                        self.log.info('field-type-ipv4-src',
                                      ipv4_dst=_ipv4_src)

                    elif field.type == fd.METADATA:
                        _metadata = field.table_metadata
                        self.log.info('field-type-metadata',
                                      metadata=_metadata)

                    else:
                        raise NotImplementedError('field.type={}'.format(
                            field.type))

                for action in fd.get_actions(flow):

                    if action.type == fd.OUTPUT:
                        _output = action.output.port
                        self.log.info('action-type-output',
                                      output=_output, in_port=_in_port)

                    elif action.type == fd.POP_VLAN:
                        self.log.info('action-type-pop-vlan',
                                      in_port=_in_port)

                    elif action.type == fd.PUSH_VLAN:
                        _push_tpid = action.push.ethertype
                        log.info('action-type-push-vlan',
                                 push_tpid=_push_tpid, in_port=_in_port)
                        if action.push.ethertype != 0x8100:
                            self.log.error('unhandled-tpid',
                                           ethertype=action.push.ethertype)

                    elif action.type == fd.SET_FIELD:
                        _field = action.set_field.field.ofb_field
                        assert (action.set_field.field.oxm_class ==
                                OFPXMC_OPENFLOW_BASIC)
                        self.log.info('action-type-set-field',
                                      field=_field, in_port=_in_port)
                        if _field.type == fd.VLAN_VID:
                            _set_vlan_vid = _field.vlan_vid & 0xfff
                            self.log.info('set-field-type-valn-vid', _set_vlan_vid)
                        else:
                            self.log.error('unsupported-action-set-field-type',
                                           field_type=_field.type)
                    else:
                        log.error('unsupported-action-type',
                                  action_type=action.type, in_port=_in_port)

                #
                # All flows created from ONU adapter should be OMCI based
                #
                if _vlan_vid == 0 and _set_vlan_vid != None and _set_vlan_vid != 0:
                    # allow priority tagged packets
                    # Set AR - ExtendedVlanTaggingOperationConfigData
                    #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid

                    results = yield omci.send_delete_vlan_tagging_filter_data(0x2102)

                    # self.send_set_vlan_tagging_filter_data(0x2102, _set_vlan_vid)
                    results = yield omci.send_create_vlan_tagging_filter_data(
                                        0x2102,
                                        _set_vlan_vid)

                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_untagged(
                                        0x202,
                                        0x1000,
                                        _set_vlan_vid)

                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(
                                        0x202,
                                        8,
                                        0,
                                        0,
                                        1,
                                        8,
                                        _set_vlan_vid)

                    # Set AR - ExtendedVlanTaggingOperationConfigData
                    #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid
                    '''
                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(0x205, 8, 0, 0,
                                                   
                    '''

            except Exception as e:
                log.exception('failed-to-install-flow', e=e, flow=flow)

    @inlineCallbacks
    def reboot(self):
        from common.utils.asleep import asleep
        self.log.info('rebooting', device_id=self.device_id)
        self._cancel_deferred()

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

        # Update the operational status to ACTIVATING and connect status to
        # UNREACHABLE
        device = self.adapter_agent.get_device(self.device_id)
        previous_oper_status = device.oper_status
        previous_conn_status = device.connect_status
        device.oper_status = OperStatus.ACTIVATING
        device.connect_status = ConnectStatus.UNREACHABLE
        device.reason = 'Rebooting'

        self.adapter_agent.update_device(device)

        # Sleep 10 secs, simulating a reboot
        # TODO: send alert and clear alert after the reboot
        yield asleep(10)    # TODO: Need to reboot for real

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

        # Change the operational status back to its previous state.  With a
        # real OLT the operational state should be the state the device is
        # after a reboot.
        # Get the latest device reference
        device = self.adapter_agent.get_device(self.device_id)
        device.oper_status = previous_oper_status
        device.connect_status = previous_conn_status
        device.reason = ''
        self.adapter_agent.update_device(device)
        self.log.info('rebooted', device_id=self.device_id)

    def self_test_device(self, device):
        """
        This is called to Self a device based on a NBI call.
        :param device: A Voltha.Device object.
        :return: Will return result of self test
        """
        from voltha.protos.voltha_pb2 import SelfTestResponse
        self.log.info('self-test-device', device=device.id)
        # TODO: Support self test?
        return SelfTestResponse(result=SelfTestResponse.NOT_SUPPORTED)

    def disable(self):
        self.log.info('disabling', device_id=self.device_id)
        self.enabled = False

        # Get the latest device reference
        device = self.adapter_agent.get_device(self.device_id)

        # Disable all ports on that device
        self.adapter_agent.disable_all_ports(self.device_id)

        # Update the device operational status to UNKNOWN
        device.oper_status = OperStatus.UNKNOWN
        device.connect_status = ConnectStatus.UNREACHABLE
        device.reason = 'Disabled'
        self.adapter_agent.update_device(device)

        # Remove the uni logical port from the OLT, if still present
        parent_device = self.adapter_agent.get_device(device.parent_id)
        assert parent_device
        logical_device_id = parent_device.parent_id
        assert logical_device_id

        for uni in self.uni_ports:
            port_id = 'uni-{}'.format(uni.port_number)

            try:
                port = self.adapter_agent.get_logical_port(logical_device_id,
                                                           port_id)
                self.adapter_agent.delete_logical_port(logical_device_id, port)
            except KeyError:
                self.log.info('logical-port-not-found', device_id=self.device_id,
                              portid=port_id)

        # Remove pon port from parent
        for port in self.pon_ports:
            self.adapter_agent.delete_port_reference_from_parent(self.device_id,
                                                                 port.get_port())

        # Just updating the port status may be an option as well
        # port.ofp_port.config = OFPPC_NO_RECV
        # yield self.adapter_agent.update_logical_port(logical_device_id,
        #                                             port)
        # Unregister for proxied message
        self.adapter_agent.unregister_for_proxied_messages(
            device.proxy_address)

        # TODO:
        # 1) Remove all flows from the device
        # 2) Remove the device from ponsim

        self.log.info('disabled', device_id=device.id)

    def reenable(self):
        self.log.info('re-enabling', device_id=self.device_id)
        try:
            # Get the latest device reference
            device = self.adapter_agent.get_device(self.device_id)
            self._cancel_deferred()

            # First we verify that we got parent reference and proxy info
            assert device.parent_id
            assert device.proxy_address.device_id
            # assert device.proxy_address.channel_id

            # Re-register for proxied messages right away
            self.proxy_address = device.proxy_address
            self.adapter_agent.register_for_proxied_messages(
                device.proxy_address)

            # Re-enable the ports on that device
            self.adapter_agent.enable_all_ports(self.device_id)

            # Refresh the port reference
            # self.uni_port = self._get_uni_port()   deprecated

            # Add the pon port reference to the parent
            for port in self.pon_ports:
                # TODO: Send 'enable' to PonPort?
                self.adapter_agent.add_port_reference_to_parent(device.id,
                                                                port.get_port())

            # Update the connect status to REACHABLE
            device.connect_status = ConnectStatus.REACHABLE
            self.adapter_agent.update_device(device)

            # re-add uni port to logical device
            parent_device = self.adapter_agent.get_device(device.parent_id)
            self.logical_device_id = parent_device.parent_id
            assert self.logical_device_id, 'Invalid logical device ID'

            if self.olt_created:
                # vlan non-zero if created via legacy method (not xPON)
                self.uni_port('deprecated').add_logical_port(device.vlan, device.vlan,
                                                             control_vlan=device.vlan)

            device = self.adapter_agent.get_device(device.id)
            device.oper_status = OperStatus.ACTIVE
            device.reason = ''

            self.enabled = True
            self.adapter_agent.update_device(device)

            self.log.info('re-enabled', device_id=device.id)
        except Exception, e:
            self.log.exception('error-reenabling', e=e)
Ejemplo n.º 13
0
    def __init__(self, adapter, device_id):
        self.log = structlog.get_logger(device_id=device_id)
        self.log.debug('function-entry')
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.parent_adapter = None
        self.parent_id = None
        self.device_id = device_id
        self.incoming_messages = DeferredQueue()
        self.event_messages = DeferredQueue()
        self.proxy_address = None
        self.tx_id = 0
        self._enabled = False
        self.alarms = None
        self.pm_metrics = None
        self._omcc_version = OMCCVersion.Unknown
        self._total_tcont_count = 0  # From ANI-G ME
        self._qos_flexibility = 0  # From ONT2_G ME

        self._onu_indication = None
        self._unis = dict()  # Port # -> UniPort
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 0)

        self._pon = None
        # TODO: probably shouldnt be hardcoded, determine from olt maybe?
        self._pon_port_number = 100
        self.logical_device_id = None

        self._heartbeat = HeartBeat.create(self, device_id)

        # Set up OpenOMCI environment
        self._onu_omci_device = None
        self._dev_info_loaded = False
        self._deferred = None

        self._in_sync_subscription = None
        self._connectivity_subscription = None
        self._capabilities_subscription = None

        self.mac_bridge_service_profile_entity_id = 0x201
        self.gal_enet_profile_entity_id = 0x1

        self._tp_service_specific_task = dict()
        self._tech_profile_download_done = dict()

        # Initialize KV store client
        self.args = registry('main').get_args()
        if self.args.backend == 'etcd':
            host, port = self.args.etcd.split(':', 1)
            self.kv_client = EtcdStore(
                host, port, TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX)
        elif self.args.backend == 'consul':
            host, port = self.args.consul.split(':', 1)
            self.kv_client = ConsulStore(
                host, port, TechProfile.KV_STORE_TECH_PROFILE_PATH_PREFIX)
        else:
            self.log.error('Invalid-backend')
            raise Exception("Invalid-backend-for-kv-store")

        # Handle received ONU event messages
        reactor.callLater(0, self.handle_onu_events)
Ejemplo n.º 14
0
    def __init__(self,
                 agent,
                 device_id,
                 tasks,
                 advertise_events=False,
                 states=DEFAULT_STATES,
                 transitions=DEFAULT_TRANSITIONS,
                 initial_state='disabled',
                 timeout_delay=DEFAULT_RETRY,
                 tick_delay=DEFAULT_TICK_DELAY,
                 interval_skew=DEFAULT_INTERVAL_SKEW,
                 collect_attempts=DEFAULT_COLLECT_ATTEMPTS,
                 create_attempts=DEFAULT_CREATE_ATTEMPTS):
        """
        Class initialization

        :param agent: (OpenOmciAgent) Agent
        :param device_id: (str) ONU Device ID
        :param tasks: (dict) Tasks to run
        :param advertise_events: (bool) Advertise events on OpenOMCI Event Bus
        :param states: (list) List of valid states
        :param transitions: (dict) Dictionary of triggers and state changes
        :param initial_state: (str) Initial state machine state
        :param timeout_delay: (int/float) Number of seconds after a timeout to pause
        :param tick_delay: (int/float) Collection poll check delay while idle
        :param interval_skew: (int/float) Seconds to randomly skew the next interval
                              collection to spread out requests for PM intervals
        :param collect_attempts: (int) Max requests for a single PM interval before fail
        :param create_attempts: (int) Max attempts to create PM Managed entities before stopping state machine
        """
        self.log = structlog.get_logger(device_id=device_id)

        self._agent = agent
        self._device_id = device_id
        self._device = None
        self._pm_config = None
        self._timeout_delay = timeout_delay
        self._tick_delay = tick_delay
        self._interval_skew = interval_skew
        self._collect_attempts = collect_attempts
        self._create_attempts = create_attempts

        self._sync_time_task = tasks['sync-time']
        self._get_interval_task = tasks['collect-data']
        self._create_pm_task = tasks['create-pm']
        self._delete_pm_task = tasks['delete-pm']
        self._advertise_events = advertise_events

        self._omci_cc_subscriptions = {  # RxEvent.enum -> Subscription Object
            RxEvent.MIB_Reset: None,
            RxEvent.Create: None,
            RxEvent.Delete: None
        }
        self._omci_cc_sub_mapping = {
            RxEvent.MIB_Reset: self.on_mib_reset_response,
            RxEvent.Create: self.on_create_response,
            RxEvent.Delete: self.on_delete_response,
        }
        self._me_watch_list = {
            MacBridgePortConfigurationData.class_id: {
                'create-delete': self.add_remove_enet_frame_pm,
                'instances':
                dict()  # BP entity_id -> (PM class_id, PM entity_id)
            }
        }
        self._deferred = None
        self._task_deferred = None
        self._current_task = None
        self._add_me_deferred = None
        self._delete_me_deferred = None
        self._next_interval = None
        self._enet_entity_id = IndexPool(1024, 1)
        self._add_pm_me_retry = 0

        # (Class ID, Instance ID) -> Collect attempts remaining
        self._pm_me_collect_retries = dict()
        self._pm_me_extended_info = dict()
        self._add_pm_me = dict(
        )  # (pm cid, pm eid) -> (me cid, me eid, upstream)
        self._del_pm_me = set()

        # Pollable PM items
        # Note that some items the KPI extracts are not listed below. These are the
        # administrative states, operational states, and sensed ethernet type. The values
        # in the MIB database should be accurate for these items.

        self._ani_g_items = ["optical_signal_level", "transmit_optical_level"]
        self._next_poll_time = datetime.utcnow()
        self._poll_interval = 60  # TODO: Fixed at once a minute

        # Statistics and attributes
        # TODO: add any others if it will support problem diagnosis

        # Set up state machine to manage states
        self.machine = Machine(model=self,
                               states=states,
                               transitions=transitions,
                               initial=initial_state,
                               queued=True,
                               ignore_invalid_triggers=True,
                               name='{}-{}'.format(self.__class__.__name__,
                                                   device_id))
        try:
            import logging
            logging.getLogger('transitions').setLevel(logging.WARNING)
        except Exception as e:
            self.log.exception('log-level-failed', e=e)
Ejemplo n.º 15
0
class AdtranOnuHandler(AdtranXPON):
    def __init__(self, adapter, device_id):
        kwargs = dict()
        super(AdtranOnuHandler, self).__init__(**kwargs)
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.log = structlog.get_logger(device_id=device_id)
        self.logical_device_id = None
        self.proxy_address = None
        self._enabled = False
        self.pm_metrics = None
        self.alarms = None
        self._mgmt_gemport_aes = False
        self._upstream_channel_speed = 0

        self._openomci = OMCI(self, adapter.omci_agent)
        self._in_sync_subscription = None

        self._onu_port_number = 0
        self._pon_port_number = 1
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 0)

        self._unis = dict()         # Port # -> UniPort
        self._pon = PonPort.create(self, self._pon_port_number)
        self._heartbeat = HeartBeat.create(self, device_id)
        self._deferred = None

        # Flow entries
        self._flows = dict()

        # OMCI resources
        # TODO: Some of these could be dynamically chosen
        self.vlan_tcis_1 = 0x900
        self.mac_bridge_service_profile_entity_id = self.vlan_tcis_1
        self.gal_enet_profile_entity_id = 0     # Was 0x100, but ONU seems to overwrite and use zero

    def __str__(self):
        return "AdtranOnuHandler: {}".format(self.device_id)

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

    @property
    def enabled(self):
        return self._enabled

    @enabled.setter
    def enabled(self, value):
        assert isinstance(value, bool), 'enabled is a boolean'
        if self._enabled != value:
            self._enabled = value
            if self._enabled:
                self.start()
            else:
                self.stop()

    @property
    def mgmt_gemport_aes(self):
        return self._mgmt_gemport_aes

    @mgmt_gemport_aes.setter
    def mgmt_gemport_aes(self, value):
        if self._mgmt_gemport_aes != value:
            self._mgmt_gemport_aes = value
            # TODO: Anything else

    @property
    def upstream_channel_speed(self):
        return self._upstream_channel_speed

    @upstream_channel_speed.setter
    def upstream_channel_speed(self, value):
        if self._upstream_channel_speed != value:
            self._upstream_channel_speed = value
            # TODO: Anything else

    @property
    def openomci(self):
        return self._openomci

    @property
    def heartbeat(self):
        return self._heartbeat

    @property
    def uni_ports(self):
        return self._unis.values()

    def uni_port(self, port_no_or_name):
        if isinstance(port_no_or_name, (str, unicode)):
            return next((uni for uni in self.uni_ports
                         if uni.name == port_no_or_name), None)

        assert isinstance(port_no_or_name, int), 'Invalid parameter type'
        return self._unis.get(port_no_or_name)

    def pon_port(self, port_no=None):
        return self._pon if port_no is None or port_no == self._pon.port_number else None

    @property
    def pon_ports(self):
        return [self._pon]

    @property
    def _next_port_number(self):
        return self._port_number_pool.get_next()

    def _release_port_number(self, number):
        self._port_number_pool.release(number)

    def start(self):
        assert self._enabled, 'Start should only be called if enabled'
        self._cancel_deferred()

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

        # OpenOMCI Startup
        self._subscribe_to_events()
        self._openomci.enabled = True

        # Port startup
        if self._pon is not None:
            self._pon.enabled = True

        for port in self.uni_ports:
            port.enabled = True

        # Heartbeat
        self._heartbeat.enabled = True

    def stop(self):
        assert not self._enabled, 'Stop should only be called if disabled'
        self._cancel_deferred()

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

        # Heartbeat
        self._heartbeat.enabled = False

        # OMCI subscriptions
        self._unsubscribe_to_events()

        # Port shutdown
        for port in self.uni_ports:
            port.enabled = False

        if self._pon is not None:
            self._pon.enabled = False

        # OMCI Communications
        self._openomci.enabled = False

    def receive_message(self, msg):
        if self.enabled:
            # TODO: Have OpenOMCI actually receive the messages
            self.openomci.receive_message(msg)

    def activate(self, device):
        self.log.info('activating')

        try:
            # first we verify that we got parent reference and proxy info
            assert device.parent_id, 'Invalid Parent ID'
            assert device.proxy_address.device_id, 'Invalid Device ID'

            # register for proxied messages right away
            self.proxy_address = device.proxy_address
            self.adapter_agent.register_for_proxied_messages(device.proxy_address)

            # initialize device info
            device.root = False
            device.vendor = 'Adtran Inc.'
            device.model = 'n/a'
            device.hardware_version = 'n/a'
            device.firmware_version = 'n/a'
            device.reason = ''
            device.connect_status = ConnectStatus.UNKNOWN

            # Register physical ports.  Should have at least one of each
            self.adapter_agent.add_port(device.id, self._pon.get_port())

            def xpon_not_found():
                self.enabled = True

            # Schedule xPON 'not found' startup for 10 seconds from now. We will
            # easily get a vONT-ANI create within that time if xPON is being used
            # as this is how we are initially launched and activated in the first
            # place if xPON is in use.
            reactor.callLater(10, xpon_not_found)   # TODO: Clean up old xPON delay

            # reference of uni_port is required when re-enabling the device if
            # it was disabled previously
            # Need to query ONU for number of supported uni ports
            # For now, temporarily set number of ports to 1 - port #2
            parent_device = self.adapter_agent.get_device(device.parent_id)

            self.logical_device_id = parent_device.parent_id
            self.adapter_agent.update_device(device)

            ############################################################################
            # Setup PM configuration for this device
            # Pass in ONU specific options
            kwargs = {
                OnuPmMetrics.DEFAULT_FREQUENCY_KEY: OnuPmMetrics.DEFAULT_ONU_COLLECTION_FREQUENCY,
                'heartbeat': self.heartbeat,
                OnuOmciPmMetrics.OMCI_DEV_KEY: self.openomci.onu_omci_device
            }
            self.pm_metrics = OnuPmMetrics(self.adapter_agent, self.device_id,
                                           self.logical_device_id, grouped=True,
                                           freq_override=False, **kwargs)
            pm_config = self.pm_metrics.make_proto()
            self.openomci.set_pm_config(self.pm_metrics.omci_pm.openomci_interval_pm)
            self.log.info("initial-pm-config", pm_config=pm_config)
            self.adapter_agent.update_device_pm_config(pm_config, init=True)

            ############################################################################
            # Setup Alarm handler
            self.alarms = AdapterAlarms(self.adapter_agent, device.id, self.logical_device_id)
            self.openomci.onu_omci_device.alarm_synchronizer.set_alarm_params(mgr=self.alarms,
                                                                              ani_ports=[self._pon])
            ############################################################################
            # Start collecting stats from the device after a brief pause
            reactor.callLater(30, self.pm_metrics.start_collector)

        except Exception as e:
            self.log.exception('activate-failure', e=e)
            device.reason = 'Failed to activate: {}'.format(e.message)
            device.connect_status = ConnectStatus.UNREACHABLE
            device.oper_status = OperStatus.FAILED
            self.adapter_agent.update_device(device)

    def reconcile(self, device):
        self.log.info('reconciling-ONU-device-starts')

        # first we verify that we got parent reference and proxy info
        assert device.parent_id
        assert device.proxy_address.device_id
        # assert device.proxy_address.channel_id
        self._cancel_deferred()

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

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

        # Set the connection status to REACHABLE
        device.connect_status = ConnectStatus.REACHABLE
        self.adapter_agent.update_device(device)
        self.enabled = True

        # TODO: Verify that the uni, pon and logical ports exists

        # Mark the device as REACHABLE and ACTIVE
        device = self.adapter_agent.get_device(device.id)
        device.connect_status = ConnectStatus.REACHABLE
        device.oper_status = OperStatus.ACTIVE
        device.reason = ''
        self.adapter_agent.update_device(device)

        self.log.info('reconciling-ONU-device-ends')

    def update_pm_config(self, device, pm_config):
        # TODO: This has not been tested
        self.log.info('update_pm_config', pm_config=pm_config)
        self.pm_metrics.update(pm_config)

    @inlineCallbacks
    def update_flow_table(self, flows):
        if len(flows) == 0:
            returnValue('nop')  # TODO:  Do we need to delete all flows if empty?

        self.log.debug('bulk-flow-update', flows=flows)
        valid_flows = set()

        for flow in flows:
            # Decode it
            flow_entry = FlowEntry.create(flow, self)

            # Already handled?
            if flow_entry.flow_id in self._flows:
                valid_flows.add(flow_entry.flow_id)

            if flow_entry is None or flow_entry.flow_direction not in \
                    FlowEntry.upstream_flow_types | FlowEntry.downstream_flow_types:
                continue

            is_upstream = flow_entry.flow_direction in FlowEntry.upstream_flow_types

            # Ignore untagged upstream etherType flows. These are trapped at the
            # OLT and the default flows during initial OMCI service download will
            # send them to the Default VLAN (4091) port for us
            if is_upstream and flow_entry.vlan_vid is None and flow_entry.etype is not None:
                continue

            # Also ignore upstream untagged/priority tag that sets priority tag
            # since that is already installed and any user-data flows for upstream
            # priority tag data will be at a higher level.  Also should ignore the
            # corresponding priority-tagged to priority-tagged flow as well.
            if (flow_entry.vlan_vid == 0 and flow_entry.set_vlan_vid == 0) or \
                    (flow_entry.vlan_vid is None and flow_entry.set_vlan_vid == 0
                     and not is_upstream):
                continue

            # Add it to hardware
            try:
                def failed(_reason, fid):
                    del self._flows[fid]

                task = AdtnInstallFlowTask(self.openomci.omci_agent, self, flow_entry)
                d = self.openomci.onu_omci_device.task_runner.queue_task(task)
                d.addErrback(failed, flow_entry.flow_id)

                valid_flows.add(flow_entry.flow_id)
                self._flows[flow_entry.flow_id] = flow_entry

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

        # Now check for flows that were missing in the bulk update
        deleted_flows = set(self._flows.keys()) - valid_flows

        for flow_id in deleted_flows:
            try:
                del_flow = self._flows[flow_id]

                task = AdtnRemoveFlowTask(self.openomci.omci_agent, self, del_flow)
                self.openomci.onu_omci_device.task_runner.queue_task(task)
                # TODO: Change to success/failure callback checks later
                # d.addCallback(success, flow_entry.flow_id)
                del self._flows[flow_id]

            except Exception as e:
                self.log.exception('flow-remove', e=e, flow=self._flows[flow_id])

    @inlineCallbacks
    def reboot(self):
        self.log.info('rebooting', device_id=self.device_id)
        self._cancel_deferred()

        reregister = False
        # try:
        #     # Drop registration for adapter messages
        #     reregister = True
        #     self.adapter_agent.unregister_for_inter_adapter_messages()
        #
        # except KeyError:
        #     reregister = False

        # Update the operational status to ACTIVATING and connect status to
        # UNREACHABLE
        device = self.adapter_agent.get_device(self.device_id)

        previous_oper_status = device.oper_status
        previous_conn_status = device.connect_status

        device.oper_status = OperStatus.ACTIVATING
        device.connect_status = ConnectStatus.UNREACHABLE
        device.reason = 'Attempting reboot'
        self.adapter_agent.update_device(device)

        # TODO: send alert and clear alert after the reboot
        try:
            ######################################################
            # MIB Reset
            yield self.openomci.onu_omci_device.reboot(timeout=1)

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

        # Reboot in progress. A reboot may take up to 3 min 30 seconds
        # Go ahead and pause less than that and start to look
        # for it being alive
        device.reason = 'reboot in progress'
        self.adapter_agent.update_device(device)

        # Disable OpenOMCI
        self.omci.enabled = False
        self._deferred = reactor.callLater(_ONU_REBOOT_MIN,
                                           self._finish_reboot,
                                           previous_oper_status,
                                           previous_conn_status,
                                           reregister)

    @inlineCallbacks
    def _finish_reboot(self, previous_oper_status, previous_conn_status,
                       reregister):
        # Restart OpenOMCI
        self.omci.enabled = True

        device = self.adapter_agent.get_device(self.device_id)
        device.oper_status = previous_oper_status
        device.connect_status = previous_conn_status
        device.reason = ''
        self.adapter_agent.update_device(device)

        # if reregister:
        #     self.adapter_agent.register_for_inter_adapter_messages()

        self.log.info('reboot-complete', device_id=self.device_id)

    def self_test_device(self, device):
        """
        This is called to Self a device based on a NBI call.
        :param device: A Voltha.Device object.
        :return: Will return result of self test
        """
        from voltha.protos.voltha_pb2 import SelfTestResponse
        self.log.info('self-test-device', device=device.id)
        # TODO: Support self test?
        return SelfTestResponse(result=SelfTestResponse.NOT_SUPPORTED)

    def disable(self):
        self.log.info('disabling', device_id=self.device_id)
        try:
            # Get the latest device reference (If deleted by OLT, it will
            # throw an exception

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

            # Disable all ports on that device
            self.adapter_agent.disable_all_ports(self.device_id)

            # Update the device operational status to UNKNOWN
            device.oper_status = OperStatus.UNKNOWN
            device.connect_status = ConnectStatus.UNREACHABLE
            device.reason = 'Disabled'
            self.adapter_agent.update_device(device)

            # Remove the uni logical port from the OLT, if still present
            parent_device = self.adapter_agent.get_device(device.parent_id)
            assert parent_device

            for uni in self.uni_ports:
                # port_id = 'uni-{}'.format(uni.port_number)
                port_id = uni.port_id_name()
                try:
                    logical_device_id = parent_device.parent_id
                    assert logical_device_id
                    port = self.adapter_agent.get_logical_port(logical_device_id,port_id)
                    self.adapter_agent.delete_logical_port(logical_device_id, port)
                except KeyError:
                    self.log.info('logical-port-not-found', device_id=self.device_id,
                                  portid=port_id)

            # Remove pon port from parent and disable
            if self._pon is not None:
                self.adapter_agent.delete_port_reference_from_parent(self.device_id,
                                                                     self._pon.get_port())
                self._pon.enabled = False

            # Unregister for proxied message
            self.adapter_agent.unregister_for_proxied_messages(device.proxy_address)

        except Exception as _e:
            pass    # This is expected if OLT has deleted the ONU device handler

        # And disable OMCI as well
        self.enabled = False
        self.log.info('disabled', device_id=device.id)

    def reenable(self):
        self.log.info('re-enabling', device_id=self.device_id)
        try:
            # Get the latest device reference
            device = self.adapter_agent.get_device(self.device_id)
            self._cancel_deferred()

            # First we verify that we got parent reference and proxy info
            assert device.parent_id
            assert device.proxy_address.device_id
            # assert device.proxy_address.channel_id

            # Re-register for proxied messages right away
            self.proxy_address = device.proxy_address
            self.adapter_agent.register_for_proxied_messages(
                device.proxy_address)

            # Re-enable the ports on that device
            self.adapter_agent.enable_all_ports(self.device_id)

            # Add the pon port reference to the parent
            if self._pon is not None:
                self._pon.enabled = True
                self.adapter_agent.add_port_reference_to_parent(device.id,
                                                                self._pon.get_port())
            # Update the connect status to REACHABLE
            device.connect_status = ConnectStatus.REACHABLE
            self.adapter_agent.update_device(device)

            # re-add uni port to logical device
            parent_device = self.adapter_agent.get_device(device.parent_id)
            self.logical_device_id = parent_device.parent_id
            assert self.logical_device_id, 'Invalid logical device ID'

            # reestablish logical ports for each UNI
            for uni in self.uni_ports:
                self.adapter_agent.add_port(device.id, uni.get_port())
                uni.add_logical_port(uni.logical_port_number)

            device = self.adapter_agent.get_device(device.id)
            device.oper_status = OperStatus.ACTIVE
            device.connect_status = ConnectStatus.REACHABLE
            device.reason = ''

            self.enabled = True
            self.adapter_agent.update_device(device)

            self.log.info('re-enabled', device_id=device.id)

        except Exception, e:
            self.log.exception('error-reenabling', e=e)
Ejemplo n.º 16
0
class AdtranOnuHandler(AdtranXPON):
    def __init__(self, adapter, device_id):
        kwargs = dict()
        super(AdtranOnuHandler, self).__init__(**kwargs)
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.log = structlog.get_logger(device_id=device_id)
        self.logical_device_id = None
        self.proxy_address = None
        self._event_messages = None
        self._enabled = False
        self.pm_metrics = None
        self.alarms = None
        self._mgmt_gemport_aes = False
        self._upstream_channel_speed = 0

        self._unis = dict()  # Port # -> UniPort
        self._pons = dict()  # Port # -> PonPort
        self._heartbeat = HeartBeat.create(self, device_id)

        self._deferred = None
        self._event_deferred = None
        self._omci = None
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 1)

        self._olt_created = False  # True if deprecated method of OLT creating DA is used
        self._is_mock = False

    def __str__(self):
        return "AdtranOnuHandler: {}".format(self.device_id)

    def _cancel_deferred(self):
        d1, self._deferred = self._deferred, None
        d2, self._event_deferred = self._event_deferred, None

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

    @property
    def enabled(self):
        return self._enabled

    @enabled.setter
    def enabled(self, value):
        assert isinstance(value, bool), 'enabled is a boolean'
        if self._enabled != value:
            self._enabled = value
            if self._enabled:
                self.start()
            else:
                self.stop()

    @property
    def mgmt_gemport_aes(self):
        return self._mgmt_gemport_aes

    @mgmt_gemport_aes.setter
    def mgmt_gemport_aes(self, value):
        if self._mgmt_gemport_aes != value:
            self._mgmt_gemport_aes = value
            # TODO: Anything else

    @property
    def upstream_channel_speed(self):
        return self._upstream_channel_speed

    @upstream_channel_speed.setter
    def upstream_channel_speed(self, value):
        if self._upstream_channel_speed != value:
            self._upstream_channel_speed = value
            # TODO: Anything else

    @property
    def is_mock(self):
        return self._is_mock  # Not pointing to real hardware

    @property
    def olt_created(self):
        return self._olt_created  # ONU was created with deprecated 'child_device_detected' call

    @property
    def omci(self):
        return self._omci

    @property
    def heartbeat(self):
        return self._heartbeat

    @property
    def uni_ports(self):
        return self._unis.values()

    def uni_port(self, port_no_or_name):
        if isinstance(port_no_or_name, (str, unicode)):
            return next(
                (uni for uni in self.uni_ports if uni.name == port_no_or_name),
                None)

        assert isinstance(port_no_or_name, int), 'Invalid parameter type'
        return self._unis.get(port_no_or_name)

    @property
    def pon_ports(self):
        return self._pons.values()

    def pon_port(self, port_no):
        return self._pons.get(port_no)

    @property
    def _next_port_number(self):
        return self._port_number_pool.get_next()

    def _release_port_number(self, number):
        self._port_number_pool.release(number)

    def start(self):
        assert self._enabled, 'Start should only be called if enabled'
        #
        # TODO: Perform common startup tasks here
        #
        self._cancel_deferred()

        self._omci = OMCI_CC(self.adapter_agent,
                             self.device_id,
                             custom_me_entries=onu_custom_entity_classes)
        self._omci.enabled = True

        # Handle received ONU event messages
        self._event_messages = DeferredQueue()
        self._event_deferred = reactor.callLater(0, self._handle_onu_events)

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

        # Port startup
        for port in self.uni_ports:
            port.enabled = True

        for port in self.pon_ports:
            port.enabled = True

        # Heartbeat
        self._heartbeat.enabled = True

    def stop(self):
        assert not self._enabled, 'Stop should only be called if disabled'
        #
        # TODO: Perform common shutdown tasks here
        #
        self._cancel_deferred()

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

        # Heartbeat
        self._heartbeat.stop()

        # Port shutdown
        for port in self.uni_ports:
            port.enabled = False

        for port in self.pon_ports:
            port.enabled = False

        omci, self._omci = self._omci, None
        if omci is not None:
            omci.enabled = False

        queue, self._event_deferred = self._event_deferred, None
        if queue is not None:
            while queue.pending:
                _ = yield queue.get()

    def receive_message(self, msg):
        if self._omci is not None and self.enabled:
            self._omci.receive_message(msg)

    def activate(self, device):
        self.log.info('activating')

        # first we verify that we got parent reference and proxy info
        assert device.parent_id, 'Invalid Parent ID'
        assert device.proxy_address.device_id, 'Invalid Device ID'

        if device.vlan:
            # vlan non-zero if created via legacy method (not xPON). Also
            # Set a random serial number since not xPON based
            self._olt_created = True

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

        # initialize device info
        device.root = True
        device.vendor = 'Adtran Inc.'
        device.model = 'n/a'
        device.hardware_version = 'n/a'
        device.firmware_version = 'n/a'
        device.reason = ''

        # TODO: Support more versions as needed
        images = Image(version='NOT AVAILABLE')
        device.images.image.extend([images])

        device.connect_status = ConnectStatus.UNKNOWN

        ############################################################################
        # Setup PM configuration for this device

        self.pm_metrics = OnuPmMetrics(self,
                                       device,
                                       grouped=True,
                                       freq_override=False)
        pm_config = self.pm_metrics.make_proto()
        self.log.info("initial-pm-config", pm_config=pm_config)
        self.adapter_agent.update_device_pm_config(pm_config, init=True)

        ############################################################################
        # Setup Alarm handler

        self.alarms = AdapterAlarms(self.adapter, device.id)

        # reference of uni_port is required when re-enabling the device if
        # it was disabled previously
        # Need to query ONU for number of supported uni ports
        # For now, temporarily set number of ports to 1 - port #2

        # Register physical ports.  Should have at least one of each

        pon_port = PonPort.create(self, self._next_port_number)

        self._pons[pon_port.port_number] = pon_port
        self.adapter_agent.add_port(device.id, pon_port.get_port())

        parent_device = self.adapter_agent.get_device(device.parent_id)
        self.logical_device_id = parent_device.parent_id
        assert self.logical_device_id, 'Invalid logical device ID'

        if self._olt_created:
            # vlan non-zero if created via legacy method (not xPON). Also
            # Set a random serial number since not xPON based

            uni_port = UniPort.create(self, self._next_port_number,
                                      'deprecated', device.vlan)
            self._unis[uni_port.port_number] = uni_port
            self.adapter_agent.add_port(device.id, uni_port.get_port())

            device.serial_number = uuid4().hex
            uni_port.add_logical_port(device.vlan, control_vlan=device.vlan)

            # Start things up for this ONU Handler.
            self.enabled = True

        # Start collecting stats from the device after a brief pause
        reactor.callLater(30, self.start_kpi_collection, device.id)

        self.adapter_agent.update_device(device)

    def reconcile(self, device):
        self.log.info('reconciling-ONU-device-starts')

        # first we verify that we got parent reference and proxy info
        assert device.parent_id
        assert device.proxy_address.device_id
        # assert device.proxy_address.channel_id
        self._cancel_deferred()

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

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

        # Set the connection status to REACHABLE
        device.connect_status = ConnectStatus.REACHABLE
        self.adapter_agent.update_device(device)
        self.enabled = True

        # TODO: Verify that the uni, pon and logical ports exists

        # Mark the device as REACHABLE and ACTIVE
        device = self.adapter_agent.get_device(device.id)
        device.connect_status = ConnectStatus.REACHABLE
        device.oper_status = OperStatus.ACTIVE
        device.reason = ''
        self.adapter_agent.update_device(device)

        self.log.info('reconciling-ONU-device-ends')

    def update_pm_config(self, device, pm_config):
        # TODO: This has not been tested
        self.log.info('update_pm_config', pm_config=pm_config)
        self.pm_metrics.update(pm_config)

    def start_kpi_collection(self, device_id):
        # TODO: This has not been tested
        def _collect(device_id, prefix):
            from voltha.protos.events_pb2 import KpiEvent, KpiEventType, MetricValuePairs

            if self.enabled:
                try:
                    # Step 1: gather metrics from device
                    port_metrics = self.pm_metrics.collect_port_metrics()

                    # Step 2: prepare the KpiEvent for submission
                    # we can time-stamp them here or could use time derived from OLT
                    ts = arrow.utcnow().timestamp
                    kpi_event = KpiEvent(
                        type=KpiEventType.slice,
                        ts=ts,
                        prefixes={
                            prefix + '.{}'.format(k):
                            MetricValuePairs(metrics=port_metrics[k])
                            for k in port_metrics.keys()
                        })
                    # Step 3: submit
                    self.adapter_agent.submit_kpis(kpi_event)

                except Exception as e:
                    self.log.exception('failed-to-submit-kpis', e=e)

        self.pm_metrics.start_collector(_collect)

    @inlineCallbacks
    def update_flow_table(self, device, flows):
        #
        # We need to proxy through the OLT to get to the ONU
        # Configuration from here should be using OMCI
        #
        # self.log.info('bulk-flow-update', device_id=device.id, flows=flows)

        import voltha.core.flow_decomposer as fd
        from voltha.protos.openflow_13_pb2 import OFPXMC_OPENFLOW_BASIC, ofp_port

        def is_downstream(port):
            return port == 100  # Need a better way

        def is_upstream(port):
            return not is_downstream(port)

        omci = self._omci

        for flow in flows:
            _type = None
            _port = None
            _vlan_vid = None
            _udp_dst = None
            _udp_src = None
            _ipv4_dst = None
            _ipv4_src = None
            _metadata = None
            _output = None
            _push_tpid = None
            _field = None
            _set_vlan_vid = None
            self.log.info('bulk-flow-update', device_id=device.id, flow=flow)
            try:
                _in_port = fd.get_in_port(flow)
                assert _in_port is not None

                if is_downstream(_in_port):
                    self.log.info('downstream-flow')
                elif is_upstream(_in_port):
                    self.log.info('upstream-flow')
                else:
                    raise Exception('port should be 1 or 2 by our convention')

                _out_port = fd.get_out_port(flow)  # may be None
                self.log.info('out-port', out_port=_out_port)

                for field in fd.get_ofb_fields(flow):
                    if field.type == fd.ETH_TYPE:
                        _type = field.eth_type
                        self.log.info('field-type-eth-type', eth_type=_type)

                    elif field.type == fd.IP_PROTO:
                        _proto = field.ip_proto
                        self.log.info('field-type-ip-proto', ip_proto=_proto)

                    elif field.type == fd.IN_PORT:
                        _port = field.port
                        self.log.info('field-type-in-port', in_port=_port)

                    elif field.type == fd.VLAN_VID:
                        _vlan_vid = field.vlan_vid & 0xfff
                        self.log.info('field-type-vlan-vid', vlan=_vlan_vid)

                    elif field.type == fd.VLAN_PCP:
                        _vlan_pcp = field.vlan_pcp
                        self.log.info('field-type-vlan-pcp', pcp=_vlan_pcp)

                    elif field.type == fd.UDP_DST:
                        _udp_dst = field.udp_dst
                        self.log.info('field-type-udp-dst', udp_dst=_udp_dst)

                    elif field.type == fd.UDP_SRC:
                        _udp_src = field.udp_src
                        self.log.info('field-type-udp-src', udp_src=_udp_src)

                    elif field.type == fd.IPV4_DST:
                        _ipv4_dst = field.ipv4_dst
                        self.log.info('field-type-ipv4-dst',
                                      ipv4_dst=_ipv4_dst)

                    elif field.type == fd.IPV4_SRC:
                        _ipv4_src = field.ipv4_src
                        self.log.info('field-type-ipv4-src',
                                      ipv4_dst=_ipv4_src)

                    elif field.type == fd.METADATA:
                        _metadata = field.table_metadata
                        self.log.info('field-type-metadata',
                                      metadata=_metadata)

                    else:
                        raise NotImplementedError('field.type={}'.format(
                            field.type))

                for action in fd.get_actions(flow):

                    if action.type == fd.OUTPUT:
                        _output = action.output.port
                        self.log.info('action-type-output',
                                      output=_output,
                                      in_port=_in_port)

                    elif action.type == fd.POP_VLAN:
                        self.log.info('action-type-pop-vlan', in_port=_in_port)

                    elif action.type == fd.PUSH_VLAN:
                        _push_tpid = action.push.ethertype
                        log.info('action-type-push-vlan',
                                 push_tpid=_push_tpid,
                                 in_port=_in_port)
                        if action.push.ethertype != 0x8100:
                            self.log.error('unhandled-tpid',
                                           ethertype=action.push.ethertype)

                    elif action.type == fd.SET_FIELD:
                        _field = action.set_field.field.ofb_field
                        assert (action.set_field.field.oxm_class ==
                                OFPXMC_OPENFLOW_BASIC)
                        self.log.info('action-type-set-field',
                                      field=_field,
                                      in_port=_in_port)
                        if _field.type == fd.VLAN_VID:
                            _set_vlan_vid = _field.vlan_vid & 0xfff
                            self.log.info('set-field-type-valn-vid',
                                          _set_vlan_vid)
                        else:
                            self.log.error('unsupported-action-set-field-type',
                                           field_type=_field.type)
                    else:
                        log.error('unsupported-action-type',
                                  action_type=action.type,
                                  in_port=_in_port)

                #
                # All flows created from ONU adapter should be OMCI based
                #
                if _vlan_vid == 0 and _set_vlan_vid != None and _set_vlan_vid != 0:
                    # allow priority tagged packets
                    # Set AR - ExtendedVlanTaggingOperationConfigData
                    #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid

                    results = yield omci.send_delete_vlan_tagging_filter_data(
                        0x2102)

                    # self.send_set_vlan_tagging_filter_data(0x2102, _set_vlan_vid)
                    results = yield omci.send_create_vlan_tagging_filter_data(
                        0x2102, _set_vlan_vid)

                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_untagged(
                        0x202, 0x1000, _set_vlan_vid)

                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(
                        0x202, 8, 0, 0, 1, 8, _set_vlan_vid)

                    # Set AR - ExtendedVlanTaggingOperationConfigData
                    #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid
                    '''
                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(0x205, 8, 0, 0,
                                                   
                    '''

            except Exception as e:
                log.exception('failed-to-install-flow', e=e, flow=flow)

    @inlineCallbacks
    def reboot(self):
        from common.utils.asleep import asleep
        self.log.info('rebooting', device_id=self.device_id)
        self._cancel_deferred()

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

        # Update the operational status to ACTIVATING and connect status to
        # UNREACHABLE
        device = self.adapter_agent.get_device(self.device_id)
        previous_oper_status = device.oper_status
        previous_conn_status = device.connect_status
        device.oper_status = OperStatus.ACTIVATING
        device.connect_status = ConnectStatus.UNREACHABLE
        device.reason = 'Rebooting'

        self.adapter_agent.update_device(device)

        # Sleep 10 secs, simulating a reboot
        # TODO: send alert and clear alert after the reboot
        yield asleep(10)  # TODO: Need to reboot for real

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

        # Change the operational status back to its previous state.  With a
        # real OLT the operational state should be the state the device is
        # after a reboot.
        # Get the latest device reference
        device = self.adapter_agent.get_device(self.device_id)
        device.oper_status = previous_oper_status
        device.connect_status = previous_conn_status
        device.reason = ''
        self.adapter_agent.update_device(device)
        self.log.info('rebooted', device_id=self.device_id)

    def self_test_device(self, device):
        """
        This is called to Self a device based on a NBI call.
        :param device: A Voltha.Device object.
        :return: Will return result of self test
        """
        from voltha.protos.voltha_pb2 import SelfTestResponse
        self.log.info('self-test-device', device=device.id)
        # TODO: Support self test?
        return SelfTestResponse(result=SelfTestResponse.NOT_SUPPORTED)

    def disable(self):
        self.log.info('disabling', device_id=self.device_id)
        self.enabled = False

        # Get the latest device reference
        device = self.adapter_agent.get_device(self.device_id)

        # Disable all ports on that device
        self.adapter_agent.disable_all_ports(self.device_id)

        # Update the device operational status to UNKNOWN
        device.oper_status = OperStatus.UNKNOWN
        device.connect_status = ConnectStatus.UNREACHABLE
        device.reason = 'Disabled'
        self.adapter_agent.update_device(device)

        # Remove the uni logical port from the OLT, if still present
        parent_device = self.adapter_agent.get_device(device.parent_id)
        assert parent_device
        logical_device_id = parent_device.parent_id
        assert logical_device_id

        for uni in self.uni_ports:
            port_id = 'uni-{}'.format(uni.port_number)

            try:
                port = self.adapter_agent.get_logical_port(
                    logical_device_id, port_id)
                self.adapter_agent.delete_logical_port(logical_device_id, port)
            except KeyError:
                self.log.info('logical-port-not-found',
                              device_id=self.device_id,
                              portid=port_id)

        # Remove pon port from parent
        for port in self.pon_ports:
            self.adapter_agent.delete_port_reference_from_parent(
                self.device_id, port.get_port())

        # Just updating the port status may be an option as well
        # port.ofp_port.config = OFPPC_NO_RECV
        # yield self.adapter_agent.update_logical_port(logical_device_id,
        #                                             port)
        # Unregister for proxied message
        self.adapter_agent.unregister_for_proxied_messages(
            device.proxy_address)

        # TODO:
        # 1) Remove all flows from the device
        # 2) Remove the device from ponsim

        self.log.info('disabled', device_id=device.id)

    def reenable(self):
        self.log.info('re-enabling', device_id=self.device_id)
        try:
            # Get the latest device reference
            device = self.adapter_agent.get_device(self.device_id)
            self._cancel_deferred()

            # First we verify that we got parent reference and proxy info
            assert device.parent_id
            assert device.proxy_address.device_id
            # assert device.proxy_address.channel_id

            # Re-register for proxied messages right away
            self.proxy_address = device.proxy_address
            self.adapter_agent.register_for_proxied_messages(
                device.proxy_address)

            # Re-enable the ports on that device
            self.adapter_agent.enable_all_ports(self.device_id)

            # Refresh the port reference
            # self.uni_port = self._get_uni_port()   deprecated

            # Add the pon port reference to the parent
            for port in self.pon_ports:
                # TODO: Send 'enable' to PonPort?
                self.adapter_agent.add_port_reference_to_parent(
                    device.id, port.get_port())

            # Update the connect status to REACHABLE
            device.connect_status = ConnectStatus.REACHABLE
            self.adapter_agent.update_device(device)

            # re-add uni port to logical device
            parent_device = self.adapter_agent.get_device(device.parent_id)
            self.logical_device_id = parent_device.parent_id
            assert self.logical_device_id, 'Invalid logical device ID'

            if self.olt_created:
                # vlan non-zero if created via legacy method (not xPON)
                self.uni_port('deprecated').add_logical_port(
                    device.vlan, device.vlan, control_vlan=device.vlan)

            device = self.adapter_agent.get_device(device.id)
            device.oper_status = OperStatus.ACTIVE
            device.reason = ''

            self.enabled = True
            self.adapter_agent.update_device(device)

            self.log.info('re-enabled', device_id=device.id)
        except Exception, e:
            self.log.exception('error-reenabling', e=e)
Ejemplo n.º 17
0
class AdtranOnuHandler(AdtranXPON):
    def __init__(self, adapter, device_id):
        kwargs = dict()
        super(AdtranOnuHandler, self).__init__(**kwargs)
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.log = structlog.get_logger(device_id=device_id)
        self.logical_device_id = None
        self.proxy_address = None
        self._event_messages = None
        self._enabled = False
        self.pm_metrics = None
        self.alarms = None
        self._mgmt_gemport_aes = False
        self._upstream_channel_speed = 0

        self._openomci = OMCI(self, adapter.omci_agent)
        self._in_sync_subscription = None

        self._unis = dict()         # Port # -> UniPort
        self._pon = None
        self._heartbeat = HeartBeat.create(self, device_id)

        self._deferred = None
        self._event_deferred = None

        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 1)

        self._olt_created = False   # True if deprecated method of OLT creating DA is used

    def __str__(self):
        return "AdtranOnuHandler: {}".format(self.device_id)

    def _cancel_deferred(self):
        d1, self._deferred = self._deferred, None
        d2, self._event_deferred = self._event_deferred, None

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

    @property
    def enabled(self):
        return self._enabled

    @enabled.setter
    def enabled(self, value):
        assert isinstance(value, bool), 'enabled is a boolean'
        if self._enabled != value:
            self._enabled = value
            if self._enabled:
                self.start()
            else:
                self.stop()

    @property
    def mgmt_gemport_aes(self):
        return self._mgmt_gemport_aes

    @mgmt_gemport_aes.setter
    def mgmt_gemport_aes(self, value):
        if self._mgmt_gemport_aes != value:
            self._mgmt_gemport_aes = value
            # TODO: Anything else

    @property
    def upstream_channel_speed(self):
        return self._upstream_channel_speed

    @upstream_channel_speed.setter
    def upstream_channel_speed(self, value):
        if self._upstream_channel_speed != value:
            self._upstream_channel_speed = value
            # TODO: Anything else

    @property
    def olt_created(self):
        return self._olt_created    # ONU was created with deprecated 'child_device_detected' call

    @property
    def openomci(self):
        return self._openomci

    @property
    def heartbeat(self):
        return self._heartbeat

    @property
    def uni_ports(self):
        return self._unis.values()

    def uni_port(self, port_no_or_name):
        if isinstance(port_no_or_name, (str, unicode)):
            return next((uni for uni in self.uni_ports
                         if uni.name == port_no_or_name), None)

        assert isinstance(port_no_or_name, int), 'Invalid parameter type'
        return self._unis.get(port_no_or_name)

    @property
    def pon_port(self):
        return self._pon

    @property
    def pon_ports(self):
        return [self._pon]

    @property
    def _next_port_number(self):
        return self._port_number_pool.get_next()

    def _release_port_number(self, number):
        self._port_number_pool.release(number)

    def start(self):
        assert self._enabled, 'Start should only be called if enabled'

        self._cancel_deferred()

        # Handle received ONU event messages   TODO: Deprecate this....
        self._event_messages = DeferredQueue()
        self._event_deferred = reactor.callLater(0, self._handle_onu_events)

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

        # OpenOMCI Startup
        self._subscribe_to_events()
        self._openomci.enabled = True

        # Port startup
        if self._pon is not None:
            self._pon.enabled = True

        for port in self.uni_ports:
            port.enabled = True

        # Heartbeat
        self._heartbeat.enabled = True

    def stop(self):
        assert not self._enabled, 'Stop should only be called if disabled'
        #
        # TODO: Perform common shutdown tasks here
        #
        self._cancel_deferred()

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

        # Heartbeat
        self._heartbeat.stop()

        # OMCI Communications
        self._unsubscribe_to_events()
        self._openomci.enabled = False

        # Port shutdown
        for port in self.uni_ports:
            port.enabled = False

        if self._pon is not None:
            self._pon.enabled = False

        queue, self._event_deferred = self._event_deferred, None
        if queue is not None:
            while queue.pending:
                _ = yield queue.get()

    def receive_message(self, msg):
        if self.enabled:
            # TODO: Have OpenOMCI actually receive the messages
            self.openomci.receive_message(msg)

    def activate(self, device):
        self.log.info('activating')

        try:
            # first we verify that we got parent reference and proxy info
            assert device.parent_id, 'Invalid Parent ID'
            assert device.proxy_address.device_id, 'Invalid Device ID'

            if device.vlan:
                # vlan non-zero if created via legacy method (not xPON).
                self._olt_created = True

            # register for proxied messages right away
            self.proxy_address = device.proxy_address
            self.adapter_agent.register_for_proxied_messages(device.proxy_address)

            # initialize device info
            device.root = True
            device.vendor = 'Adtran Inc.'
            device.model = 'n/a'
            device.hardware_version = 'n/a'
            device.firmware_version = 'n/a'
            device.reason = ''
            device.connect_status = ConnectStatus.UNKNOWN

            # Register physical ports.  Should have at least one of each
            self._pon = PonPort.create(self, self._next_port_number)
            self.adapter_agent.add_port(device.id, self._pon.get_port())

            if self._olt_created:
                # vlan non-zero if created via legacy method (not xPON). Also
                # Set a random serial number since not xPON based

                uni_port = UniPort.create(self, self._next_port_number, device.vlan,
                                          'deprecated', device.vlan, None)
                self._unis[uni_port.port_number] = uni_port
                self.adapter_agent.add_port(device.id, uni_port.get_port())

                device.serial_number = uuid4().hex
                uni_port.add_logical_port(device.vlan, subscriber_vlan=device.vlan)

                # Start things up for this ONU Handler.
                self.enabled = True

            ############################################################################
            # Setup PM configuration for this device
            # Pass in ONU specific options
            kwargs = {
                'heartbeat': self.heartbeat,
                'omci-cc': self.openomci.omci_cc
            }
            self.pm_metrics = OnuPmMetrics(self.adapter_agent, self.device_id,
                                           grouped=True, freq_override=False,
                                           **kwargs)
            pm_config = self.pm_metrics.make_proto()
            self.openomci.set_pm_config(self.pm_metrics.omci_pm.openomci_interval_pm)
            self.log.info("initial-pm-config", pm_config=pm_config)
            self.adapter_agent.update_device_pm_config(pm_config, init=True)

            # reference of uni_port is required when re-enabling the device if
            # it was disabled previously
            # Need to query ONU for number of supported uni ports
            # For now, temporarily set number of ports to 1 - port #2
            parent_device = self.adapter_agent.get_device(device.parent_id)
            self.logical_device_id = parent_device.parent_id
            assert self.logical_device_id, 'Invalid logical device ID'
            self.adapter_agent.update_device(device)

            ############################################################################
            # Setup Alarm handler
            self.alarms = AdapterAlarms(self.adapter_agent, device.id, self.logical_device_id)

            ############################################################################
            # Start collecting stats from the device after a brief pause
            reactor.callLater(30, self.pm_metrics.start_collector)

        except Exception as e:
            self.log.exception('activate-failure', e=e)
            device.reason = 'Failed to activate: {}'.format(e.message)
            device.connect_status = ConnectStatus.UNREACHABLE
            device.oper_status = OperStatus.FAILED
            self.adapter_agent.update_device(device)

    def reconcile(self, device):
        self.log.info('reconciling-ONU-device-starts')

        # first we verify that we got parent reference and proxy info
        assert device.parent_id
        assert device.proxy_address.device_id
        # assert device.proxy_address.channel_id
        self._cancel_deferred()

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

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

        # Set the connection status to REACHABLE
        device.connect_status = ConnectStatus.REACHABLE
        self.adapter_agent.update_device(device)
        self.enabled = True

        # TODO: Verify that the uni, pon and logical ports exists

        # Mark the device as REACHABLE and ACTIVE
        device = self.adapter_agent.get_device(device.id)
        device.connect_status = ConnectStatus.REACHABLE
        device.oper_status = OperStatus.ACTIVE
        device.reason = ''
        self.adapter_agent.update_device(device)

        self.log.info('reconciling-ONU-device-ends')

    def update_pm_config(self, device, pm_config):
        # TODO: This has not been tested
        self.log.info('update_pm_config', pm_config=pm_config)
        self.pm_metrics.update(pm_config)

    @inlineCallbacks
    def update_flow_table(self, device, flows):
        #
        # We need to proxy through the OLT to get to the ONU
        # Configuration from here should be using OMCI
        #
        # self.log.info('bulk-flow-update', device_id=device.id, flows=flows)

        import voltha.core.flow_decomposer as fd
        from voltha.protos.openflow_13_pb2 import OFPXMC_OPENFLOW_BASIC

        def is_downstream(port):
            return port == 100  # Need a better way

        def is_upstream(port):
            return not is_downstream(port)

        omci = self.openomci.omci_cc

        for flow in flows:
            _type = None
            _port = None
            _vlan_vid = None
            _udp_dst = None
            _udp_src = None
            _ipv4_dst = None
            _ipv4_src = None
            _metadata = None
            _output = None
            _push_tpid = None
            _field = None
            _set_vlan_vid = None
            self.log.info('bulk-flow-update', device_id=device.id, flow=flow)
            try:
                _in_port = fd.get_in_port(flow)
                assert _in_port is not None

                if is_downstream(_in_port):
                    self.log.info('downstream-flow')
                elif is_upstream(_in_port):
                    self.log.info('upstream-flow')
                else:
                    raise Exception('port should be 1 or 2 by our convention')

                _out_port = fd.get_out_port(flow)  # may be None
                self.log.info('out-port', out_port=_out_port)

                for field in fd.get_ofb_fields(flow):
                    if field.type == fd.ETH_TYPE:
                        _type = field.eth_type
                        self.log.info('field-type-eth-type',
                                      eth_type=_type)

                    elif field.type == fd.IP_PROTO:
                        _proto = field.ip_proto
                        self.log.info('field-type-ip-proto',
                                      ip_proto=_proto)

                    elif field.type == fd.IN_PORT:
                        _port = field.port
                        self.log.info('field-type-in-port',
                                      in_port=_port)

                    elif field.type == fd.VLAN_VID:
                        _vlan_vid = field.vlan_vid & 0xfff
                        self.log.info('field-type-vlan-vid',
                                      vlan=_vlan_vid)

                    elif field.type == fd.VLAN_PCP:
                        _vlan_pcp = field.vlan_pcp
                        self.log.info('field-type-vlan-pcp',
                                      pcp=_vlan_pcp)

                    elif field.type == fd.UDP_DST:
                        _udp_dst = field.udp_dst
                        self.log.info('field-type-udp-dst',
                                      udp_dst=_udp_dst)

                    elif field.type == fd.UDP_SRC:
                        _udp_src = field.udp_src
                        self.log.info('field-type-udp-src',
                                      udp_src=_udp_src)

                    elif field.type == fd.IPV4_DST:
                        _ipv4_dst = field.ipv4_dst
                        self.log.info('field-type-ipv4-dst',
                                      ipv4_dst=_ipv4_dst)

                    elif field.type == fd.IPV4_SRC:
                        _ipv4_src = field.ipv4_src
                        self.log.info('field-type-ipv4-src',
                                      ipv4_dst=_ipv4_src)

                    elif field.type == fd.METADATA:
                        _metadata = field.table_metadata
                        self.log.info('field-type-metadata',
                                      metadata=_metadata)

                    else:
                        raise NotImplementedError('field.type={}'.format(
                            field.type))

                for action in fd.get_actions(flow):

                    if action.type == fd.OUTPUT:
                        _output = action.output.port
                        self.log.info('action-type-output',
                                      output=_output, in_port=_in_port)

                    elif action.type == fd.POP_VLAN:
                        self.log.info('action-type-pop-vlan',
                                      in_port=_in_port)

                    elif action.type == fd.PUSH_VLAN:
                        _push_tpid = action.push.ethertype
                        self.log.info('action-type-push-vlan',
                                 push_tpid=_push_tpid, in_port=_in_port)
                        if action.push.ethertype != 0x8100:
                            self.log.error('unhandled-tpid',
                                           ethertype=action.push.ethertype)

                    elif action.type == fd.SET_FIELD:
                        _field = action.set_field.field.ofb_field
                        assert (action.set_field.field.oxm_class ==
                                OFPXMC_OPENFLOW_BASIC)
                        self.log.info('action-type-set-field',
                                      field=_field, in_port=_in_port)
                        if _field.type == fd.VLAN_VID:
                            _set_vlan_vid = _field.vlan_vid & 0xfff
                            self.log.info('set-field-type-valn-vid', _set_vlan_vid)
                        else:
                            self.log.error('unsupported-action-set-field-type',
                                           field_type=_field.type)
                    else:
                        self.log.error('unsupported-action-type',
                                       action_type=action.type, in_port=_in_port)
                #
                # All flows created from ONU adapter should be OMCI based
                #
                if _vlan_vid == 0 and _set_vlan_vid != None and _set_vlan_vid != 0:
                    # allow priority tagged packets
                    # Set AR - ExtendedVlanTaggingOperationConfigData
                    #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid

                    results = yield omci.send_delete_vlan_tagging_filter_data(0x2102)

                    # self.send_set_vlan_tagging_filter_data(0x2102, _set_vlan_vid)
                    results = yield omci.send_create_vlan_tagging_filter_data(
                                        0x2102,
                                        _set_vlan_vid)

                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_untagged(
                                        0x202,
                                        0x1000,
                                        _set_vlan_vid)

                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(
                                        0x202,
                                        8,
                                        0,
                                        0,
                                        1,
                                        8,
                                        _set_vlan_vid)

                    # Set AR - ExtendedVlanTaggingOperationConfigData
                    #          514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid
                    '''
                    results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(0x205, 8, 0, 0,
                                                   
                    '''

            except Exception as e:
                self.log.exception('failed-to-install-flow', e=e, flow=flow)

    @inlineCallbacks
    def reboot(self):
        self.log.info('rebooting', device_id=self.device_id)
        self._cancel_deferred()

        reregister = True
        try:
            # Drop registration for adapter messages
            self.adapter_agent.unregister_for_inter_adapter_messages()

        except KeyError:
            reregister = False

        # Update the operational status to ACTIVATING and connect status to
        # UNREACHABLE
        device = self.adapter_agent.get_device(self.device_id)

        previous_oper_status = device.oper_status
        previous_conn_status = device.connect_status

        device.oper_status = OperStatus.ACTIVATING
        device.connect_status = ConnectStatus.UNREACHABLE
        device.reason = 'Attempting reboot'
        self.adapter_agent.update_device(device)

        # TODO: send alert and clear alert after the reboot
        try:
            ######################################################
            # MIB Reset
            yield self.openomci.onu_omci_device.reboot(timeout=1)

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

        # Reboot in progress. A reboot may take up to 3 min 30 seconds
        # Go ahead and pause less than that and start to look
        # for it being alive

        device.reason = 'reboot in progress'
        self.adapter_agent.update_device(device)

        # Disable OpenOMCI
        self.omci.enabled = False
        self._deferred = reactor.callLater(_ONU_REBOOT_MIN,
                                           self._finish_reboot,
                                           previous_oper_status,
                                           previous_conn_status,
                                           reregister)

    @inlineCallbacks
    def _finish_reboot(self, previous_oper_status, previous_conn_status,
                       reregister):

        # Restart OpenOMCI
        self.omci.enabled = True

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

        device.oper_status = previous_oper_status
        device.connect_status = previous_conn_status
        device.reason = ''
        self.adapter_agent.update_device(device)

        if reregister:
            self.adapter_agent.register_for_inter_adapter_messages()

        self.log.info('reboot-complete', device_id=self.device_id)

    def self_test_device(self, device):
        """
        This is called to Self a device based on a NBI call.
        :param device: A Voltha.Device object.
        :return: Will return result of self test
        """
        from voltha.protos.voltha_pb2 import SelfTestResponse
        self.log.info('self-test-device', device=device.id)
        # TODO: Support self test?
        return SelfTestResponse(result=SelfTestResponse.NOT_SUPPORTED)

    def disable(self):
        self.log.info('disabling', device_id=self.device_id)
        self.enabled = False

        # Get the latest device reference
        device = self.adapter_agent.get_device(self.device_id)

        # Disable all ports on that device
        self.adapter_agent.disable_all_ports(self.device_id)

        # Update the device operational status to UNKNOWN
        device.oper_status = OperStatus.UNKNOWN
        device.connect_status = ConnectStatus.UNREACHABLE
        device.reason = 'Disabled'
        self.adapter_agent.update_device(device)

        # Remove the uni logical port from the OLT, if still present
        parent_device = self.adapter_agent.get_device(device.parent_id)
        assert parent_device

        for uni in self.uni_ports:
            # port_id = 'uni-{}'.format(uni.port_number)
            port_id = uni.port_id_name()

            try:
                #TODO: there is no logical device if olt disables first
                logical_device_id = parent_device.parent_id
                assert logical_device_id
                port = self.adapter_agent.get_logical_port(logical_device_id,
                                                           port_id)
                self.adapter_agent.delete_logical_port(logical_device_id, port)
            except KeyError:
                self.log.info('logical-port-not-found', device_id=self.device_id,
                              portid=port_id)

        # Remove pon port from parent and disable
        if self._pon is not None:
            self.adapter_agent.delete_port_reference_from_parent(self.device_id,
                                                                 self._pon.get_port())
            self._pon.enabled = False

        # Send Uni Admin State Down

        # ethernet_uni_entity_id = 0x101
        # omci = self._handler.omci
        # attributes = dict(
        #     administrative_state=1  # - lock
        # )
        # frame = PptpEthernetUniFrame(
        #     ethernet_uni_entity_id,  # Entity ID
        #     attributes=attributes  # See above
        # ).set()
        # results = yield omci.send(frame)
        #
        # status = results.fields['omci_message'].fields['success_code']
        # failed_attributes_mask = results.fields['omci_message'].fields['failed_attributes_mask']
        # unsupported_attributes_mask = results.fields['omci_message'].fields['unsupported_attributes_mask']
        # self.log.debug('set-pptp-ethernet-uni', status=status,
        #                failed_attributes_mask=failed_attributes_mask,
        #                unsupported_attributes_mask=unsupported_attributes_mask)


        # Just updating the port status may be an option as well
        # port.ofp_port.config = OFPPC_NO_RECV
        # yield self.adapter_agent.update_logical_port(logical_device_id,
        #                                             port)
        # Unregister for proxied message
        self.adapter_agent.unregister_for_proxied_messages(
            device.proxy_address)

        # TODO:
        # 1) Remove all flows from the device
        # 2) Remove the device from ponsim

        self.log.info('disabled', device_id=device.id)

    def reenable(self):
        self.log.info('re-enabling', device_id=self.device_id)
        try:
            # Get the latest device reference
            device = self.adapter_agent.get_device(self.device_id)
            self._cancel_deferred()

            # First we verify that we got parent reference and proxy info
            assert device.parent_id
            assert device.proxy_address.device_id
            # assert device.proxy_address.channel_id

            # Re-register for proxied messages right away
            self.proxy_address = device.proxy_address
            self.adapter_agent.register_for_proxied_messages(
                device.proxy_address)

            # Re-enable the ports on that device
            self.adapter_agent.enable_all_ports(self.device_id)

            # Refresh the port reference
            # self.uni_port = self._get_uni_port()   deprecated

            # Add the pon port reference to the parent
            if self._pon is not None:
                self._pon.enabled = True
                self.adapter_agent.add_port_reference_to_parent(device.id,
                                                                self._pon.get_port())

            # Update the connect status to REACHABLE
            device.connect_status = ConnectStatus.REACHABLE
            self.adapter_agent.update_device(device)

            # re-add uni port to logical device
            parent_device = self.adapter_agent.get_device(device.parent_id)
            self.logical_device_id = parent_device.parent_id
            assert self.logical_device_id, 'Invalid logical device ID'

            if self.olt_created:
                # vlan non-zero if created via legacy method (not xPON)
                self.uni_port('deprecated').add_logical_port(device.vlan, device.vlan,
                                                             subscriber_vlan=device.vlan)
            else:
                # reestablish logical ports for each UNI
                for uni in self.uni_ports:
                    self.adapter_agent.add_port(device.id, uni.get_port())
                    uni.add_logical_port(uni.logical_port_number, subscriber_vlan=uni.subscriber_vlan)

            device = self.adapter_agent.get_device(device.id)
            device.oper_status = OperStatus.ACTIVE
            device.connect_status = ConnectStatus.REACHABLE
            device.reason = ''

            self.enabled = True
            self.adapter_agent.update_device(device)

            self.log.info('re-enabled', device_id=device.id)
            self._pon._dev_info_loaded = False
            self._bridge_initialized = False
        except Exception, e:
            self.log.exception('error-reenabling', e=e)
Ejemplo n.º 18
0
class PerformanceIntervals(object):
    """
    OpenOMCI ONU Performance Monitoring Intervals State machine

    This state machine focuses on L2 Internet Data Service and Classical
    PM (for the v2.0 release).
    """
    DEFAULT_STATES = [
        'disabled', 'starting', 'synchronize_time', 'idle', 'create_pm_me',
        'collect_data', 'threshold_exceeded'
    ]

    DEFAULT_TRANSITIONS = [
        {
            'trigger': 'start',
            'source': 'disabled',
            'dest': 'starting'
        },
        {
            'trigger': 'tick',
            'source': 'starting',
            'dest': 'synchronize_time'
        },
        {
            'trigger': 'success',
            'source': 'synchronize_time',
            'dest': 'idle'
        },
        {
            'trigger': 'failure',
            'source': 'synchronize_time',
            'dest': 'synchronize_time'
        },
        {
            'trigger': 'tick',
            'source': 'idle',
            'dest': 'collect_data'
        },
        {
            'trigger': 'add_me',
            'source': 'idle',
            'dest': 'create_pm_me'
        },
        {
            'trigger': 'delete_me',
            'source': 'idle',
            'dest': 'delete_pm_me'
        },
        {
            'trigger': 'success',
            'source': 'create_pm_me',
            'dest': 'idle'
        },
        {
            'trigger': 'failure',
            'source': 'create_pm_me',
            'dest': 'idle'
        },
        {
            'trigger': 'success',
            'source': 'delete_pm_me',
            'dest': 'idle'
        },
        {
            'trigger': 'failure',
            'source': 'delete_pm_me',
            'dest': 'idle'
        },
        {
            'trigger': 'success',
            'source': 'collect_data',
            'dest': 'idle'
        },
        {
            'trigger': 'failure',
            'source': 'collect_data',
            'dest': 'idle'
        },

        # TODO: Add rebooted event transitions to disabled or synchronize_time
        # TODO: Need to capture Threshold Crossing Alarms appropriately

        # Do wildcard 'stop' trigger last so it covers all previous states
        {
            'trigger': 'stop',
            'source': '*',
            'dest': 'disabled'
        },
        {
            'trigger': 'reboot',
            'source': '*',
            'dest': 'rebooted'
        },
    ]
    DEFAULT_RETRY = 10  # Seconds to delay after task failure/timeout/poll
    DEFAULT_TICK_DELAY = 15  # Seconds between checks for collection tick
    DEFAULT_INTERVAL_SKEW = 10 * 60  # Seconds to skew past interval boundary
    DEFAULT_COLLECT_ATTEMPTS = 3  # Maximum number of collection fetch attempts
    DEFAULT_CREATE_ATTEMPTS = 15  # Maximum number of attempts to create a PM Managed Entities

    def __init__(self,
                 agent,
                 device_id,
                 tasks,
                 advertise_events=False,
                 states=DEFAULT_STATES,
                 transitions=DEFAULT_TRANSITIONS,
                 initial_state='disabled',
                 timeout_delay=DEFAULT_RETRY,
                 tick_delay=DEFAULT_TICK_DELAY,
                 interval_skew=DEFAULT_INTERVAL_SKEW,
                 collect_attempts=DEFAULT_COLLECT_ATTEMPTS,
                 create_attempts=DEFAULT_CREATE_ATTEMPTS):
        """
        Class initialization

        :param agent: (OpenOmciAgent) Agent
        :param device_id: (str) ONU Device ID
        :param tasks: (dict) Tasks to run
        :param advertise_events: (bool) Advertise events on OpenOMCI Event Bus
        :param states: (list) List of valid states
        :param transitions: (dict) Dictionary of triggers and state changes
        :param initial_state: (str) Initial state machine state
        :param timeout_delay: (int/float) Number of seconds after a timeout to pause
        :param tick_delay: (int/float) Collection poll check delay while idle
        :param interval_skew: (int/float) Seconds to randomly skew the next interval
                              collection to spread out requests for PM intervals
        :param collect_attempts: (int) Max requests for a single PM interval before fail
        :param create_attempts: (int) Max attempts to create PM Managed entities before stopping state machine
        """
        self.log = structlog.get_logger(device_id=device_id)

        self._agent = agent
        self._device_id = device_id
        self._device = None
        self._pm_config = None
        self._timeout_delay = timeout_delay
        self._tick_delay = tick_delay
        self._interval_skew = interval_skew
        self._collect_attempts = collect_attempts
        self._create_attempts = create_attempts

        self._sync_time_task = tasks['sync-time']
        self._get_interval_task = tasks['collect-data']
        self._create_pm_task = tasks['create-pm']
        self._delete_pm_task = tasks['delete-pm']
        self._advertise_events = advertise_events

        self._omci_cc_subscriptions = {  # RxEvent.enum -> Subscription Object
            RxEvent.MIB_Reset: None,
            RxEvent.Create: None,
            RxEvent.Delete: None
        }
        self._omci_cc_sub_mapping = {
            RxEvent.MIB_Reset: self.on_mib_reset_response,
            RxEvent.Create: self.on_create_response,
            RxEvent.Delete: self.on_delete_response,
        }
        self._me_watch_list = {
            MacBridgePortConfigurationData.class_id: {
                'create-delete': self.add_remove_enet_frame_pm,
                'instances':
                dict()  # BP entity_id -> (PM class_id, PM entity_id)
            }
        }
        self._deferred = None
        self._task_deferred = None
        self._current_task = None
        self._add_me_deferred = None
        self._delete_me_deferred = None
        self._next_interval = None
        self._enet_entity_id = IndexPool(1024, 1)
        self._add_pm_me_retry = 0

        # (Class ID, Instance ID) -> Collect attempts remaining
        self._pm_me_collect_retries = dict()
        self._pm_me_extended_info = dict()
        self._add_pm_me = dict(
        )  # (pm cid, pm eid) -> (me cid, me eid, upstream)
        self._del_pm_me = set()

        # Pollable PM items
        # Note that some items the KPI extracts are not listed below. These are the
        # administrative states, operational states, and sensed ethernet type. The values
        # in the MIB database should be accurate for these items.

        self._ani_g_items = ["optical_signal_level", "transmit_optical_level"]
        self._next_poll_time = datetime.utcnow()
        self._poll_interval = 60  # TODO: Fixed at once a minute

        # Statistics and attributes
        # TODO: add any others if it will support problem diagnosis

        # Set up state machine to manage states
        self.machine = Machine(model=self,
                               states=states,
                               transitions=transitions,
                               initial=initial_state,
                               queued=True,
                               ignore_invalid_triggers=True,
                               name='{}-{}'.format(self.__class__.__name__,
                                                   device_id))
        try:
            import logging
            logging.getLogger('transitions').setLevel(logging.WARNING)
        except Exception as e:
            self.log.exception('log-level-failed', e=e)

    def _cancel_deferred(self):
        d1, self._deferred = self._deferred, None
        d2, self._task_deferred = self._task_deferred, None
        d3, self._add_me_deferred = self._add_me_deferred, None
        d4, self._delete_me_deferred = self._delete_me_deferred, None

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

    def _cancel_tasks(self):
        task, self._current_task = self._current_task, None
        if task is not None:
            task.stop()

    def __str__(self):
        return 'PerformanceIntervals: Device ID: {}, State:{}'.format(
            self._device_id, self.state)

    def delete(self):
        """
        Cleanup any state information
        """
        self.stop()

    @property
    def device_id(self):
        return self._device_id

    @property
    def advertise_events(self):
        return self._advertise_events

    @advertise_events.setter
    def advertise_events(self, value):
        if not isinstance(value, bool):
            raise TypeError('Advertise event is a boolean')
        self._advertise_events = value

    def advertise(self, event, info):
        """Advertise an event on the OpenOMCI event bus"""
        if self._advertise_events:
            self._agent.advertise(
                event, {
                    'state-machine': self.machine.name,
                    'info': info,
                    'time': str(datetime.utcnow()),
                    'next': str(self._next_interval)
                })

    def set_pm_config(self, pm_config):
        """
        Set PM interval configuration

        :param pm_config: (OnuPmIntervalMetrics) PM Interval configuration
        :return:
        """
        self._pm_config = pm_config

    def _me_is_supported(self, class_id):
        """
        Check to see if ONU supports this ME
        :param class_id: (int) ME Class ID
        :return: (bool) If ME is supported
        """
        #
        supported = self._device.omci_capabilities.supported_managed_entities
        return class_id in supported if supported is not None else False

    def add_pm_me(self,
                  pm_class_id,
                  pm_entity_id,
                  cid=0,
                  eid=0,
                  upstream=False):
        """
        Add a new Performance Monitoring ME.

        The ME ID will be added to an internal list and will be added the next
        time the idle state is reached. An 'add_pm_me' trigger will be raised in
        case already in the Idle state.

        :param pm_class_id: (int) ME Class ID (1..0xFFFE)
        :param pm_entity_id: (int) Instance ID (1..0xFFFE)
        :param cid: (int) Class ID of entity monitored, may be None
        :param eid: (int) Instance ID of entity monitored, may be None
        :param upstream: (bool): Flag indicating if PM is for upstream traffic
        """
        if not isinstance(pm_class_id, int):
            raise TypeError('PM ME Instance ID is an integer')
        if not 0 < pm_class_id < 0xFFFF:
            raise ValueError('PM ME Instance ID must be 1..65534')

        # Check to see if ONU supports this ME
        if not self._me_is_supported(pm_class_id):
            self.log.warn('unsupported-PM-me', class_id=pm_class_id)
            return

        key = (pm_class_id, pm_entity_id)
        entry = (cid, eid, upstream)

        if key not in self._pm_me_collect_retries and key not in self._add_pm_me:
            self._add_pm_me[key] = entry

            if self._add_me_deferred is None:
                self._add_me_deferred = reactor.callLater(0, self.add_me)

        if (pm_class_id, pm_entity_id) in self._del_pm_me:
            self._del_pm_me.remove((pm_class_id, pm_entity_id))

    def delete_pm_me(self, class_id, entity_id):
        """
        Remove a new Performance Monitoring ME.

        The ME ID will be added to an internal list and will be removed the next
        time the idle state is reached. An 'delete_pm_me' trigger will be raised in
        case already in the Idle state.

        :param class_id: (int) ME Class ID (1..0xFFFE)
        :param entity_id: (int) Instance ID (1..0xFFFE)
        """
        if not isinstance(class_id, int):
            raise TypeError('PM ME Class ID is an integer')
        if not 0 < class_id < 0xFFFF:
            raise ValueError('PM ME Class ID must be 1..65534')

        # Check to see if ONU supports this ME
        if not self._me_is_supported(class_id):
            self.log.warn('unsupported-PM-me', class_id=class_id)
            return

        key = (class_id, entity_id)

        if key in self._pm_me_collect_retries and key not in self._del_pm_me:
            self._del_pm_me.add(key)

            if self._delete_me_deferred is None:
                self._delete_me_deferred = reactor.callLater(0, self.delete_me)

        if key in self._add_pm_me:
            self._add_pm_me.pop(key)

    def on_enter_disabled(self):
        """
        State machine is being stopped
        """
        self.advertise(OpenOmciEventType.state_change, self.state)
        self._cancel_deferred()
        self._cancel_tasks()
        self._next_interval = None

        # Drop OMCI ME Response subscriptions
        for event, sub in self._omci_cc_subscriptions.iteritems():
            if sub is not None:
                self._omci_cc_subscriptions[event] = None
                self._device.omci_cc.event_bus.unsubscribe(sub)

        # Manually remove ani ANI/PON and UNI PM interval MEs
        config = self._device.configuration
        anis = config.ani_g_entities
        unis = config.uni_g_entities

        if anis is not None:
            for entity_id in anis.iterkeys():
                self.delete_pm_me(FecPerformanceMonitoringHistoryData.class_id,
                                  entity_id)
                self.delete_pm_me(
                    XgPonTcPerformanceMonitoringHistoryData.class_id,
                    entity_id)
                self.delete_pm_me(
                    XgPonDownstreamPerformanceMonitoringHistoryData.class_id,
                    entity_id)
                self.delete_pm_me(
                    XgPonUpstreamPerformanceMonitoringHistoryData.class_id,
                    entity_id)

        if unis is not None:
            for entity_id in config.uni_g_entities.iterkeys():
                self.delete_pm_me(EthernetPMMonitoringHistoryData.class_id,
                                  entity_id)

    def on_enter_starting(self):
        """ Add the PON/ANI and UNI PM intervals"""
        self.advertise(OpenOmciEventType.state_change, self.state)

        self._device = self._agent.get_device(self._device_id)
        self._cancel_deferred()

        # Set up OMCI ME Response subscriptions
        try:
            for event, sub in self._omci_cc_sub_mapping.iteritems():
                if self._omci_cc_subscriptions[event] is None:
                    self._omci_cc_subscriptions[event] = \
                        self._device.omci_cc.event_bus.subscribe(
                            topic=OMCI_CC.event_bus_topic(self._device_id, event),
                            callback=sub)

        except Exception as e:
            self.log.exception('omci-cc-subscription-setup', e=e)

        try:
            # Manually start some ANI/PON and UNI PM interval MEs
            config = self._device.configuration
            anis = config.ani_g_entities
            unis = config.uni_g_entities

            if anis is not None:
                for entity_id in anis.iterkeys():
                    self.add_pm_me(
                        FecPerformanceMonitoringHistoryData.class_id,
                        entity_id)
                    self.add_pm_me(
                        XgPonTcPerformanceMonitoringHistoryData.class_id,
                        entity_id)
                    self.add_pm_me(
                        XgPonDownstreamPerformanceMonitoringHistoryData.
                        class_id, entity_id)
                    self.add_pm_me(
                        XgPonUpstreamPerformanceMonitoringHistoryData.class_id,
                        entity_id)

            if unis is not None:
                for entity_id in config.uni_g_entities.iterkeys():
                    self.add_pm_me(EthernetPMMonitoringHistoryData.class_id,
                                   entity_id)

            # Look for existing instances of dynamically created ME's that have PM
            # associated with them and add them now
            for class_id in self._me_watch_list.iterkeys():
                instances = {
                    k: v
                    for k, v in self._device.query_mib(
                        class_id=class_id).items() if isinstance(k, int)
                }

                for entity_id, data in instances.items():
                    method = self._me_watch_list[class_id]['create-delete']
                    cid, eid = method(None,
                                      class_id,
                                      entity_id,
                                      add=True,
                                      attributes=data[ATTRIBUTES_KEY])
                    if cid > 0:
                        # BP entity_id -> (PM class_id, PM entity_id)
                        instances = self._me_watch_list[class_id]['instances']
                        instances[entity_id] = (cid, eid)

        except Exception as e:
            self.log.exception('pm-me-setup', class_id=class_id, e=e)

        # Got to synchronize_time state
        self._deferred = reactor.callLater(0, self.tick)

    def on_enter_synchronize_time(self):
        """
        State machine has just transitioned to the synchronize_time state
        """
        self.advertise(OpenOmciEventType.state_change, self.state)
        self._cancel_deferred()

        def success(_results):
            self.log.debug('sync-time-success')
            self._current_task = None
            self._deferred = reactor.callLater(0, self.success)
            # Calculate next interval time
            self._next_interval = self.get_next_interval

        def failure(reason):
            self.log.info('sync-time-failure', reason=reason)
            self._current_task = None
            self._deferred = reactor.callLater(self._timeout_delay,
                                               self.failure)

        # Schedule a task to set the ONU time
        self._current_task = self._sync_time_task(self._agent, self._device_id)
        self._task_deferred = self._device.task_runner.queue_task(
            self._current_task)
        self._task_deferred.addCallbacks(success, failure)

    def on_enter_idle(self):
        """
        State machine has just transitioned to the idle state

        In this state, any added PM MEs that need to be created will be.
        TODO: some non-interval PM stats (if there are any) are collected here
        """
        self.advertise(OpenOmciEventType.state_change, self.state)
        self._cancel_deferred()

        if len(self._del_pm_me) and self._delete_me_deferred is None:
            self._delete_me_deferred = reactor.callLater(0, self.delete_me)

        elif len(self._add_pm_me) and self._add_me_deferred is None:
            self._add_me_deferred = reactor.callLater(0, self.add_me)

        elif datetime.utcnow() >= self._next_poll_time:

            def success(results):
                self._device.timestamp = arrow.utcnow().float_timestamp
                self._device.mib_synchronizer.mib_set(
                    results.me_class.class_id, results.entity_id,
                    results.attributes)
                self._next_poll_time = datetime.utcnow() + timedelta(
                    seconds=self._poll_interval)

            def failure(reason):
                self.log.info('poll-failure', reason=reason)
                self._device.timestamp = None
                return None

            # Scan all ANI-G ports
            ani_g_entities = self._device.configuration.ani_g_entities
            ani_g_entities_ids = ani_g_entities.keys(
            ) if ani_g_entities is not None else None

            if ani_g_entities_ids is not None and len(ani_g_entities_ids):
                for entity_id in ani_g_entities_ids:
                    task = OmciGetRequest(self._agent,
                                          self.device_id,
                                          AniG,
                                          entity_id,
                                          self._ani_g_items,
                                          allow_failure=True)
                    self._task_deferred = self._device.task_runner.queue_task(
                        task)
                    self._task_deferred.addCallbacks(success, failure)
            else:
                self.log.warn('poll-pm-no-anis')
                self._next_poll_time = datetime.utcnow() + timedelta(
                    seconds=self._poll_interval)

        # TODO: Compute a better mechanism than just polling here, perhaps based on
        #       the next time to fetch data for 'any' interval
        self._deferred = reactor.callLater(self._tick_delay, self.tick)

    def on_enter_create_pm_me(self):
        """
        State machine has just transitioned to the create_pm_me state
        """
        self.advertise(OpenOmciEventType.state_change, self.state)
        self._cancel_deferred()
        self._cancel_tasks()
        mes, self._add_pm_me = self._add_pm_me, dict()

        def success(results):
            self.log.debug('create-me-success', results=results)

            # Check if already here. The create request could have received
            # an already-exists status code which we consider successful
            for pm, me in mes.items():
                self._pm_me_collect_retries[pm] = self.pm_collected(pm)
                self._pm_me_extended_info[pm] = me

            self._current_task = None
            self._deferred = reactor.callLater(0, self.success)

        def failure(reason):
            self.log.info('create-me-failure',
                          reason=reason,
                          retries=self._add_pm_me_retry)
            self._current_task = None
            if self._add_pm_me_retry <= self._create_attempts:
                for pm, me in mes.items():
                    self._add_pm_me[pm] = me
                self._add_pm_me_retry += 1
                self._deferred = reactor.callLater(self._timeout_delay,
                                                   self.failure)
            else:
                # we cant seem to create any collection me, no point in doing anything
                self.log.warn('unable-to-create-pm-me-disabling-collection',
                              reason=reason,
                              device_id=self._device_id)
                self._deferred = reactor.callLater(self._timeout_delay,
                                                   self.stop)

        self._current_task = self._create_pm_task(self._agent, self._device_id,
                                                  mes)
        self._task_deferred = self._device.task_runner.queue_task(
            self._current_task)
        self._task_deferred.addCallbacks(success, failure)

    def on_enter_delete_pm_me(self):
        """
        State machine has just transitioned to the delete_pm_me state
        """
        self.advertise(OpenOmciEventType.state_change, self.state)
        self._cancel_deferred()
        self._cancel_tasks()

        mes, self._del_pm_me = self._del_pm_me, set()

        def success(results):
            self.log.debug('delete-me-success', results=results)
            self._current_task = None
            for me in mes:
                self._pm_me_collect_retries.pop(me)

            self._deferred = reactor.callLater(0, self.success)

        def failure(reason):
            self.log.info('delete-me-failure', reason=reason)
            self._current_task = None
            for me in mes:
                self._del_pm_me.add(me)

            self._deferred = reactor.callLater(self._timeout_delay,
                                               self.failure)

        self._current_task = self._delete_pm_task(self._agent, self._device_id,
                                                  mes)
        self._task_deferred = self._device.task_runner.queue_task(
            self._current_task)
        self._task_deferred.addCallbacks(success, failure)

    def on_enter_collect_data(self):
        """
        State machine has just transitioned to the collect_data state
        """

        if self._next_interval is not None and self._next_interval > datetime.utcnow(
        ):
            self.log.debug('wait-next-interval')
            # Not ready for next interval, transition back to idle and we should get
            # called again after a short delay
            reactor.callLater(0, self.success)
            return

        self.advertise(OpenOmciEventType.state_change, self.state)
        self._cancel_deferred()
        self._cancel_tasks()
        keys = self._pm_me_collect_retries.keys()
        shuffle(keys)

        for key in keys:
            class_id = key[0]
            entity_id = key[1]

            self.log.debug("in-enter-collect-data",
                           data_key=key,
                           retries=self._pm_me_collect_retries[key])

            # Collect the data ?
            if self._pm_me_collect_retries[key] > 0:

                def success(results):
                    self.log.debug('collect-success',
                                   results=results,
                                   class_id=results.get('class_id'),
                                   entity_id=results.get('entity_id'))
                    self._current_task = None
                    self._pm_me_collect_retries[key] = 0
                    self._deferred = reactor.callLater(0, self.success)
                    return results

                def failure(reason):
                    self.log.info('collect-failure', reason=reason)
                    self._current_task = None
                    self._pm_me_collect_retries[key] -= 1
                    self._deferred = reactor.callLater(self._timeout_delay,
                                                       self.failure)
                    return reason  # Halt callback processing

                # start the task
                if key in self._pm_me_extended_info:
                    self.log.debug(
                        'collect-extended-info-found',
                        data_key=key,
                        extended_info=self._pm_me_extended_info[key])
                    parent_class_id = self._pm_me_extended_info[key][0]
                    parent_entity_id = self._pm_me_extended_info[key][1]
                    upstream = self._pm_me_extended_info[key][2]
                else:
                    self.log.debug('collect-extended-info-not-found',
                                   data_key=key)
                    parent_class_id = None
                    parent_entity_id = None
                    upstream = None

                self._current_task = self._get_interval_task(
                    self._agent,
                    self._device_id,
                    class_id,
                    entity_id,
                    parent_class_id=parent_class_id,
                    parent_entity_id=parent_entity_id,
                    upstream=upstream)
                self._task_deferred = self._device.task_runner.queue_task(
                    self._current_task)
                self._task_deferred.addCallbacks(success, failure)
                self._task_deferred.addCallback(self.publish_data)
                return

        # Here if all intervals have been collected (we are up to date)
        self._next_interval = self.get_next_interval
        self.log.debug('collect-calculate-next', next=self._next_interval)

        self._pm_me_collect_retries = dict.fromkeys(
            self._pm_me_collect_retries, self._collect_attempts)
        reactor.callLater(0, self.success)

    def on_enter_threshold_exceeded(self):
        """
        State machine has just transitioned to the threshold_exceeded state
        """
        pass  # TODO: Not sure if we want this state. Need to get alarm synchronizer working first

    @property
    def get_next_interval(self):
        """
        Determine the time for the next interval collection for all of this
        ONUs PM Intervals. Earliest fetch time is at least 1 minute into the
        next interval.

        :return: (datetime) UTC time to get the next interval
        """
        now = datetime.utcnow()

        # Get delta seconds to at least 1 minute into next interval
        next_delta_secs = (16 - (now.minute % 15)) * 60
        next_interval = now + timedelta(seconds=next_delta_secs)

        # NOTE: For debugging, uncomment next section to perform collection
        #       right after initial code startup/mib-sync
        if self._next_interval is None:
            return now  # Do it now  (just for debugging purposes)

        # Skew the next time up to the maximum specified
        # TODO: May want to skew in a shorter range and select the minute
        #       based off some device property value to make collection a
        #       little more predictable on a per-ONU basis.
        return next_interval + timedelta(
            seconds=uniform(0, self._interval_skew))

    def pm_collected(self, key):
        """
        Query database and determine if PM data needs to be collected for this ME
        """
        class_id = key[0]
        entity_id = key[1]

        return self._collect_attempts  # TODO: Implement persistent storage

    def publish_data(self, results):
        """
        Publish the PM interval results on the appropriate bus.  The results are
        a dictionary with the following format.

            'class-id':          (int) ME Class ID,
            'entity-id':         (int) ME Entity ID,
            'me-name':           (str) ME Class name,   # Mostly for debugging...
            'interval-end-time': None,
            'interval-utc-time': (DateTime) UTC time when retrieved from ONU,

            Counters added here as they are retrieved with the format of
            'counter-attribute-name': value (int)

        :param results: (dict) PM results
        """
        self.log.debug('collect-publish', results=results)

        if self._pm_config is not None:
            self._pm_config.publish_metrics(results)

        pass  # TODO: Save off last time interval fetched to persistent storage?

    def on_mib_reset_response(self, _topic, msg):
        """
        Called upon receipt of a MIB Reset Response for this ONU

        :param _topic: (str) OMCI-RX topic
        :param msg: (dict) Dictionary with 'rx-response' and 'tx-request' (if any)
        """
        self.log.debug('on-mib-reset-response', state=self.state)
        try:
            response = msg[RX_RESPONSE_KEY]
            omci_msg = response.fields['omci_message'].fields
            status = omci_msg['success_code']

            if status == RC.Success:
                for class_id in self._me_watch_list.iterkeys():
                    # BP entity_id -> (PM class_id, PM entity_id)
                    instances = self._me_watch_list[class_id]['instances']
                    for _, me_pair in instances.items():
                        self._me_watch_list[class_id]['create-delete'](
                            None, me_pair[0], me_pair[1], add=False)
                    self._me_watch_list[class_id]['instances'] = dict()

        except KeyError:
            pass  # NOP

    def on_create_response(self, _topic, msg):
        """
        Called upon receipt of a Create Response for this ONU.

        :param _topic: (str) OMCI-RX topic
        :param msg: (dict) Dictionary with 'rx-response' and 'tx-request' (if any)
        """
        self.log.debug('on-create-response', state=self.state)

        def valid_request(stat, c_id, e_id):
            return self._omci_cc_subscriptions[RxEvent.Delete] is not None\
                and stat in (RC.Success, RC.InstanceExists) \
                and c_id in self._me_watch_list.keys() \
                and e_id not in self._me_watch_list[c_id]['instances']

        response = msg[RX_RESPONSE_KEY]
        omci = response.fields['omci_message'].fields
        class_id = omci['entity_class']
        entity_id = omci['entity_id']
        status = omci['success_code']

        if valid_request(status, class_id, entity_id):
            request = msg[TX_REQUEST_KEY]
            method = self._me_watch_list[class_id]['create-delete']
            cid, eid = method(request, class_id, entity_id, add=True)

            if cid > 0:
                # BP entity_id -> (PM class_id, PM entity_id)
                instances = self._me_watch_list[class_id]['instances']
                instances[entity_id] = (cid, eid)

    def on_delete_response(self, _topic, msg):
        """
        Called upon receipt of a Delete Response for this ONU

        :param _topic: (str) OMCI-RX topic
        :param msg: (dict) Dictionary with 'rx-response' and 'tx-request' (if any)
        """
        self.log.debug('on-delete-response', state=self.state)

        def valid_request(stat, cid, eid):
            return self._omci_cc_subscriptions[RxEvent.Delete] is not None\
                and stat in (RC.Success, RC.UnknownInstance) \
                and cid in self._me_watch_list.keys() \
                and eid in self._me_watch_list[cid]['instances']

        response = msg[RX_RESPONSE_KEY]
        omci = response.fields['omci_message'].fields
        class_id = omci['entity_class']
        entity_id = omci['entity_id']
        status = omci['success_code']

        if valid_request(status, class_id, entity_id):
            request = msg[TX_REQUEST_KEY]
            method = self._me_watch_list[class_id]['create-delete']

            method(request, class_id, entity_id, add=False)
            # BP entity_id -> (PM class_id, PM entity_id)
            instances = self._me_watch_list[class_id]['instances']
            del instances[entity_id]

    def get_pm_entity_id_for_add(self, pm_cid, eid):
        """
        Select the Entity ID to use for a specific PM Class ID.  For extended
        PM ME's, an entity id (>0) is allocated

        :param pm_cid: (int) PM ME Class ID to create/get entry ID for
        :param eid: (int) Reference class's entity ID. Used as PM entity ID for non-
                    extended PM history PMs
        :return: (int) Entity ID to use
        """
        if pm_cid in (
                EthernetFrameExtendedPerformanceMonitoring.class_id,
                EthernetFrameExtendedPerformanceMonitoring64Bit.class_id):
            return self._enet_entity_id.get_next()
        return eid

    def release_pm_entity_id(self, pm_cid, eid):
        if pm_cid in (
                EthernetFrameExtendedPerformanceMonitoring.class_id,
                EthernetFrameExtendedPerformanceMonitoring64Bit.class_id):
            try:
                self._enet_entity_id.release(eid)
            except:
                pass

    def add_remove_enet_frame_pm(self,
                                 request,
                                 class_id,
                                 entity_id,
                                 add=True,
                                 attributes=None):
        """
        Add/remove PM for the dynamic MAC Port configuration data.

        This can be called in a variety of ways:

           o If from an Response event from OMCI_CC, the request will contain
             the original create/delete request. The class_id and entity_id will
             be the MAC Data Configuration Data class and instance ID.
             add = True if create, False if delete

           o If starting up (and the associated ME is already created), the MAC
             Data Configuration Data class and instance ID, and attributes are
             provided. request = None and add = True

           o If cleaning up (stopping), the PM ME class_id, entity_id are provided.
             request = None and add = False

        :return: (int, int) PM ME class_id and entity_id for add/remove was performed.
                            class and entity IDs are non-zero on success
        """
        pm_entity_id = 0
        cid = 0
        eid = 0
        upstream = False

        def tp_type_to_pm(tp):
            # TODO: Support 64-bit extended Monitoring MEs.
            # This will result in the need to maintain entity IDs of PMs differently
            upstream_types = [  # EthernetFrameExtendedPerformanceMonitoring64Bit.class_id,
                EthernetFrameExtendedPerformanceMonitoring.class_id,
                EthernetFrameUpstreamPerformanceMonitoringHistoryData.class_id
            ], True
            downstream_types = [  # EthernetFrameExtendedPerformanceMonitoring64Bit.class_id,
                EthernetFrameExtendedPerformanceMonitoring.class_id,
                EthernetFrameDownstreamPerformanceMonitoringHistoryData.
                class_id
            ], False
            return {
                1: downstream_types,
                3: upstream_types,
                5: downstream_types,
                6: downstream_types,
            }.get(tp, None)

        if request is not None:
            assert class_id == MacBridgePortConfigurationData.class_id

            # Is this associated with the ANI or the UNI side of the bridge?
            # For VOLTHA v2.0, only high-speed internet data service is
            attributes = request.fields['omci_message'].fields['data']
            pm_class_ids, upstream = tp_type_to_pm(attributes['tp_type'])
            cid = request.fields['omci_message'].fields['entity_class']
            eid = request.fields['omci_message'].fields['entity_id']
            if not add:
                instances = self._me_watch_list[cid]['instances']
                _, pm_entity_id = instances.get(eid, (None, None))

        elif add:
            assert class_id == MacBridgePortConfigurationData.class_id
            assert isinstance(attributes, dict)

            # Is this associated with the ANI or the UNI side of the bridge?
            pm_class_ids, upstream = tp_type_to_pm(attributes.get('tp_type'))
            cid = class_id
            eid = entity_id

        else:
            assert class_id in (
                EthernetFrameUpstreamPerformanceMonitoringHistoryData.class_id,
                EthernetFrameDownstreamPerformanceMonitoringHistoryData.
                class_id, EthernetFrameExtendedPerformanceMonitoring.class_id,
                EthernetFrameExtendedPerformanceMonitoring64Bit.class_id)
            pm_class_ids = [class_id]

        if pm_class_ids is None:
            return False  # Unable to select a supported ME for this ONU

        if add:
            for pm_class_id in pm_class_ids:
                if self._me_is_supported(pm_class_id):
                    pm_entity_id = self.get_pm_entity_id_for_add(
                        pm_class_id, eid)
                    self.add_pm_me(pm_class_id,
                                   pm_entity_id,
                                   cid=cid,
                                   eid=eid,
                                   upstream=upstream)
                    return pm_class_id, pm_entity_id
        else:
            for pm_class_id in pm_class_ids:
                if self._me_is_supported(pm_class_id):
                    self.delete_pm_me(pm_class_id, pm_entity_id)
                    self.release_pm_entity_id(pm_class_id, pm_entity_id)
                    return pm_class_id, pm_entity_id

        return 0, 0
Ejemplo n.º 19
0
class BrcmOpenomciOnuHandler(object):

    def __init__(self, adapter, device_id):
        self.log = structlog.get_logger(device_id=device_id)
        self.log.debug('function-entry')
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.device_id = device_id
        self.incoming_messages = DeferredQueue()
        self.event_messages = DeferredQueue()
        self.proxy_address = None
        self.tx_id = 0
        self._enabled = False
        self._omcc_version = OMCCVersion.Unknown
        self._total_tcont_count = 0  # From ANI-G ME
        self._qos_flexibility = 0  # From ONT2_G ME

        self._onu_indication = None
        self._unis = dict()  # Port # -> UniPort
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 0)

        self._pon = None
        #TODO: probably shouldnt be hardcoded, determine from olt maybe?
        self._pon_port_number = 100
        self.logical_device_id = None

        # Set up OpenOMCI environment
        self._onu_omci_device = None
        self._dev_info_loaded = False
        self._deferred = None

        self._in_sync_subscription = None
        self._connectivity_subscription = None
        self._capabilities_subscription = None

    @property
    def enabled(self):
        self.log.debug('function-entry')
        return self._enabled

    @enabled.setter
    def enabled(self, value):
        self.log.debug('function-entry')
        if self._enabled != value:
            self._enabled = value

    @property
    def omci_agent(self):
        self.log.debug('function-entry')
        return self.adapter.omci_agent

    @property
    def omci_cc(self):
        self.log.debug('function-entry')
        return self._onu_omci_device.omci_cc if self._onu_omci_device is not None else None

    @property
    def uni_ports(self):
        self.log.debug('function-entry')
        return self._unis.values()

    def uni_port(self, port_no_or_name):
        self.log.debug('function-entry')
        if isinstance(port_no_or_name, (str, unicode)):
            return next((uni for uni in self.uni_ports
                         if uni.name == port_no_or_name), None)

        assert isinstance(port_no_or_name, int), 'Invalid parameter type'
        return self._unis.get(port_no_or_name)

    @property
    def pon_port(self):
        self.log.debug('function-entry')
        return self._pon

    @property
    def _next_port_number(self):
        self.log.debug('function-entry')
        return self._port_number_pool.get_next()

    def _release_port_number(self, number):
        self.log.debug('function-entry', number=number)
        self._port_number_pool.release(number)

    def receive_message(self, msg):
        self.log.debug('function-entry', msg=hexify(msg))
        if self.omci_cc is not None:
            self.omci_cc.receive_message(msg)

    def activate(self, device):
        self.log.debug('function-entry', device=device)

        # first we verify that we got parent reference and proxy info
        assert device.parent_id
        assert device.proxy_address.device_id

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

        if self.enabled is not True:
            self.log.info('activating-new-onu')
            # populate what we know.  rest comes later after mib sync
            device.root = True
            device.vendor = 'Broadcom'
            device.connect_status = ConnectStatus.REACHABLE
            device.oper_status = OperStatus.DISCOVERED
            self.adapter_agent.update_device(device)

            self._pon = PonPort.create(self, self._pon_port_number)
            self.adapter_agent.add_port(device.id, self._pon.get_port())

            self.log.debug('added-pon-port-to-agent', pon=self._pon)

            parent_device = self.adapter_agent.get_device(device.parent_id)
            self.logical_device_id = parent_device.parent_id

            self.adapter_agent.update_device(device)

            self.log.debug('set-device-discovered')

            # Create and start the OpenOMCI ONU Device Entry for this ONU
            self._onu_omci_device = self.omci_agent.add_device(self.device_id,
                                                               self.adapter_agent,
                                                               support_classes=self.adapter.broadcom_omci)
            # Port startup
            if self._pon is not None:
                self._pon.enabled = True

            self.enabled = True
        else:
            self.log.info('onu-already-activated')


    def reconcile(self, device):
        self.log.debug('function-entry', device=device)

        # first we verify that we got parent reference and proxy info
        assert device.parent_id
        assert device.proxy_address.device_id

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

        # TODO: Query ONU current status after reconcile and update.
        #       To be addressed in future commits.

        self.log.info('reconciling-broadcom-onu-device-ends')

    # TODO: move to UniPort
    def update_logical_port(self, logical_device_id, port_id, state):
        try:
            self.log.info('updating-logical-port', logical_port_id=port_id,
                          logical_device_id=logical_device_id, state=state)
            logical_port = self.adapter_agent.get_logical_port(logical_device_id,
                                                               port_id)
            logical_port.ofp_port.state = state
            self.adapter_agent.update_logical_port(logical_device_id,
                                                   logical_port)
        except Exception as e:
            self.log.exception("exception-updating-port",e=e)

    @inlineCallbacks
    def delete(self, device):
        self.log.info('delete-onu', device=device)

        parent_device = self.adapter_agent.get_device(device.parent_id)
        if parent_device.type == 'openolt':
            parent_adapter = registry('adapter_loader').get_agent(parent_device.adapter).adapter
            self.log.debug('parent-adapter-delete-onu', onu_device=device,
                          parent_device=parent_device,
                          parent_adapter=parent_adapter)
            try:
                parent_adapter.delete_child_device(parent_device.id, device)
            except AttributeError:
                self.log.debug('parent-device-delete-child-not-implemented')

    @inlineCallbacks
    def update_flow_table(self, device, flows):
        self.log.debug('function-entry', device=device, flows=flows)
        #
        # We need to proxy through the OLT to get to the ONU
        # Configuration from here should be using OMCI
        #
        #self.log.info('bulk-flow-update', device_id=device.id, flows=flows)

        def is_downstream(port):
            return port == self._pon_port_number

        def is_upstream(port):
            return not is_downstream(port)

        for flow in flows:
            _type = None
            _port = None
            _vlan_vid = None
            _udp_dst = None
            _udp_src = None
            _ipv4_dst = None
            _ipv4_src = None
            _metadata = None
            _output = None
            _push_tpid = None
            _field = None
            _set_vlan_vid = None
            self.log.debug('bulk-flow-update', device_id=device.id, flow=flow)
            try:
                _in_port = fd.get_in_port(flow)
                assert _in_port is not None

                if is_downstream(_in_port):
                    self.log.debug('downstream-flow')
                elif is_upstream(_in_port):
                    self.log.debug('upstream-flow')
                else:
                    raise Exception('port should be 1 or 2 by our convention')

                _out_port = fd.get_out_port(flow)  # may be None
                self.log.debug('out-port', out_port=_out_port)

                for field in fd.get_ofb_fields(flow):
                    if field.type == fd.ETH_TYPE:
                        _type = field.eth_type
                        self.log.debug('field-type-eth-type',
                                      eth_type=_type)

                    elif field.type == fd.IP_PROTO:
                        _proto = field.ip_proto
                        self.log.debug('field-type-ip-proto',
                                      ip_proto=_proto)

                    elif field.type == fd.IN_PORT:
                        _port = field.port
                        self.log.debug('field-type-in-port',
                                      in_port=_port)

                    elif field.type == fd.VLAN_VID:
                        _vlan_vid = field.vlan_vid & 0xfff
                        self.log.debug('field-type-vlan-vid',
                                      vlan=_vlan_vid)

                    elif field.type == fd.VLAN_PCP:
                        _vlan_pcp = field.vlan_pcp
                        self.log.debug('field-type-vlan-pcp',
                                      pcp=_vlan_pcp)

                    elif field.type == fd.UDP_DST:
                        _udp_dst = field.udp_dst
                        self.log.debug('field-type-udp-dst',
                                      udp_dst=_udp_dst)

                    elif field.type == fd.UDP_SRC:
                        _udp_src = field.udp_src
                        self.log.debug('field-type-udp-src',
                                      udp_src=_udp_src)

                    elif field.type == fd.IPV4_DST:
                        _ipv4_dst = field.ipv4_dst
                        self.log.debug('field-type-ipv4-dst',
                                      ipv4_dst=_ipv4_dst)

                    elif field.type == fd.IPV4_SRC:
                        _ipv4_src = field.ipv4_src
                        self.log.debug('field-type-ipv4-src',
                                      ipv4_dst=_ipv4_src)

                    elif field.type == fd.METADATA:
                        _metadata = field.table_metadata
                        self.log.debug('field-type-metadata',
                                      metadata=_metadata)

                    else:
                        raise NotImplementedError('field.type={}'.format(
                            field.type))

                for action in fd.get_actions(flow):

                    if action.type == fd.OUTPUT:
                        _output = action.output.port
                        self.log.debug('action-type-output',
                                      output=_output, in_port=_in_port)

                    elif action.type == fd.POP_VLAN:
                        self.log.debug('action-type-pop-vlan',
                                      in_port=_in_port)

                    elif action.type == fd.PUSH_VLAN:
                        _push_tpid = action.push.ethertype
                        self.log.debug('action-type-push-vlan',
                                 push_tpid=_push_tpid, in_port=_in_port)
                        if action.push.ethertype != 0x8100:
                            self.log.error('unhandled-tpid',
                                           ethertype=action.push.ethertype)

                    elif action.type == fd.SET_FIELD:
                        _field = action.set_field.field.ofb_field
                        assert (action.set_field.field.oxm_class ==
                                OFPXMC_OPENFLOW_BASIC)
                        self.log.debug('action-type-set-field',
                                      field=_field, in_port=_in_port)
                        if _field.type == fd.VLAN_VID:
                            _set_vlan_vid = _field.vlan_vid & 0xfff
                            self.log.debug('set-field-type-valn-vid', _set_vlan_vid)
                        else:
                            self.log.error('unsupported-action-set-field-type',
                                           field_type=_field.type)
                    else:
                        self.log.error('unsupported-action-type',
                                  action_type=action.type, in_port=_in_port)

                #
                # All flows created from ONU adapter should be OMCI based
                #
                if _vlan_vid == 0 and _set_vlan_vid != None and _set_vlan_vid != 0:

                    # TODO: find a better place for all of this
                    # TODO: make this a member of the onu gem port or the uni port
                    _mac_bridge_service_profile_entity_id = 0x201
                    _mac_bridge_port_ani_entity_id = 0x2102   # TODO: can we just use the entity id from the anis list?

                    # Delete bridge ani side vlan filter
                    msg = VlanTaggingFilterDataFrame(_mac_bridge_port_ani_entity_id)
                    frame = msg.delete()
                    self.log.debug('openomci-msg', msg=msg)
                    results = yield self.omci_cc.send(frame)
                    self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')

                    # Re-Create bridge ani side vlan filter
                    msg = VlanTaggingFilterDataFrame(
                        _mac_bridge_port_ani_entity_id,  # Entity ID
                        vlan_tcis=[_set_vlan_vid],        # VLAN IDs
                        forward_operation=0x10
                    )
                    frame = msg.create()
                    self.log.debug('openomci-msg', msg=msg)
                    results = yield self.omci_cc.send(frame)
                    self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')

                    # Update uni side extended vlan filter
                    # filter for untagged
                    # probably for eapol
                    # TODO: magic 0x1000 / 4096?
                    # TODO: lots of magic
                    attributes = dict(
                        received_frame_vlan_tagging_operation_table=
                        VlanTaggingOperation(
                            filter_outer_priority=15,
                            filter_outer_vid=4096,
                            filter_outer_tpid_de=0,

                            filter_inner_priority=15,
                            filter_inner_vid=4096,
                            filter_inner_tpid_de=0,
                            filter_ether_type=0,

                            treatment_tags_to_remove=0,
                            treatment_outer_priority=15,
                            treatment_outer_vid=0,
                            treatment_outer_tpid_de=0,

                            treatment_inner_priority=0,
                            treatment_inner_vid=_set_vlan_vid,
                            treatment_inner_tpid_de=4
                        )
                    )
                    msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
                        _mac_bridge_service_profile_entity_id,  # Bridge Entity ID
                        attributes=attributes  # See above
                    )
                    frame = msg.set()
                    self.log.debug('openomci-msg', msg=msg)
                    results = yield self.omci_cc.send(frame)
                    self.check_status_and_state(results,
                                                'flow-set-ext-vlan-tagging-op-config-data-untagged')

                    # Update uni side extended vlan filter
                    # filter for vlan 0
                    # TODO: lots of magic
                    attributes = dict(
                        received_frame_vlan_tagging_operation_table=
                        VlanTaggingOperation(
                            filter_outer_priority=15,  # This entry is not a double-tag rule
                            filter_outer_vid=4096,  # Do not filter on the outer VID value
                            filter_outer_tpid_de=0,  # Do not filter on the outer TPID field

                            filter_inner_priority=8,  # Filter on inner vlan
                            filter_inner_vid=0x0,  # Look for vlan 0
                            filter_inner_tpid_de=0,  # Do not filter on inner TPID field
                            filter_ether_type=0,  # Do not filter on EtherType

                            treatment_tags_to_remove=1,
                            treatment_outer_priority=15,
                            treatment_outer_vid=0,
                            treatment_outer_tpid_de=0,

                            treatment_inner_priority=8,  # Add an inner tag and insert this value as the priority
                            treatment_inner_vid=_set_vlan_vid,  # use this value as the VID in the inner VLAN tag
                            treatment_inner_tpid_de=4,  # set TPID
                        )
                    )
                    msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
                        _mac_bridge_service_profile_entity_id,  # Bridge Entity ID
                        attributes=attributes  # See above
                    )
                    frame = msg.set()
                    self.log.debug('openomci-msg', msg=msg)
                    results = yield self.omci_cc.send(frame)
                    self.check_status_and_state(results,
                                                'flow-set-ext-vlan-tagging-op-config-data-zero-tagged')

            except Exception as e:
                self.log.exception('failed-to-install-flow', e=e, flow=flow)

    def get_tx_id(self):
        self.log.debug('function-entry')
        self.tx_id += 1
        return self.tx_id

    def create_interface(self, data):
        self.log.debug('function-entry', data=data)
        self._onu_indication = data

        self.log.debug('starting-openomci-statemachine')
        self._subscribe_to_events()
        reactor.callLater(1, self._onu_omci_device.start)

    def update_interface(self, data):
        self.log.debug('function-entry', data=data)

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

        if data.oper_state == 'down':
            self.log.debug('stopping-openomci-statemachine')
            reactor.callLater(0, self._onu_omci_device.stop)
            self.disable_ports(onu_device)
            onu_device.connect_status = ConnectStatus.UNREACHABLE
            onu_device.oper_status = OperStatus.DISCOVERED
            self.adapter_agent.update_device(onu_device)
        else:
            self.log.debug('not-changing-openomci-statemachine')

    def remove_interface(self, data):
        self.log.debug('function-entry', data=data)

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

        self.log.debug('stopping-openomci-statemachine')
        reactor.callLater(0, self._onu_omci_device.stop)
        self.disable_ports(onu_device)

        # TODO: im sure there is more to do here


    def create_gemport(self, data):
        self.log.debug('create-gemport', data=data)
        gem_portdata = GemportsConfigData()
        gem_portdata.CopyFrom(data)

        # TODO: fill in what i have.  This needs to be provided from the OLT
        # currently its hardcoded/static
        gemdict = dict()
        gemdict['gemport-id'] = gem_portdata.gemport_id
        gemdict['encryption'] = gem_portdata.aes_indicator
        gemdict['tcont-ref'] = int(gem_portdata.tcont_ref)
        gemdict['name'] = gem_portdata.gemport_id
        gemdict['traffic-class'] = gem_portdata.traffic_class
        gemdict['traffic-class'] = gem_portdata.traffic_class

        gem_port = OnuGemPort.create(self, gem_port=gemdict, entity_id=self._pon.next_gem_entity_id)

        self._pon.add_gem_port(gem_port)

        self.log.debug('pon-add-gemport', gem_port=gem_port)


    @inlineCallbacks
    def remove_gemport(self, data):
        self.log.debug('remove-gemport', data=data)
        gem_port = GemportsConfigData()
        gem_port.CopyFrom(data)
        device = self.adapter_agent.get_device(self.device_id)
        if device.connect_status != ConnectStatus.REACHABLE:
            self.log.error('device-unreachable')
            returnValue(None)

        #TODO: Create a remove task that encompasses this



    def create_tcont(self, tcont_data, traffic_descriptor_data):
        self.log.debug('create-tcont', tcont_data=tcont_data, traffic_descriptor_data=traffic_descriptor_data)
        tcontdata = TcontsConfigData()
        tcontdata.CopyFrom(tcont_data)

        # TODO: fill in what i have.  This needs to be provided from the OLT
        # currently its hardcoded/static
        tcontdict = dict()
        tcontdict['alloc-id'] = tcontdata.alloc_id
        tcontdict['name'] = tcontdata.name
        tcontdict['vont-ani'] = tcontdata.interface_reference

        # TODO: Not sure what to do with any of this...
        tddata = dict()
        tddata['name'] = 'not-sure-td-profile'
        tddata['fixed-bandwidth'] = "not-sure-fixed"
        tddata['assured-bandwidth'] = "not-sure-assured"
        tddata['maximum-bandwidth'] = "not-sure-max"
        tddata['additional-bw-eligibility-indicator'] = "not-sure-additional"

        td = OnuTrafficDescriptor.create(tddata)
        tcont = OnuTCont.create(self, tcont=tcontdict, td=td)

        self._pon.add_tcont(tcont)

        self.log.debug('pon-add-tcont', tcont=tcont)

        if tcontdata.interface_reference is not None:
            self.log.debug('tcont', tcont=tcont.alloc_id)
        else:
            self.log.info('received-null-tcont-data', tcont=tcont.alloc_id)

    @inlineCallbacks
    def remove_tcont(self, tcont_data, traffic_descriptor_data):
        self.log.debug('remove-tcont', tcont_data=tcont_data, traffic_descriptor_data=traffic_descriptor_data)
        device = self.adapter_agent.get_device(self.device_id)
        if device.connect_status != ConnectStatus.REACHABLE:
            self.log.error('device-unreachable')
            returnValue(None)

        # TODO: Create some omci task that encompases this what intended


    def create_multicast_gemport(self, data):
        self.log.debug('function-entry', data=data)

        # TODO: create objects and populate for later omci calls


    @inlineCallbacks
    def disable(self, device):
        self.log.debug('function-entry', device=device)
        try:
            self.log.info('sending-uni-lock-towards-device', device=device)

            def stop_anyway(reason):
                # proceed with disable regardless if we could reach the onu. for example onu is unplugged
                self.log.debug('stopping-openomci-statemachine')
                reactor.callLater(0, self._onu_omci_device.stop)
                self.disable_ports(device)
                device.oper_status = OperStatus.UNKNOWN
                device.connect_status = ConnectStatus.UNREACHABLE
                self.adapter_agent.update_device(device)

            # lock all the unis
            task = BrcmUniLockTask(self.omci_agent, self.device_id, lock=True)
            self._deferred = self._onu_omci_device.task_runner.queue_task(task)
            self._deferred.addCallbacks(stop_anyway, stop_anyway)
            '''
            # Disable in parent device (OLT)
            parent_device = self.adapter_agent.get_device(device.parent_id)

            if parent_device.type == 'openolt':
                parent_adapter = registry('adapter_loader').get_agent(parent_device.adapter).adapter
                self.log.info('parent-adapter-disable-onu', onu_device=device,
                              parent_device=parent_device,
                              parent_adapter=parent_adapter)
                try:
                    parent_adapter.disable_child_device(parent_device.id, device)
                except AttributeError:
                    self.log.debug('parent-device-disable-child-not-implemented')
            '''
        except Exception as e:
            log.exception('exception-in-onu-disable', exception=e)

    @inlineCallbacks
    def reenable(self, device):
        self.log.debug('function-entry', device=device)
        try:
            # Start up OpenOMCI state machines for this device
            # this will ultimately resync mib and unlock unis on successful redownloading the mib
            self.log.debug('restarting-openomci-statemachine')
            self._subscribe_to_events()
            reactor.callLater(1, self._onu_omci_device.start)
        except Exception as e:
            log.exception('exception-in-onu-reenable', exception=e)

    @inlineCallbacks
    def reboot(self):
        self.log.info('reboot-device')
        device = self.adapter_agent.get_device(self.device_id)
        if device.connect_status != ConnectStatus.REACHABLE:
            self.log.error("device-unreacable")
            returnValue(None)

        def success(_results):
            self.log.info('reboot-success', _results=_results)
            self.disable_ports(device)
            device.connect_status = ConnectStatus.UNREACHABLE
            device.oper_status = OperStatus.DISCOVERED
            self.adapter_agent.update_device(device)

        def failure(_reason):
            self.log.info('reboot-failure', _reason=_reason)

        self._deferred = self._onu_omci_device.reboot()
        self._deferred.addCallbacks(success, failure)

    def disable_ports(self, onu_device):
        self.log.info('disable-ports', device_id=self.device_id,
                   onu_device=onu_device)

        # Disable all ports on that device
        self.adapter_agent.disable_all_ports(self.device_id)

        parent_device = self.adapter_agent.get_device(onu_device.parent_id)
        assert parent_device
        logical_device_id = parent_device.parent_id
        assert logical_device_id
        ports = self.adapter_agent.get_ports(onu_device.id, Port.ETHERNET_UNI)
        for port in ports:
            port_id = 'uni-{}'.format(port.port_no)
            # TODO: move to UniPort
            self.update_logical_port(logical_device_id, port_id, OFPPS_LINK_DOWN)

    def enable_ports(self, onu_device):
        self.log.info('enable-ports', device_id=self.device_id, onu_device=onu_device)

        # Disable all ports on that device
        self.adapter_agent.enable_all_ports(self.device_id)

        parent_device = self.adapter_agent.get_device(onu_device.parent_id)
        assert parent_device
        logical_device_id = parent_device.parent_id
        assert logical_device_id
        ports = self.adapter_agent.get_ports(onu_device.id, Port.ETHERNET_UNI)
        for port in ports:
            port_id = 'uni-{}'.format(port.port_no)
            # TODO: move to UniPort
            self.update_logical_port(logical_device_id, port_id, OFPPS_LIVE)


    def _subscribe_to_events(self):
        self.log.debug('function-entry')

        # OMCI MIB Database sync status
        bus = self._onu_omci_device.event_bus
        topic = OnuDeviceEntry.event_bus_topic(self.device_id,
                                               OnuDeviceEvents.MibDatabaseSyncEvent)
        self._in_sync_subscription = bus.subscribe(topic, self.in_sync_handler)

        # OMCI Capabilities
        bus = self._onu_omci_device.event_bus
        topic = OnuDeviceEntry.event_bus_topic(self.device_id,
                                               OnuDeviceEvents.OmciCapabilitiesEvent)
        self._capabilities_subscription = bus.subscribe(topic, self.capabilties_handler)

    def _unsubscribe_to_events(self):
        self.log.debug('function-entry')
        if self._in_sync_subscription is not None:
            bus = self._onu_omci_device.event_bus
            bus.unsubscribe(self._in_sync_subscription)
            self._in_sync_subscription = None

    def in_sync_handler(self, _topic, msg):
        self.log.debug('function-entry', _topic=_topic, msg=msg)
        if self._in_sync_subscription is not None:
            try:
                in_sync = msg[IN_SYNC_KEY]

                if in_sync:
                    # Only call this once
                    bus = self._onu_omci_device.event_bus
                    bus.unsubscribe(self._in_sync_subscription)
                    self._in_sync_subscription = None

                    # Start up device_info load
                    self.log.debug('running-mib-sync')
                    reactor.callLater(0, self._mib_in_sync)

            except Exception as e:
                self.log.exception('in-sync', e=e)

    def capabilties_handler(self, _topic, _msg):
        self.log.debug('function-entry', _topic=_topic, msg=_msg)
        if self._capabilities_subscription is not None:
            self.log.debug('capabilities-handler-done')

    def _mib_in_sync(self):
        self.log.debug('function-entry')

        omci = self._onu_omci_device
        in_sync = omci.mib_db_in_sync

        device = self.adapter_agent.get_device(self.device_id)
        device.reason = 'discovery-mibsync-complete'
        self.adapter_agent.update_device(device)

        if not self._dev_info_loaded:
            self.log.info('loading-device-data-from-mib', in_sync=in_sync, already_loaded=self._dev_info_loaded)

            omci_dev = self._onu_omci_device
            config = omci_dev.configuration

            # TODO: run this sooner somehow...
            # In Sync, we can register logical ports now. Ideally this could occur on
            # the first time we received a successful (no timeout) OMCI Rx response.
            try:
                parent_device = self.adapter_agent.get_device(device.parent_id)

                parent_adapter_agent = registry('adapter_loader').get_agent(parent_device.adapter)
                if parent_adapter_agent is None:
                    self.log.error('openolt_adapter_agent-could-not-be-retrieved')

                ani_g = config.ani_g_entities
                uni_g = config.uni_g_entities
                pptp = config.pptp_entities

                for key, value in ani_g.iteritems():
                    self.log.debug("discovered-ani", key=key, value=value)

                for key, value in uni_g.iteritems():
                    self.log.debug("discovered-uni", key=key, value=value)

                for key, value in pptp.iteritems():
                    self.log.debug("discovered-pptp-uni", key=key, value=value)

                    entity_id = key

                    # TODO: This knowledge is locked away in openolt.  and it assumes one onu equals one uni...
                    uni_no_start = platform.mk_uni_port_num(self._onu_indication.intf_id,
                                                            self._onu_indication.onu_id)

                    working_port = self._next_port_number
                    uni_no = uni_no_start + working_port
                    uni_name = "uni-{}".format(uni_no)

                    mac_bridge_port_num = working_port + 1

                    self.log.debug('live-port-number-ready', uni_no=uni_no, uni_name=uni_name)

                    uni_port = UniPort.create(self, uni_name, uni_no, uni_name, device.vlan, device.vlan)
                    uni_port.entity_id = entity_id
                    uni_port.enabled = True
                    uni_port.mac_bridge_port_num = mac_bridge_port_num
                    uni_port.add_logical_port(uni_port.port_number, subscriber_vlan=device.vlan)

                    self.log.debug("created-uni-port", uni=uni_port)

                    self.adapter_agent.add_port(device.id, uni_port.get_port())
                    parent_adapter_agent.add_port(device.parent_id, uni_port.get_port())

                    self._unis[uni_port.port_number] = uni_port

                    # TODO: this should be in the PonPortclass
                    pon_port = self._pon.get_port()
                    self.adapter_agent.delete_port_reference_from_parent(self.device_id,
                                                                         pon_port)

                    pon_port.peers.extend([Port.PeerPort(device_id=device.parent_id,
                                                        port_no=uni_port.port_number)])

                    self._pon._port = pon_port

                    self.adapter_agent.add_port_reference_to_parent(self.device_id,
                                                                    pon_port)

                    # TODO: only one uni/pptp for now. flow bug in openolt
                    break

                self._total_tcont_count = ani_g.get('total-tcont-count')
                self._qos_flexibility = config.qos_configuration_flexibility or 0
                self._omcc_version = config.omcc_version or OMCCVersion.Unknown
                self.log.debug("set-total-tcont-count", tcont_count=self._total_tcont_count)

                # Save our device information
                self._dev_info_loaded = True
                self.adapter_agent.update_device(device)

            except Exception as e:
                self.log.exception('device-info-load', e=e)
                self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT, self._mib_in_sync)

        else:
            self.log.info('device-info-already-loaded', in_sync=in_sync, already_loaded=self._dev_info_loaded)

        def success(_results):
            self.log.info('mib-download-success', _results=_results)
            device = self.adapter_agent.get_device(self.device_id)
            device.reason = 'initial-mib-downloaded'
            device.oper_status = OperStatus.ACTIVE
            device.connect_status = ConnectStatus.REACHABLE
            self.enable_ports(device)
            self.adapter_agent.update_device(device)
            self._mib_download_task = None

        def failure(_reason):
            self.log.info('mib-download-failure', _reason=_reason)
            # TODO: test this.  also verify i can add this task this way
            self._mib_download_task = BrcmMibDownloadTask(self.omci_agent, self)
            self._deferred = self._onu_omci_device.task_runner.queue_task(self._mib_download_task)

        self.log.info('downloading-initial-mib-configuration')
        self._mib_download_task = BrcmMibDownloadTask(self.omci_agent, self)
        self._deferred = self._onu_omci_device.task_runner.queue_task(self._mib_download_task)
        self._deferred.addCallbacks(success, failure)



    def check_status_and_state(self, results, operation=''):
        self.log.debug('function-entry')
        omci_msg = results.fields['omci_message'].fields
        status = omci_msg['success_code']
        error_mask = omci_msg.get('parameter_error_attributes_mask', 'n/a')
        failed_mask = omci_msg.get('failed_attributes_mask', 'n/a')
        unsupported_mask = omci_msg.get('unsupported_attributes_mask', 'n/a')

        self.log.debug("OMCI Result:", operation, omci_msg=omci_msg, status=status, error_mask=error_mask,
                       failed_mask=failed_mask, unsupported_mask=unsupported_mask)

        if status == RC.Success:
            return True

        elif status == RC.InstanceExists:
            return False
Ejemplo n.º 20
0
class BrcmOpenomciOnuHandler(object):
    def __init__(self, adapter, device_id):
        self.log = structlog.get_logger(device_id=device_id)
        self.log.debug('function-entry')
        self.adapter = adapter
        self.adapter_agent = adapter.adapter_agent
        self.parent_adapter = None
        self.parent_id = None
        self.device_id = device_id
        self.incoming_messages = DeferredQueue()
        self.event_messages = DeferredQueue()
        self.proxy_address = None
        self.tx_id = 0
        self._enabled = False
        self.alarms = None
        self.pm_metrics = None
        self._omcc_version = OMCCVersion.Unknown
        self._total_tcont_count = 0  # From ANI-G ME
        self._qos_flexibility = 0  # From ONT2_G ME

        self._onu_indication = None
        self._unis = dict()  # Port # -> UniPort
        self._port_number_pool = IndexPool(_MAXIMUM_PORT, 0)

        self._pon = None
        #TODO: probably shouldnt be hardcoded, determine from olt maybe?
        self._pon_port_number = 100
        self.logical_device_id = None

        self._heartbeat = HeartBeat.create(self, device_id)

        # Set up OpenOMCI environment
        self._onu_omci_device = None
        self._dev_info_loaded = False
        self._deferred = None

        self._in_sync_subscription = None
        self._connectivity_subscription = None
        self._capabilities_subscription = None

    @property
    def enabled(self):
        return self._enabled

    @enabled.setter
    def enabled(self, value):
        if self._enabled != value:
            self._enabled = value

    @property
    def omci_agent(self):
        return self.adapter.omci_agent

    @property
    def omci_cc(self):
        return self._onu_omci_device.omci_cc if self._onu_omci_device is not None else None

    @property
    def heartbeat(self):
        return self._heartbeat

    @property
    def uni_ports(self):
        return self._unis.values()

    def uni_port(self, port_no_or_name):
        if isinstance(port_no_or_name, (str, unicode)):
            return next(
                (uni for uni in self.uni_ports if uni.name == port_no_or_name),
                None)

        assert isinstance(port_no_or_name, int), 'Invalid parameter type'
        return self._unis.get(port_no_or_name)

    @property
    def pon_port(self):
        return self._pon

    @property
    def _next_port_number(self):
        return self._port_number_pool.get_next()

    def _release_port_number(self, number):
        self._port_number_pool.release(number)

    def receive_message(self, msg):
        if self.omci_cc is not None:
            self.omci_cc.receive_message(msg)

    # Called once when the adapter creates the device/onu instance
    def activate(self, device):
        self.log.debug('function-entry', device=device)

        # first we verify that we got parent reference and proxy info
        assert device.parent_id
        assert device.proxy_address.device_id

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)
        self.parent_id = device.parent_id
        parent_device = self.adapter_agent.get_device(self.parent_id)
        if parent_device.type == 'openolt':
            self.parent_adapter = registry('adapter_loader').\
                                  get_agent(parent_device.adapter).adapter

        if self.enabled is not True:
            self.log.info('activating-new-onu')
            # populate what we know.  rest comes later after mib sync
            device.root = True
            device.vendor = 'Broadcom'
            device.connect_status = ConnectStatus.REACHABLE
            device.oper_status = OperStatus.DISCOVERED
            device.reason = 'activating-onu'

            # pm_metrics requires a logical device id
            parent_device = self.adapter_agent.get_device(device.parent_id)
            self.logical_device_id = parent_device.parent_id
            assert self.logical_device_id, 'Invalid logical device ID'

            self.adapter_agent.update_device(device)

            self.log.debug('set-device-discovered')

            self._init_pon_state(device)

            ############################################################################
            # Setup PM configuration for this device
            # Pass in ONU specific options
            kwargs = {
                OnuPmMetrics.DEFAULT_FREQUENCY_KEY:
                OnuPmMetrics.DEFAULT_ONU_COLLECTION_FREQUENCY,
                'heartbeat': self.heartbeat,
                OnuOmciPmMetrics.OMCI_DEV_KEY: self._onu_omci_device
            }
            self.pm_metrics = OnuPmMetrics(self.adapter_agent,
                                           self.device_id,
                                           self.logical_device_id,
                                           grouped=True,
                                           freq_override=False,
                                           **kwargs)
            pm_config = self.pm_metrics.make_proto()
            self._onu_omci_device.set_pm_config(
                self.pm_metrics.omci_pm.openomci_interval_pm)
            self.log.info("initial-pm-config", pm_config=pm_config)
            self.adapter_agent.update_device_pm_config(pm_config, init=True)

            ############################################################################
            # Setup Alarm handler
            self.alarms = AdapterAlarms(self.adapter_agent, device.id,
                                        self.logical_device_id)
            # Note, ONU ID and UNI intf set in add_uni_port method
            self._onu_omci_device.alarm_synchronizer.set_alarm_params(
                mgr=self.alarms, ani_ports=[self._pon])
            self.enabled = True
        else:
            self.log.info('onu-already-activated')

    # Called once when the adapter needs to re-create device.  usually on vcore restart
    def reconcile(self, device):
        self.log.debug('function-entry', device=device)

        # first we verify that we got parent reference and proxy info
        assert device.parent_id
        assert device.proxy_address.device_id

        # register for proxied messages right away
        self.proxy_address = device.proxy_address
        self.adapter_agent.register_for_proxied_messages(device.proxy_address)

        if self.enabled is not True:
            self.log.info('reconciling-broadcom-onu-device')

            self._init_pon_state(device)

            # need to restart state machines on vcore restart.  there is no indication to do it for us.
            self._onu_omci_device.start()
            device.reason = "restarting-openomci"
            self.adapter_agent.update_device(device)

            # TODO: this is probably a bit heavy handed
            # Force a reboot for now.  We need indications to reflow to reassign tconts and gems given vcore went away
            # This may not be necessary when mib resync actually works
            reactor.callLater(1, self.reboot)

            self.enabled = True
        else:
            self.log.info('onu-already-activated')

    def _init_pon_state(self, device):
        self.log.debug('function-entry', device=device)

        self._pon = PonPort.create(self, self._pon_port_number)
        self.adapter_agent.add_port(device.id, self._pon.get_port())

        self.log.debug('added-pon-port-to-agent', pon=self._pon)

        parent_device = self.adapter_agent.get_device(device.parent_id)
        self.logical_device_id = parent_device.parent_id

        self.adapter_agent.update_device(device)

        # Create and start the OpenOMCI ONU Device Entry for this ONU
        self._onu_omci_device = self.omci_agent.add_device(
            self.device_id,
            self.adapter_agent,
            support_classes=self.adapter.broadcom_omci,
            custom_me_map=self.adapter.custom_me_entities())
        # Port startup
        if self._pon is not None:
            self._pon.enabled = True

    # TODO: move to UniPort
    def update_logical_port(self, logical_device_id, port_id, state):
        try:
            self.log.info('updating-logical-port',
                          logical_port_id=port_id,
                          logical_device_id=logical_device_id,
                          state=state)
            logical_port = self.adapter_agent.get_logical_port(
                logical_device_id, port_id)
            logical_port.ofp_port.state = state
            self.adapter_agent.update_logical_port(logical_device_id,
                                                   logical_port)
        except Exception as e:
            self.log.exception("exception-updating-port", e=e)

    def delete(self, device):
        self.log.info('delete-onu', device=device)
        if self.parent_adapter:
            try:
                self.parent_adapter.delete_child_device(self.parent_id, device)
            except AttributeError:
                self.log.debug('parent-device-delete-child-not-implemented')
        else:
            self.log.debug("parent-adapter-not-available")

    def update_pm_config(self, device, pm_config):
        # TODO: This has not been tested
        self.log.info('update_pm_config', pm_config=pm_config)
        self.pm_metrics.update(pm_config)

    # Calling this assumes the onu is active/ready and had at least an initial mib downloaded.   This gets called from
    # flow decomposition that ultimately comes from onos
    def update_flow_table(self, device, flows):
        self.log.debug('function-entry', device=device, flows=flows)
        #
        # We need to proxy through the OLT to get to the ONU
        # Configuration from here should be using OMCI
        #
        #self.log.info('bulk-flow-update', device_id=device.id, flows=flows)

        # no point in pushing omci flows if the device isnt reachable
        if device.connect_status != ConnectStatus.REACHABLE or \
           device.admin_state != AdminState.ENABLED:
            self.log.warn("device-disabled-or-offline-skipping-flow-update",
                          admin=device.admin_state,
                          connect=device.connect_status)
            return

        def is_downstream(port):
            return port == self._pon_port_number

        def is_upstream(port):
            return not is_downstream(port)

        for flow in flows:
            _type = None
            _port = None
            _vlan_vid = None
            _udp_dst = None
            _udp_src = None
            _ipv4_dst = None
            _ipv4_src = None
            _metadata = None
            _output = None
            _push_tpid = None
            _field = None
            _set_vlan_vid = None
            self.log.debug('bulk-flow-update', device_id=device.id, flow=flow)
            try:
                _in_port = fd.get_in_port(flow)
                assert _in_port is not None

                if is_downstream(_in_port):
                    self.log.debug('downstream-flow')
                elif is_upstream(_in_port):
                    self.log.debug('upstream-flow')
                else:
                    raise Exception('port should be 1 or 2 by our convention')

                _out_port = fd.get_out_port(flow)  # may be None
                self.log.debug('out-port', out_port=_out_port)

                for field in fd.get_ofb_fields(flow):
                    if field.type == fd.ETH_TYPE:
                        _type = field.eth_type
                        self.log.debug('field-type-eth-type', eth_type=_type)

                    elif field.type == fd.IP_PROTO:
                        _proto = field.ip_proto
                        self.log.debug('field-type-ip-proto', ip_proto=_proto)

                    elif field.type == fd.IN_PORT:
                        _port = field.port
                        self.log.debug('field-type-in-port', in_port=_port)

                    elif field.type == fd.VLAN_VID:
                        _vlan_vid = field.vlan_vid & 0xfff
                        self.log.debug('field-type-vlan-vid', vlan=_vlan_vid)

                    elif field.type == fd.VLAN_PCP:
                        _vlan_pcp = field.vlan_pcp
                        self.log.debug('field-type-vlan-pcp', pcp=_vlan_pcp)

                    elif field.type == fd.UDP_DST:
                        _udp_dst = field.udp_dst
                        self.log.debug('field-type-udp-dst', udp_dst=_udp_dst)

                    elif field.type == fd.UDP_SRC:
                        _udp_src = field.udp_src
                        self.log.debug('field-type-udp-src', udp_src=_udp_src)

                    elif field.type == fd.IPV4_DST:
                        _ipv4_dst = field.ipv4_dst
                        self.log.debug('field-type-ipv4-dst',
                                       ipv4_dst=_ipv4_dst)

                    elif field.type == fd.IPV4_SRC:
                        _ipv4_src = field.ipv4_src
                        self.log.debug('field-type-ipv4-src',
                                       ipv4_dst=_ipv4_src)

                    elif field.type == fd.METADATA:
                        _metadata = field.table_metadata
                        self.log.debug('field-type-metadata',
                                       metadata=_metadata)

                    else:
                        raise NotImplementedError('field.type={}'.format(
                            field.type))

                for action in fd.get_actions(flow):

                    if action.type == fd.OUTPUT:
                        _output = action.output.port
                        self.log.debug('action-type-output',
                                       output=_output,
                                       in_port=_in_port)

                    elif action.type == fd.POP_VLAN:
                        self.log.debug('action-type-pop-vlan',
                                       in_port=_in_port)

                    elif action.type == fd.PUSH_VLAN:
                        _push_tpid = action.push.ethertype
                        self.log.debug('action-type-push-vlan',
                                       push_tpid=_push_tpid,
                                       in_port=_in_port)
                        if action.push.ethertype != 0x8100:
                            self.log.error('unhandled-tpid',
                                           ethertype=action.push.ethertype)

                    elif action.type == fd.SET_FIELD:
                        _field = action.set_field.field.ofb_field
                        assert (action.set_field.field.oxm_class ==
                                OFPXMC_OPENFLOW_BASIC)
                        self.log.debug('action-type-set-field',
                                       field=_field,
                                       in_port=_in_port)
                        if _field.type == fd.VLAN_VID:
                            _set_vlan_vid = _field.vlan_vid & 0xfff
                            self.log.debug('set-field-type-vlan-vid',
                                           _set_vlan_vid)
                        else:
                            self.log.error('unsupported-action-set-field-type',
                                           field_type=_field.type)
                    else:
                        self.log.error('unsupported-action-type',
                                       action_type=action.type,
                                       in_port=_in_port)

                # TODO: We only set vlan omci flows.  Handle omci matching ethertypes at some point in another task
                if _type is not None:
                    self.log.warn('ignoring-flow-with-ethType', ethType=_type)
                elif _set_vlan_vid is None or _set_vlan_vid == 0:
                    self.log.warn('ignorning-flow-that-does-not-set-vlanid')
                else:
                    self._add_vlan_filter_task(device, _set_vlan_vid)

            except Exception as e:
                self.log.exception('failed-to-install-flow', e=e, flow=flow)

    def _add_vlan_filter_task(self, device, _set_vlan_vid):
        def success(_results):
            self.log.info('vlan-tagging-success', _results=_results)
            device.reason = 'omci-flows-pushed'
            self._vlan_filter_task = None

        def failure(_reason):
            self.log.warn('vlan-tagging-failure', _reason=_reason)
            device.reason = 'omci-flows-failed-retrying'
            self._vlan_filter_task = reactor.callLater(
                _STARTUP_RETRY_WAIT, self._add_vlan_filter_task, device,
                _set_vlan_vid)

        self.log.info('setting-vlan-tag')
        self._vlan_filter_task = BrcmVlanFilterTask(self.omci_agent,
                                                    self.device_id,
                                                    _set_vlan_vid)
        self._deferred = self._onu_omci_device.task_runner.queue_task(
            self._vlan_filter_task)
        self._deferred.addCallbacks(success, failure)

    def get_tx_id(self):
        self.log.debug('function-entry')
        self.tx_id += 1
        return self.tx_id

    # TODO: Actually conform to or create a proper interface.
    # this and the other functions called from the olt arent very clear.
    # Called each time there is an onu "up" indication from the olt handler
    def create_interface(self, data):
        self.log.debug('function-entry', data=data)
        self._onu_indication = data

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

        self.log.debug('starting-openomci-statemachine')
        self._subscribe_to_events()
        reactor.callLater(1, self._onu_omci_device.start)
        onu_device.reason = "starting-openomci"
        self.adapter_agent.update_device(onu_device)
        self._heartbeat.enabled = True

    # Currently called each time there is an onu "down" indication from the olt handler
    # TODO: possibly other reasons to "update" from the olt?
    def update_interface(self, data):
        self.log.debug('function-entry', data=data)
        oper_state = data.get('oper_state', None)

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

        if oper_state == 'down':
            self.log.debug('stopping-openomci-statemachine')
            reactor.callLater(0, self._onu_omci_device.stop)
            self.disable_ports(onu_device)
            onu_device.reason = "stopping-openomci"
            onu_device.connect_status = ConnectStatus.UNREACHABLE
            onu_device.oper_status = OperStatus.DISCOVERED
            self.adapter_agent.update_device(onu_device)
        else:
            self.log.debug('not-changing-openomci-statemachine')

    # Not currently called by olt or anything else
    def remove_interface(self, data):
        self.log.debug('function-entry', data=data)

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

        self.log.debug('stopping-openomci-statemachine')
        reactor.callLater(0, self._onu_omci_device.stop)
        self.disable_ports(onu_device)
        onu_device.reason = "stopping-openomci"
        self.adapter_agent.update_device(onu_device)

        # TODO: im sure there is more to do here

    # Called when there is an olt up indication, providing the gem port id chosen by the olt handler
    def create_gemport(self, data):
        self.log.debug('create-gemport', data=data)
        gem_portdata = GemportsConfigData()
        gem_portdata.CopyFrom(data)

        # TODO: fill in what i have.  This needs to be provided from the OLT
        # currently its hardcoded/static
        gemdict = dict()
        gemdict['gemport-id'] = gem_portdata.gemport_id
        gemdict['encryption'] = gem_portdata.aes_indicator
        gemdict['tcont-ref'] = int(gem_portdata.tcont_ref)
        gemdict['name'] = gem_portdata.gemport_id
        gemdict['traffic-class'] = gem_portdata.traffic_class
        gemdict['traffic-class'] = gem_portdata.traffic_class

        gem_port = OnuGemPort.create(self,
                                     gem_port=gemdict,
                                     entity_id=self._pon.next_gem_entity_id)

        self._pon.add_gem_port(gem_port)

        self.log.debug('pon-add-gemport', gem_port=gem_port)

    # Not currently called.  Would be called presumably from the olt handler
    def remove_gemport(self, data):
        self.log.debug('remove-gemport', data=data)
        gem_port = GemportsConfigData()
        gem_port.CopyFrom(data)
        device = self.adapter_agent.get_device(self.device_id)
        if device.connect_status != ConnectStatus.REACHABLE:
            self.log.error('device-unreachable')
            return

        #TODO: Create a remove task that encompasses this

    # Called when there is an olt up indication, providing the tcont id chosen by the olt handler
    def create_tcont(self, tcont_data, traffic_descriptor_data):
        self.log.debug('create-tcont',
                       tcont_data=tcont_data,
                       traffic_descriptor_data=traffic_descriptor_data)
        tcontdata = TcontsConfigData()
        tcontdata.CopyFrom(tcont_data)

        # TODO: fill in what i have.  This needs to be provided from the OLT
        # currently its hardcoded/static
        tcontdict = dict()
        tcontdict['alloc-id'] = tcontdata.alloc_id
        tcontdict['name'] = tcontdata.name
        tcontdict['vont-ani'] = tcontdata.interface_reference

        # TODO: Not sure what to do with any of this...
        tddata = dict()
        tddata['name'] = 'not-sure-td-profile'
        tddata['fixed-bandwidth'] = "not-sure-fixed"
        tddata['assured-bandwidth'] = "not-sure-assured"
        tddata['maximum-bandwidth'] = "not-sure-max"
        tddata['additional-bw-eligibility-indicator'] = "not-sure-additional"

        td = OnuTrafficDescriptor.create(tddata)
        tcont = OnuTCont.create(self, tcont=tcontdict, td=td)

        self._pon.add_tcont(tcont)

        self.log.debug('pon-add-tcont', tcont=tcont)

        if tcontdata.interface_reference is not None:
            self.log.debug('tcont', tcont=tcont.alloc_id)
        else:
            self.log.info('received-null-tcont-data', tcont=tcont.alloc_id)

    # Not currently called.  Would be called presumably from the olt handler
    def remove_tcont(self, tcont_data, traffic_descriptor_data):
        self.log.debug('remove-tcont',
                       tcont_data=tcont_data,
                       traffic_descriptor_data=traffic_descriptor_data)
        device = self.adapter_agent.get_device(self.device_id)
        if device.connect_status != ConnectStatus.REACHABLE:
            self.log.error('device-unreachable')
            return

        # TODO: Create some omci task that encompases this what intended

    # Not currently called.  Would be called presumably from the olt handler
    def create_multicast_gemport(self, data):
        self.log.debug('function-entry', data=data)

        # TODO: create objects and populate for later omci calls

    def disable(self, device):
        self.log.debug('function-entry', device=device)
        try:
            self.log.info('sending-uni-lock-towards-device', device=device)

            def stop_anyway(reason):
                # proceed with disable regardless if we could reach the onu. for example onu is unplugged
                self.log.debug('stopping-openomci-statemachine')
                reactor.callLater(0, self._onu_omci_device.stop)
                self.disable_ports(device)
                device.oper_status = OperStatus.UNKNOWN
                device.reason = "omci-admin-lock"
                self.adapter_agent.update_device(device)

            # lock all the unis
            task = BrcmUniLockTask(self.omci_agent, self.device_id, lock=True)
            self._deferred = self._onu_omci_device.task_runner.queue_task(task)
            self._deferred.addCallbacks(stop_anyway, stop_anyway)
        except Exception as e:
            log.exception('exception-in-onu-disable', exception=e)

    def reenable(self, device):
        self.log.debug('function-entry', device=device)
        try:
            # Start up OpenOMCI state machines for this device
            # this will ultimately resync mib and unlock unis on successful redownloading the mib
            self.log.debug('restarting-openomci-statemachine')
            self._subscribe_to_events()
            device.reason = "restarting-openomci"
            self.adapter_agent.update_device(device)
            reactor.callLater(1, self._onu_omci_device.start)
            self._heartbeat.enabled = True
        except Exception as e:
            log.exception('exception-in-onu-reenable', exception=e)

    def reboot(self):
        self.log.info('reboot-device')
        device = self.adapter_agent.get_device(self.device_id)
        if device.connect_status != ConnectStatus.REACHABLE:
            self.log.error("device-unreachable")
            return

        def success(_results):
            self.log.info('reboot-success', _results=_results)
            self.disable_ports(device)
            device.connect_status = ConnectStatus.UNREACHABLE
            device.oper_status = OperStatus.DISCOVERED
            device.reason = "rebooting"
            self.adapter_agent.update_device(device)

        def failure(_reason):
            self.log.info('reboot-failure', _reason=_reason)

        self._deferred = self._onu_omci_device.reboot()
        self._deferred.addCallbacks(success, failure)

    def disable_ports(self, onu_device):
        self.log.info('disable-ports',
                      device_id=self.device_id,
                      onu_device=onu_device)

        # Disable all ports on that device
        self.adapter_agent.disable_all_ports(self.device_id)

        parent_device = self.adapter_agent.get_device(onu_device.parent_id)
        assert parent_device
        logical_device_id = parent_device.parent_id
        assert logical_device_id
        ports = self.adapter_agent.get_ports(onu_device.id, Port.ETHERNET_UNI)
        for port in ports:
            port_id = 'uni-{}'.format(port.port_no)
            # TODO: move to UniPort
            self.update_logical_port(logical_device_id, port_id,
                                     OFPPS_LINK_DOWN)

    def enable_ports(self, onu_device):
        self.log.info('enable-ports',
                      device_id=self.device_id,
                      onu_device=onu_device)

        # Disable all ports on that device
        self.adapter_agent.enable_all_ports(self.device_id)

        parent_device = self.adapter_agent.get_device(onu_device.parent_id)
        assert parent_device
        logical_device_id = parent_device.parent_id
        assert logical_device_id
        ports = self.adapter_agent.get_ports(onu_device.id, Port.ETHERNET_UNI)
        for port in ports:
            port_id = 'uni-{}'.format(port.port_no)
            # TODO: move to UniPort
            self.update_logical_port(logical_device_id, port_id, OFPPS_LIVE)

    # Called just before openomci state machine is started.  These listen for events from selected state machines,
    # most importantly, mib in sync.  Which ultimately leads to downloading the mib
    def _subscribe_to_events(self):
        self.log.debug('function-entry')

        # OMCI MIB Database sync status
        bus = self._onu_omci_device.event_bus
        topic = OnuDeviceEntry.event_bus_topic(
            self.device_id, OnuDeviceEvents.MibDatabaseSyncEvent)
        self._in_sync_subscription = bus.subscribe(topic, self.in_sync_handler)

        # OMCI Capabilities
        bus = self._onu_omci_device.event_bus
        topic = OnuDeviceEntry.event_bus_topic(
            self.device_id, OnuDeviceEvents.OmciCapabilitiesEvent)
        self._capabilities_subscription = bus.subscribe(
            topic, self.capabilties_handler)

    # Called when the mib is in sync
    def in_sync_handler(self, _topic, msg):
        self.log.debug('function-entry', _topic=_topic, msg=msg)
        if self._in_sync_subscription is not None:
            try:
                in_sync = msg[IN_SYNC_KEY]

                if in_sync:
                    # Only call this once
                    bus = self._onu_omci_device.event_bus
                    bus.unsubscribe(self._in_sync_subscription)
                    self._in_sync_subscription = None

                    # Start up device_info load
                    self.log.debug('running-mib-sync')
                    reactor.callLater(0, self._mib_in_sync)

            except Exception as e:
                self.log.exception('in-sync', e=e)

    def capabilties_handler(self, _topic, _msg):
        self.log.debug('function-entry', _topic=_topic, msg=_msg)
        if self._capabilities_subscription is not None:
            self.log.debug('capabilities-handler-done')

    # Mib is in sync, we can now query what we learned and actually start pushing ME (download) to the ONU.
    # Currently uses a basic mib download task that create a bridge with a single gem port and uni, only allowing EAP
    # Implement your own MibDownloadTask if you wish to setup something different by default
    def _mib_in_sync(self):
        self.log.debug('function-entry')

        omci = self._onu_omci_device
        in_sync = omci.mib_db_in_sync

        device = self.adapter_agent.get_device(self.device_id)
        device.reason = 'discovery-mibsync-complete'
        self.adapter_agent.update_device(device)

        if not self._dev_info_loaded:
            self.log.info('loading-device-data-from-mib',
                          in_sync=in_sync,
                          already_loaded=self._dev_info_loaded)

            omci_dev = self._onu_omci_device
            config = omci_dev.configuration

            # TODO: run this sooner somehow. shouldnt have to wait for mib sync to push an initial download
            # In Sync, we can register logical ports now. Ideally this could occur on
            # the first time we received a successful (no timeout) OMCI Rx response.
            try:

                # sort the lists so we get consistent port ordering.
                ani_list = sorted(
                    config.ani_g_entities) if config.ani_g_entities else []
                uni_list = sorted(
                    config.uni_g_entities) if config.uni_g_entities else []
                pptp_list = sorted(
                    config.pptp_entities) if config.pptp_entities else []
                veip_list = sorted(
                    config.veip_entities) if config.veip_entities else []

                if ani_list is None or uni_list is None:
                    device.reason = 'onu-missing-required-elements'
                    self.log.warn("no-ani-or-unis")
                    self.adapter_agent.update_device(device)
                    raise Exception("onu-missing-required-elements")

                # Currently logging the ani, pptp, veip, and uni for information purposes.
                # Actually act on the veip/pptp as its ME is the most correct one to use in later tasks.
                for entity_id in ani_list:
                    ani_value = config.ani_g_entities[entity_id]
                    self.log.debug("discovered-ani",
                                   entity_id=entity_id,
                                   value=ani_value)
                    # TODO: currently only one OLT PON port/ANI, so this works out.  With NGPON there will be 2..?
                    self._total_tcont_count = ani_value.get(
                        'total-tcont-count')
                    self.log.debug("set-total-tcont-count",
                                   tcont_count=self._total_tcont_count)

                for entity_id in pptp_list:
                    pptp_value = config.pptp_entities[entity_id]
                    self.log.debug("discovered-pptp",
                                   entity_id=entity_id,
                                   value=pptp_value)

                for entity_id in veip_list:
                    veip_value = config.veip_entities[entity_id]
                    self.log.debug("discovered-veip",
                                   key=entity_id,
                                   value=veip_value)

                for entity_id in uni_list:
                    uni_value = config.uni_g_entities[entity_id]
                    self.log.debug("discovered-uni",
                                   entity_id=entity_id,
                                   value=uni_value)

                    # TODO: can only support one UNI per ONU at this time. break out as soon as we have a good UNI
                    if entity_id in pptp_list:
                        self._add_uni_port(entity_id, uni_type=UniType.PPTP)
                        break
                    elif entity_id in veip_list:
                        self._add_uni_port(entity_id, uni_type=UniType.VEIP)
                        break
                    else:
                        self.log.warn("unable-to-find-uni-in-pptp-or-veip",
                                      key=entity_id,
                                      value=uni_value)

                self._qos_flexibility = config.qos_configuration_flexibility or 0
                self._omcc_version = config.omcc_version or OMCCVersion.Unknown

                if self._unis:
                    self._dev_info_loaded = True
                else:
                    device.reason = 'no-usable-unis'
                    self.adapter_agent.update_device(device)
                    self.log.warn("no-usable-unis")
                    raise Exception("no-usable-unis")

            except Exception as e:
                self.log.exception('device-info-load', e=e)
                self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT,
                                                   self._mib_in_sync)

        else:
            self.log.info('device-info-already-loaded',
                          in_sync=in_sync,
                          already_loaded=self._dev_info_loaded)

        if self._dev_info_loaded:
            if device.admin_state == AdminState.ENABLED:

                def success(_results):
                    self.log.info('mib-download-success', _results=_results)
                    device = self.adapter_agent.get_device(self.device_id)
                    device.reason = 'initial-mib-downloaded'
                    device.oper_status = OperStatus.ACTIVE
                    device.connect_status = ConnectStatus.REACHABLE
                    self.enable_ports(device)
                    self.adapter_agent.update_device(device)
                    self._mib_download_task = None

                def failure(_reason):
                    self.log.warn('mib-download-failure-retrying',
                                  _reason=_reason)
                    device.reason = 'initial-mib-download-failure-retrying'
                    self.adapter_agent.update_device(device)
                    self._deferred = reactor.callLater(_STARTUP_RETRY_WAIT,
                                                       self._mib_in_sync)

                # Download an initial mib that creates simple bridge that can pass EAP.  On success (above) finally set
                # the device to active/reachable.   This then opens up the handler to openflow pushes from outside
                self.log.info('downloading-initial-mib-configuration')
                self._mib_download_task = BrcmMibDownloadTask(
                    self.omci_agent, self)
                self._deferred = self._onu_omci_device.task_runner.queue_task(
                    self._mib_download_task)
                self._deferred.addCallbacks(success, failure)
            else:
                self.log.info('admin-down-disabling')
                self.disable(device)
        else:
            self.log.info('device-info-not-loaded-skipping-mib-download')

    def _add_uni_port(self, entity_id, uni_type=UniType.PPTP):
        self.log.debug('function-entry')

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

        parent_adapter_agent = registry('adapter_loader').get_agent(
            parent_device.adapter)
        if parent_adapter_agent is None:
            self.log.error('parent-adapter-could-not-be-retrieved')

        # TODO: This knowledge is locked away in openolt.  and it assumes one onu equals one uni...
        parent_device = self.adapter_agent.get_device(device.parent_id)
        parent_adapter = parent_adapter_agent.adapter.devices[parent_device.id]
        uni_no_start = parent_adapter.platform.mk_uni_port_num(
            self._onu_indication.intf_id, self._onu_indication.onu_id)

        # TODO: Some or parts of this likely need to move to UniPort. especially the format stuff
        working_port = self._next_port_number
        uni_no = uni_no_start + working_port
        uni_name = "uni-{}".format(uni_no)

        mac_bridge_port_num = working_port + 1

        self.log.debug('uni-port-inputs',
                       uni_no=uni_no,
                       uni_name=uni_name,
                       uni_type=uni_type,
                       entity_id=entity_id,
                       mac_bridge_port_num=mac_bridge_port_num)

        uni_port = UniPort.create(self, uni_name, uni_no, uni_name, uni_type)
        uni_port.entity_id = entity_id
        uni_port.enabled = True
        uni_port.mac_bridge_port_num = mac_bridge_port_num
        uni_port.add_logical_port(uni_port.port_number)

        self.log.debug("created-uni-port", uni=uni_port)

        self.adapter_agent.add_port(device.id, uni_port.get_port())
        parent_adapter_agent.add_port(device.parent_id, uni_port.get_port())

        self._unis[uni_port.port_number] = uni_port

        self._onu_omci_device.alarm_synchronizer.set_alarm_params(
            onu_id=self._onu_indication.onu_id, uni_ports=self._unis.values())
        # TODO: this should be in the PonPortclass
        pon_port = self._pon.get_port()
        self.adapter_agent.delete_port_reference_from_parent(
            self.device_id, pon_port)

        pon_port.peers.extend([
            Port.PeerPort(device_id=device.parent_id,
                          port_no=uni_port.port_number)
        ])

        self._pon._port = pon_port

        self.adapter_agent.add_port_reference_to_parent(
            self.device_id, pon_port)
        self.adapter_agent.update_device(device)
Ejemplo n.º 21
0
    def __init__(self,
                 agent,
                 device_id,
                 tasks,
                 advertise_events=False,
                 states=DEFAULT_STATES,
                 transitions=DEFAULT_TRANSITIONS,
                 initial_state='disabled',
                 timeout_delay=DEFAULT_RETRY,
                 tick_delay=DEFAULT_TICK_DELAY,
                 interval_skew=DEFAULT_INTERVAL_SKEW,
                 collect_attempts=DEFAULT_COLLECT_ATTEMPTS):
        """
        Class initialization

        :param agent: (OpenOmciAgent) Agent
        :param device_id: (str) ONU Device ID
        :param tasks: (dict) Tasks to run
        :param advertise_events: (bool) Advertise events on OpenOMCI Event Bus
        :param states: (list) List of valid states
        :param transitions: (dict) Dictionary of triggers and state changes
        :param initial_state: (str) Initial state machine state
        :param timeout_delay: (int/float) Number of seconds after a timeout to pause
        :param tick_delay: (int/float) Collection poll check delay while idle
        :param interval_skew: (int/float) Seconds to randomly skew the next interval
                              collection to spread out requests for PM intervals
        :param collect_attempts: (int) Max requests for a single PM interval before fail
        """
        self.log = structlog.get_logger(device_id=device_id)

        self._agent = agent
        self._device_id = device_id
        self._device = None
        self._pm_config = None
        self._timeout_delay = timeout_delay
        self._tick_delay = tick_delay
        self._interval_skew = interval_skew
        self._collect_attempts = collect_attempts

        self._sync_time_task = tasks['sync-time']
        self._get_interval_task = tasks['collect-data']
        self._create_pm_task = tasks['create-pm']
        self._delete_pm_task = tasks['delete-pm']
        self._advertise_events = advertise_events

        self._omci_cc_subscriptions = {  # RxEvent.enum -> Subscription Object
            RxEvent.MIB_Reset: None,
            RxEvent.Create: None,
            RxEvent.Delete: None
        }
        self._omci_cc_sub_mapping = {
            RxEvent.MIB_Reset: self.on_mib_reset_response,
            RxEvent.Create: self.on_create_response,
            RxEvent.Delete: self.on_delete_response,
        }
        self._me_watch_list = {
            MacBridgePortConfigurationData.class_id: {
                'create-delete': self.add_remove_enet_frame_pm,
                'instances':
                dict()  # BP entity_id -> (PM class_id, PM entity_id)
            }
        }
        self._deferred = None
        self._task_deferred = None
        self._current_task = None
        self._add_me_deferred = None
        self._delete_me_deferred = None
        self._next_interval = None
        self._enet_entity_id = IndexPool(1024, 1)

        # (Class ID, Instance ID) -> Collect attempts remaining
        self._pm_me_collect_retries = dict()
        self._add_pm_me = dict(
        )  # (pm cid, pm eid) -> (me cid, me eid, upstream)
        self._del_pm_me = set()

        # Statistics and attributes
        # TODO: add any others if it will support problem diagnosis

        # Set up state machine to manage states
        self.machine = Machine(model=self,
                               states=states,
                               transitions=transitions,
                               initial=initial_state,
                               queued=True,
                               ignore_invalid_triggers=True,
                               name='{}-{}'.format(self.__class__.__name__,
                                                   device_id))