Exemplo n.º 1
0
 def enter(self):
     self.rem_timer = StateMachineTimer(self.CONFIG_DELAY_AFTER_BOOT)
     logger.info(
         'Holding off of eNB configuration for %s seconds. '
         'Will resume after eNB REM process has finished. ',
         self.CONFIG_DELAY_AFTER_BOOT,
     )
Exemplo n.º 2
0
    def _mark_as_configured(self) -> None:
        """
        A successful attempt at setting parameter values means that we need to
        update what we think the eNB's configuration is to match what we just
        set the parameter values to.
        """
        # Values of parameters
        name_to_val = get_param_values_to_set(self.acs.desired_cfg,
                                              self.acs.device_cfg,
                                              self.acs.data_model)
        for name, val in name_to_val.items():
            magma_val = self.acs.data_model.transform_for_magma(name, val)
            self.acs.device_cfg.set_parameter(name, magma_val)

        # Values of object parameters
        obj_to_name_to_val = get_obj_param_values_to_set(
            self.acs.desired_cfg, self.acs.device_cfg, self.acs.data_model)
        for obj_name, name_to_val in obj_to_name_to_val.items():
            for name, val in name_to_val.items():
                logger.debug('Set obj: %s, name: %s, val: %s', str(obj_name),
                             str(name), str(val))
                magma_val = self.acs.data_model.transform_for_magma(name, val)
                self.acs.device_cfg.set_parameter_for_object(
                    name, magma_val, obj_name)
        logger.info('Successfully configured CPE parameters!')
Exemplo n.º 3
0
async def check_and_apply_iptables_rules(
    port: str,
    enodebd_public_ip: str,
    enodebd_ip: str,
) -> None:
    command = 'sudo iptables -t nat -L'
    output = subprocess.run(command,
                            shell=True,
                            stdout=subprocess.PIPE,
                            check=True)
    command_output = output.stdout.decode('utf-8').strip()
    prerouting_rules = _get_prerouting_rules(command_output)
    if not prerouting_rules:
        logger.info('Configuring Iptables rule')
        await run(
            get_iptables_rule(
                port,
                enodebd_public_ip,
                enodebd_ip,
                add=True,
            ), )
    else:
        # Checks each rule in PREROUTING Chain
        expected_rules_present = check_rules(prerouting_rules, port,
                                             enodebd_public_ip, enodebd_ip)
        if not expected_rules_present:
            logger.info('Configuring Iptables rule')
            await run(
                get_iptables_rule(
                    port,
                    enodebd_public_ip,
                    enodebd_ip,
                    add=True,
                ), )
Exemplo n.º 4
0
    def _check_mme_connection(self) -> None:
        """
        Check if eNodeB should be connected to MME but isn't, and maybe reboot.

        If the eNB doesn't report connection to MME within a timeout period,
        get it to reboot in the hope that it will fix things.

        Usually, enodebd polls the eNodeB for whether it is connected to MME.
        This method checks the last polled MME connection status, and if
        eNodeB should be connected to MME but it isn't.
        """
        if self.device_cfg.has_parameter(ParameterName.MME_STATUS) and \
                self.device_cfg.get_parameter(ParameterName.MME_STATUS):
            is_mme_connected = 1
        else:
            is_mme_connected = 0

        # True if we would expect MME to be connected, but it isn't
        is_mme_unexpectedly_dc = \
            self.is_enodeb_connected() \
            and self.is_enodeb_configured() \
            and self.mconfig.allow_enodeb_transmit \
            and not is_mme_connected

        if is_mme_unexpectedly_dc:
            logger.warning(
                'eNodeB is connected to AGw, is configured, '
                'and has AdminState enabled for transmit. '
                'MME connection to eNB is missing.', )
            if self.mme_timer is None:
                logger.warning(
                    'eNodeB will be rebooted if MME connection '
                    'is not established in: %s seconds.',
                    self.MME_DISCONNECT_ENODEB_REBOOT_TIMER,
                )
                metrics.STAT_ENODEB_REBOOT_TIMER_ACTIVE.set(1)
                self.mme_timer = \
                    StateMachineTimer(self.MME_DISCONNECT_ENODEB_REBOOT_TIMER)
            elif self.mme_timer.is_done():
                logger.warning(
                    'eNodeB has not established MME connection '
                    'within %s seconds - rebooting!',
                    self.MME_DISCONNECT_ENODEB_REBOOT_TIMER,
                )
                metrics.STAT_ENODEB_REBOOTS.labels(
                    cause='MME disconnect').inc()
                metrics.STAT_ENODEB_REBOOT_TIMER_ACTIVE.set(0)
                self.mme_timer = None
                self.reboot_asap()
            else:
                # eNB is not connected to MME, but we're still waiting to see
                # if it will connect within the timeout period.
                # Take no action for now.
                pass
        else:
            if self.mme_timer is not None:
                logger.info('eNodeB has established MME connection.')
                self.mme_timer = None
            metrics.STAT_ENODEB_REBOOT_TIMER_ACTIVE.set(0)
Exemplo n.º 5
0
 def _clear_stats(self) -> None:
     """
     Clear statistics. Called when eNodeB management plane disconnects
     """
     logger.info('Clearing performance counter statistics')
     # Set all metrics to 0 if eNodeB not connected
     for metric in self.PM_FILE_TO_METRIC_MAP.values():
         metric.set(0)
Exemplo n.º 6
0
 def get_msg(self, message: Any) -> AcsMsgAndTransition:
     if self.prev_msg_was_inform:
         response = models.InformResponse()
         # Set maxEnvelopes to 1, as per TR-069 spec
         response.MaxEnvelopes = 1
         return AcsMsgAndTransition(response, None)
     logger.info('Sending reboot request to eNB')
     request = models.Reboot()
     request.CommandKey = ''
     return AcsMsgAndTransition(request, self.done_transition)
Exemplo n.º 7
0
 def _clear_stats(self) -> None:
     """
     Clear statistics. Called when eNodeB management plane disconnects
     """
     logger.info('Clearing performance counter statistics')
     # Set all metrics to 0 if eNodeB not connected
     for pm_name, metric in self.PM_FILE_TO_METRIC_MAP:
         # eNB data usage metrics will not be cleared
         if pm_name not in ('PDCP.UpOctUl', 'PDCP.UpOctDl'):
             metric.set(0)
Exemplo n.º 8
0
 def get_msg(self) -> AcsMsgAndTransition:
     if self.prev_msg_was_inform:
         response = models.InformResponse()
         # Set maxEnvelopes to 1, as per TR-069 spec
         response.MaxEnvelopes = 1
         return AcsMsgAndTransition(response, None)
     logger.info('Sending reboot request to eNB')
     request = models.Reboot()
     request.CommandKey = ''
     self.acs.are_invasive_changes_applied = True
     return AcsMsgAndTransition(request, self.done_transition)
Exemplo n.º 9
0
async def run(cmd):
    """Fork shell and run command NOTE: Popen is non-blocking"""
    cmd = shlex.split(cmd)
    proc = await asyncio.create_subprocess_shell(" ".join(cmd))
    await proc.communicate()
    if proc.returncode != 0:
        # This can happen because the NAT prerouting rule didn't exist
        logger.info(
            'Possible error running async subprocess: %s exited with '
            'return code [%d].', cmd, proc.returncode)
    return proc.returncode
Exemplo n.º 10
0
def tr069_server(state_machine_manager: StateMachineManager) -> None:
    """
    TR-069 server
    Inputs:
        - acs_to_cpe_queue = instance of Queue
            containing messages from parent process/thread to be sent to CPE
        - cpe_to_acs_queue = instance of Queue
            containing messages from CPE to be sent to parent process/thread
    """
    config = load_service_config("enodebd")

    AutoConfigServer.set_state_machine_manager(state_machine_manager)

    app = Tr069Application(
        [AutoConfigServer],
        CWMP_NS,
        in_protocol=Tr069Soap11(validator='soft'),
        out_protocol=Tr069Soap11(),
    )
    wsgi_app = WsgiApplication(app)

    try:
        ip_address = get_ip_from_if(config['tr069']['interface'])
    except (ValueError, KeyError) as e:
        # Interrupt main thread since process should not continue without TR-069
        _thread.interrupt_main()
        raise e

    socket.setdefaulttimeout(SOCKET_TIMEOUT)
    logger.info(
        'Starting TR-069 server on %s:%s',
        ip_address,
        config['tr069']['port'],
    )
    server = make_server(
        ip_address,
        config['tr069']['port'],
        wsgi_app,
        WSGIServer,
        tr069_WSGIRequestHandler,
    )

    # Note: use single-thread server, to avoid state contention
    try:
        server.serve_forever()
    finally:
        # Log error and interrupt main thread, to ensure that entire process
        # is restarted if this thread exits
        logger.error('Hit error in TR-069 thread. Interrupting main thread.')
        _thread.interrupt_main()
Exemplo n.º 11
0
 def _associate_serial_to_ip(
     self,
     client_ip: str,
     enb_serial: str,
 ) -> None:
     """
     If a device/IP combination changes, then the StateMachineManager
     must detect this, and update its mapping of what serial/IP corresponds
     to which handler.
     """
     if self._ip_serial_mapping.has_ip(client_ip):
         # Same IP, different eNB connected
         prev_serial = self._ip_serial_mapping.get_serial(client_ip)
         if enb_serial != prev_serial:
             logger.info(
                 'eNodeB change on IP <%s>, from %s to %s',
                 client_ip,
                 prev_serial,
                 enb_serial,
             )
             self._ip_serial_mapping.set_ip_and_serial(
                 client_ip, enb_serial)
             self._state_machine_by_ip[client_ip] = None
     elif self._ip_serial_mapping.has_serial(enb_serial):
         # Same eNB, different IP
         prev_ip = self._ip_serial_mapping.get_ip(enb_serial)
         if client_ip != prev_ip:
             logger.info(
                 'eNodeB <%s> changed IP from %s to %s',
                 enb_serial,
                 prev_ip,
                 client_ip,
             )
             self._ip_serial_mapping.set_ip_and_serial(
                 client_ip, enb_serial)
             handler = self._state_machine_by_ip[prev_ip]
             self._state_machine_by_ip[client_ip] = handler
             del self._state_machine_by_ip[prev_ip]
     else:
         # TR069 message is coming from a different IP, and a different
         # serial ID. No need to change mapping
         handler = None
         self._ip_serial_mapping.set_ip_and_serial(client_ip, enb_serial)
         self._state_machine_by_ip[client_ip] = handler
Exemplo n.º 12
0
def should_transition_to_firmware_upgrade_download(acs):
    device_sw_version = ""
    device_serial_number = ""
    if acs.device_cfg.has_parameter(ParameterName.SW_VERSION):
        device_sw_version = acs.device_cfg.get_parameter(
            ParameterName.SW_VERSION, )
    if acs.device_cfg.has_parameter(ParameterName.SERIAL_NUMBER):
        device_serial_number = acs.device_cfg.get_parameter(
            ParameterName.SERIAL_NUMBER, )
    if not device_sw_version or not device_serial_number:
        logger.debug(
            f'Skipping FW Download for eNB, missing device config: {device_sw_version=}, {device_serial_number=}.',
        )
        return False
    if acs.is_fw_upgrade_in_progress():
        logger.debug(
            'Skipping FW Download for eNB [%s], firmware upgrade in progress.',
            device_serial_number,
        )
        return False
    fw_upgrade_config = get_firmware_upgrade_download_config(acs)
    if not fw_upgrade_config:
        logger.debug(
            'Skipping FW Download for eNB [%s], missing firmware upgrade config in enodebd.yml.',
            device_serial_number,
        )
        return False
    target_software_version = fw_upgrade_config.get('version', '')
    if device_sw_version == target_software_version:
        logger.debug(
            'Skipping FW Download for eNB [%s], eNB Software Version [%s] up to date with firmware upgrade config.',
            device_serial_number,
            target_software_version,
        )
        acs.stop_fw_upgrade_timeout()
        return False
    logger.info(
        'Initiate FW Download for eNB [%s], eNB SW Version [%s], target SW Version [%s]',
        device_serial_number,
        device_sw_version,
        target_software_version,
    )
    return True
Exemplo n.º 13
0
def get_firmware_upgrade_download_config(acs):
    device_serial_number = ''
    if acs.device_cfg.has_parameter(ParameterName.SERIAL_NUMBER):
        device_serial_number = acs.device_cfg.get_parameter(
            ParameterName.SERIAL_NUMBER, )
    fw_upgrade_config = _get_firmware_upgrade_download_config_for_serial(
        acs,
        device_serial_number,
    )
    if fw_upgrade_config:
        logger.info(f'Found {fw_upgrade_config=} for {device_serial_number=}')
        return fw_upgrade_config
    device_model = acs.device_name
    fw_upgrade_config = _get_firmware_upgrade_download_config_for_model(
        acs,
        device_model,
    )
    if fw_upgrade_config:
        logger.info(f'Found {fw_upgrade_config=} for {device_model=}')
    return fw_upgrade_config
Exemplo n.º 14
0
    def _parse_tdd_counters(self, enb_label, names, data):
        """
        Parse eNodeB performance management counters from TDD structure.
        Most of the logic is just to extract the correct counter based on the
        name of the statistic. Each counter is either of type 'V', which is a
        single integer value, or 'CV', which contains multiple integer
        sub-elements, named 'SV', which we add together. E.g:
        <V i="9">0</V>
        <CV i="10">
          <SN>RRC.AttConnReestab.RECONF_FAIL</SN>
          <SV>0</SV>
          <SN>RRC.AttConnReestab.HO_FAIL</SN>
          <SV>0</SV>
          <SN>RRC.AttConnReestab.OTHER</SN>
          <SV>0</SV>
        </CV>
        See tests/stats_manager_tests.py for a more complete example.
        """
        index_data_map = self._build_index_to_data_map(data)
        name_index_map = self._build_name_to_index_map(names)

        # For each performance metric, extract value from XML document and set
        # internal metric to that value.
        for pm_name, metric in self.PM_FILE_TO_METRIC_MAP.items():

            elements = pm_name.split(':')
            counter = elements.pop(0)
            if len(elements) == 0:
                subcounter = None
            else:
                subcounter = elements.pop(0)

            index = name_index_map.get(counter)
            if index is None:
                logger.warning('PM counter %s not found in PmNames', counter)
                continue

            data_el = index_data_map.get(index)
            if data_el is None:
                logger.warning('PM counter %s not found in PmData', counter)
                continue

            if data_el.tag == 'V':
                if subcounter is not None:
                    logger.warning('No subcounter in PM counter %s', counter)
                    continue

                # Data is singular value
                try:
                    value = int(data_el.text)
                except ValueError:
                    logger.info('PM value (%s) of counter %s not integer',
                                data_el.text, counter)
                    continue
            elif data_el.tag == 'CV':
                # Check whether we want just one subcounter, or sum them all
                subcounter_index = None
                if subcounter is not None:
                    index = 0
                    for sub_name_el in data_el.findall('SN'):
                        if sub_name_el.text == subcounter:
                            subcounter_index = index
                        index = index + 1

                if subcounter is not None and subcounter_index is None:
                    logger.warning('PM subcounter (%s) not found', subcounter)
                    continue

                # Data is multiple sub-elements. Sum them, or select the one
                # of interest
                value = 0
                try:
                    index = 0
                    for sub_data_el in data_el.findall('SV'):
                        if subcounter_index is None or \
                                subcounter_index == index:
                            value = value + int(sub_data_el.text)
                        index = index + 1
                except ValueError:
                    logger.error('PM value (%s) of counter %s not integer',
                                 sub_data_el.text, pm_name)
                    continue
            else:
                logger.warning('Unknown PM data type (%s) of counter %s',
                               data_el.tag, pm_name)
                continue

            # Apply new value to metric
            if pm_name == 'PDCP.UpOctUl' or pm_name == 'PDCP.UpOctDl':
                metric.labels(enb_label).set(value)
            else:
                metric.set(value)