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)
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)
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)
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
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
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)
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)
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)
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)