Exemplo n.º 1
0
def get_all_enb_state(
        print_grpc_payload: bool = False) -> Optional[Dict[int, int]]:
    """
    Make RPC call to 'GetENBState' method of s1ap service
    """
    try:
        chan = ServiceRegistry.get_rpc_channel(
            S1AP_SERVICE_NAME,
            ServiceRegistry.LOCAL,
        )
    except ValueError:
        logger.error('Cant get RPC channel to %s', S1AP_SERVICE_NAME)
        return {}
    client = S1apServiceStub(chan)
    try:
        request = Void()
        print_grpc(
            request,
            print_grpc_payload,
            "Get All eNB State Request:",
        )
        res = client.GetENBState(request, DEFAULT_GRPC_TIMEOUT)
        print_grpc(
            res,
            print_grpc_payload,
            "Get All eNB State Response:",
        )
        return res.enb_state_map
    except grpc.RpcError as err:
        logger.warning(
            "GetEnbState error: [%s] %s",
            err.code(),
            err.details(),
        )
    return {}
Exemplo n.º 2
0
 def read_msg(self, message: Any) -> AcsReadMsgResult:
     if type(message) == models.SetParameterValuesResponse:
         if not self.status_non_zero_allowed:
             if message.Status != 0:
                 raise Tr069Error(
                     'Received SetParameterValuesResponse with '
                     'Status=%d' % message.Status, )
         self._mark_as_configured()
         if not self.acs.are_invasive_changes_applied:
             return AcsReadMsgResult(True, self.apply_invasive_transition)
         return AcsReadMsgResult(True, self.done_transition)
     elif type(message) == models.Fault:
         logger.error(
             'Received Fault in response to SetParameterValues, '
             'Code (%s), Message (%s)',
             message.FaultCode,
             message.FaultString,
         )
         if message.SetParameterValuesFault is not None:
             for fault in message.SetParameterValuesFault:
                 logger.error(
                     'SetParameterValuesFault Param: %s, '
                     'Code: %s, String: %s',
                     fault.ParameterName,
                     fault.FaultCode,
                     fault.FaultString,
                 )
     return AcsReadMsgResult(False, None)
Exemplo n.º 3
0
    def postprocess(
        self,
        mconfig: Any,
        service_cfg: Any,
        desired_cfg: EnodebConfiguration,
    ) -> None:
        # TODO: Get this config from the domain proxy
        # TODO @amarpad, set these when DProxy integration is done.
        # For now the radio will directly talk to the SAS and get these
        # attributes.
        desired_cfg.delete_parameter(ParameterName.EARFCNDL)
        desired_cfg.delete_parameter(ParameterName.DL_BANDWIDTH)
        desired_cfg.delete_parameter(ParameterName.UL_BANDWIDTH)

        # go through misc parameters and set them to default.
        for name, val in FreedomFiOneMiscParameters.defaults.items():
            desired_cfg.set_parameter(name, val)

        # Bump up the parameter key version
        self.acs.parameter_version_inc()

        # Workaround a bug in Sercomm firmware in release 3920, 3921
        # where the meaning of CellReservedForOperatorUse is wrong.
        # Set to True to ensure the PLMN is not reserved
        num_plmns = self.acs.data_model.get_num_plmns()
        for i in range(1, num_plmns + 1):
            object_name = ParameterName.PLMN_N % i
            desired_cfg.set_parameter_for_object(
                param_name=ParameterName.PLMN_N_CELL_RESERVED % i,
                value=True,
                object_name=object_name,
            )

        if self.WEB_UI_ENABLE_LIST_KEY in service_cfg:
            serial_nos = service_cfg.get(self.WEB_UI_ENABLE_LIST_KEY)
            if self.acs.device_cfg.has_parameter(
                    ParameterName.SERIAL_NUMBER, ):
                if self.acs.get_parameter(ParameterName.SERIAL_NUMBER) in \
                        serial_nos:
                    desired_cfg.set_parameter(
                        FreedomFiOneMiscParameters.WEB_UI_ENABLE,
                        True,
                    )
            else:
                # This should not happen
                EnodebdLogger.error("Serial number unknown for device")

        if self.SAS_KEY not in service_cfg:
            return

        sas_cfg = service_cfg[self.SAS_KEY]
        sas_param_names = self.acs.data_model.get_sas_param_names()
        for name, val in sas_cfg.items():
            if name not in sas_param_names:
                EnodebdLogger.warning("Ignoring attribute %s", name)
                continue
            desired_cfg.set_parameter(name, val)
Exemplo n.º 4
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.error(
            'Possible error running async subprocess: %s exited with '
            'return code [%d].', cmd, proc.returncode)
    return proc.returncode
Exemplo n.º 5
0
 def get_metric_value(enodeb_status: Dict[str, str], key: str):
     # Metrics are "sticky" when synced to the cloud - if we don't
     # receive a status update from enodeb, set the metric to 0
     # to explicitly indicate that it was not received, otherwise the
     # metrics collector will continue to report the last value
     val = enodeb_status.get(key, None)
     if val is None:
         return 0
     if type(val) is not bool:
         logger.error('Could not cast metric value %s to int', val)
         return 0
     return int(val)  # val should be either True or False
Exemplo n.º 6
0
    def _get_enb_label_from_request(self, request) -> str:
        label = 'default'
        ip = request.headers.get('X-Forwarded-For')
        if ip is None:
            ip = request.remote_addr

        if ip is None:
            return label

        try:
            label = self.enb_manager.get_serial(ip)
        except KeyError:
            logger.error("Couldn't find serial for ip", ip)
        return label
Exemplo n.º 7
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.º 8
0
    def read_msg(self, message: Any) -> Optional[str]:
        if type(message) == models.Fault:
            logger.error('Received Fault in response to SetParameterValues')
            if message.SetParameterValuesFault is not None:
                for fault in message.SetParameterValuesFault:
                    logger.error(
                        'SetParameterValuesFault Param: %s, Code: %s, String: %s',
                        fault.ParameterName, fault.FaultCode, fault.FaultString,
                    )
            raise Tr069Error(
                'Received Fault in response to SetParameterValues '
                '(faultstring = %s)' % message.FaultString,
            )
        elif not isinstance(message, models.SetParameterValuesResponse):
            return AcsReadMsgResult(False, None)
        if message.Status != 0:
            raise Tr069Error(
                'Received SetParameterValuesResponse with '
                'Status=%d' % message.Status,
            )
        param_name = ParameterName.ADMIN_STATE
        desired_admin_value = \
                self.acs.desired_cfg.get_parameter(param_name) \
                and self.admin_value
        magma_value = \
                self.acs.data_model.transform_for_magma(
                    param_name,
                    desired_admin_value,
                )
        self.acs.device_cfg.set_parameter(param_name, magma_value)

        if len(
            get_all_objects_to_delete(
                self.acs.desired_cfg,
                self.acs.device_cfg,
            ),
        ) > 0:
            return AcsReadMsgResult(True, self.del_obj_transition)
        elif len(
            get_all_objects_to_add(
                self.acs.desired_cfg,
                self.acs.device_cfg,
            ),
        ) > 0:
            return AcsReadMsgResult(True, self.add_obj_transition)
        else:
            return AcsReadMsgResult(True, self.done_transition)
Exemplo n.º 9
0
def get_all_enb_state() -> Optional[Dict[int, int]]:
    """
    Make RPC call to 'GetENBState' method of s1ap service
    """
    try:
        chan = ServiceRegistry.get_rpc_channel(S1AP_SERVICE_NAME,
                                               ServiceRegistry.LOCAL)
    except ValueError:
        logger.error('Cant get RPC channel to %s', S1AP_SERVICE_NAME)
        return {}
    client = S1apServiceStub(chan)
    try:
        res = client.GetENBState(Void(), DEFAULT_GRPC_TIMEOUT)
        return res.enb_state_map
    except grpc.RpcError as err:
        logger.warning("GetEnbState error: [%s] %s", err.code(), err.details())
    return {}
Exemplo n.º 10
0
    def postprocess(
        self,
        mconfig: Any,
        service_cfg: Any,
        desired_cfg: EnodebConfiguration,
    ) -> None:
        # TODO: Get this config from the domain proxy
        # TODO @amarpad, set these when DProxy integration is done.
        # For now the radio will directly talk to the SAS and get these
        # attributes.
        desired_cfg.delete_parameter(ParameterName.EARFCNDL)
        desired_cfg.delete_parameter(ParameterName.DL_BANDWIDTH)
        desired_cfg.delete_parameter(ParameterName.UL_BANDWIDTH)

        # go through misc parameters and set them to default.
        for name, val in FreedomFiOneMiscParameters.defaults.items():
            desired_cfg.set_parameter(name, val)

        # Bump up the parameter key version
        self.acs.parameter_version_inc()

        if self.WEB_UI_ENABLE_LIST_KEY in service_cfg:
            serial_nos = service_cfg.get(self.WEB_UI_ENABLE_LIST_KEY)
            if self.acs.device_cfg.has_parameter(
                    ParameterName.SERIAL_NUMBER, ):
                if self.acs.get_parameter(ParameterName.SERIAL_NUMBER) in \
                        serial_nos:
                    desired_cfg.set_parameter(
                        FreedomFiOneMiscParameters.WEB_UI_ENABLE,
                        True,
                    )
            else:
                # This should not happen
                EnodebdLogger.error("Serial number unknown for device")

        if self.SAS_KEY not in service_cfg:
            return

        sas_cfg = service_cfg[self.SAS_KEY]
        sas_param_names = self.acs.data_model.get_sas_param_names()
        for name, val in sas_cfg.items():
            if name not in sas_param_names:
                EnodebdLogger.warning("Ignoring attribute %s", name)
                continue
            desired_cfg.set_parameter(name, val)
Exemplo n.º 11
0
    def _get_enb_label_from_request(self, request) -> str:
        label = 'default'
        ip = request.headers.get('X-Forwarded-For')

        if ip is None:
            peername = request.transport.get_extra_info('peername')
            if peername is not None:
                ip, _ = peername

        if ip is None:
            return label

        label = self.enb_manager.get_serial_of_ip(ip)
        if label:
            logger.debug('Found serial %s for ip %s', label, ip)
        else:
            logger.error("Couldn't find serial for ip", ip)
        return label
Exemplo n.º 12
0
 def _dump_debug_info(self) -> None:
     if self.device_cfg is not None:
         logger.error('Device configuration: %s',
                      self.device_cfg.get_debug_info())
     else:
         logger.error('Device configuration: None')
     if self.desired_cfg is not None:
         logger.error('Desired configuration: %s',
                      self.desired_cfg.get_debug_info())
     else:
         logger.error('Desired configuration: None')
Exemplo n.º 13
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 enb_serial is None:
         # TR-069 message did not contain an eNodeB serial ID.
         logger.error('Cannot associate null eNB serial to a an IP')
         pass
     elif 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.º 14
0
    def handle_tr069_message(
        self,
        message: Tr069ComplexModel,
    ) -> Tr069ComplexModel:
        """
        Accept the tr069 message from the eNB and produce a reply.

        States may transition after reading a message but BEFORE producing
        a reply. Most steps in the provisioning process are represented as
        beginning with enodebd sending a request to the eNB, and waiting for
        the reply from the eNB.
        """
        # TransferComplete messages come at random times, and we ignore them
        if isinstance(message, models.TransferComplete):
            return models.TransferCompleteResponse()
        try:
            self._read_tr069_msg(message)
            return self._get_tr069_msg(message)
        except Exception:  # pylint: disable=broad-except
            logger.error('Failed to handle tr069 message')
            logger.error(traceback.format_exc())
            self._dump_debug_info()
            self.transition(self.unexpected_fault_state_name)
            return self._get_tr069_msg(message)
Exemplo n.º 15
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)
Exemplo n.º 16
0
    def set_magma_device_cfg(
        cls,
        name_to_val: Dict,
        device_cfg: EnodebConfiguration,
    ):
        """
        Convert FreedomFiOne name_to_val representation to magma device_cfg
        """
        success_str = "SUCCESS"  # String constant returned by radio
        insync_str = "INSYNC"

        if (name_to_val.get(cls.DEFAULT_GW)
                and name_to_val[cls.DEFAULT_GW].upper() != success_str):
            # Nothing will proceed if the eNB doesn't have an IP on the WAN
            serial_num = "unknown"
            if device_cfg.has_parameter(ParameterName.SERIAL_NUMBER):
                serial_num = device_cfg.get_parameter(
                    ParameterName.SERIAL_NUMBER, )
            EnodebdLogger.error(
                "Radio with serial number %s doesn't have IP address "
                "on WAN",
                serial_num,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.RF_TX_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.GPS_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.PTP_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.MME_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.OP_STATE,
                value=False,
            )
            return

        if (name_to_val.get(cls.SAS_STATUS)
                and name_to_val[cls.SAS_STATUS].upper() == success_str):
            device_cfg.set_parameter(
                param_name=ParameterName.RF_TX_STATUS,
                value=True,
            )
        else:
            # No sas grant so not transmitting. There is no explicit node for Tx
            # in FreedomFiOne
            device_cfg.set_parameter(
                param_name=ParameterName.RF_TX_STATUS,
                value=False,
            )

        if (name_to_val.get(cls.GPS_SCAN_STATUS)
                and name_to_val[cls.GPS_SCAN_STATUS].upper() == success_str):
            device_cfg.set_parameter(
                param_name=ParameterName.GPS_STATUS,
                value=True,
            )
            # Time comes through GPS so can only be insync with GPS is
            # in sync, we use PTP_STATUS field to overload timer is in Sync.
            if (name_to_val.get(cls.SYNC_STATUS)
                    and name_to_val[cls.SYNC_STATUS].upper() == insync_str):
                device_cfg.set_parameter(
                    param_name=ParameterName.PTP_STATUS,
                    value=True,
                )
            else:
                device_cfg.set_parameter(
                    param_name=ParameterName.PTP_STATUS,
                    value=False,
                )
        else:
            device_cfg.set_parameter(
                param_name=ParameterName.GPS_STATUS,
                value=False,
            )
            device_cfg.set_parameter(
                param_name=ParameterName.PTP_STATUS,
                value=False,
            )

        if (name_to_val.get(cls.DEFAULT_GW)
                and name_to_val[cls.DEFAULT_GW].upper() == success_str):
            device_cfg.set_parameter(
                param_name=ParameterName.MME_STATUS,
                value=True,
            )
        else:
            device_cfg.set_parameter(
                param_name=ParameterName.MME_STATUS,
                value=False,
            )

        if (name_to_val.get(cls.ENB_STATUS)
                and name_to_val[cls.ENB_STATUS].upper() == success_str):
            device_cfg.set_parameter(
                param_name=ParameterName.OP_STATE,
                value=True,
            )
        else:
            device_cfg.set_parameter(
                param_name=ParameterName.OP_STATE,
                value=False,
            )

        pass_through_params = [ParameterName.GPS_LAT, ParameterName.GPS_LONG]
        for name in pass_through_params:
            device_cfg.set_parameter(name, name_to_val[name])
Exemplo n.º 17
0
 def _assert_param_in_model(self, param_name: ParameterName) -> None:
     trparam_model = self.data_model
     tr_param = trparam_model.get_parameter(param_name)
     if tr_param is None:
         logger.error('Parameter <%s> not defined in model', param_name)
         raise ConfigurationError("Parameter not defined in model.")