Beispiel #1
0
 def enter(self):
     """
     Perform additional actions on state enter
     """
     self.wait_timer = StateMachineTimer(self.CONFIG_DELAY_AFTER_BOOT)
     logger.info(
         'Holding off of eNB configuration for %s seconds. ',
         self.CONFIG_DELAY_AFTER_BOOT,
     )
Beispiel #2
0
    def enter(self):
        self.rem_timer = StateMachineTimer(self.CONFIG_DELAY_AFTER_BOOT)
        def check_timer() -> None:
            if self.rem_timer.is_done():
                self.acs.transition(self.done_transition)

        self.timer_handle =\
            self.acs.event_loop.call_later(self.CONFIG_DELAY_AFTER_BOOT,
                                           check_timer)
Beispiel #3
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.
        """
        logging.info('Checking mme connection')
        status = get_enodeb_status(self)

        reboot_disabled = \
            not self.is_enodeb_connected() \
            or not self.is_enodeb_configured() \
            or status['mme_connected'] == '1' \
            or not self.mconfig.allow_enodeb_transmit

        if reboot_disabled:
            if self.mme_timer is not None:
                logging.info('Clearing eNodeB reboot timer')
            metrics.STAT_ENODEB_REBOOT_TIMER_ACTIVE.set(0)
            self.mme_timer = None
            return

        if self.mme_timer is None:
            logging.info('Set eNodeB reboot timer: %s',
                         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():
            logging.warning('eNodeB reboot timer expired - rebooting!')
            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
Beispiel #4
0
    def test_is_done(self):
        timer_a = StateMachineTimer(0)
        self.assertTrue(timer_a.is_done(), 'Timer should be done')

        timer_b = StateMachineTimer(600)
        self.assertFalse(timer_b.is_done(), 'Timer should not be done')
Beispiel #5
0
class BaicellsQRTBQueuedEventsWaitState(EnodebAcsState):
    """
    We've already received an Inform message. This state is to handle a
    Baicells eNodeB issue.

    After eNodeB is rebooted, hold off configuring it for some time.

    In this state, just hang at responding to Inform, and then ending the
    TR-069 session.
    """

    CONFIG_DELAY_AFTER_BOOT = 60

    def __init__(self, acs: EnodebAcsStateMachine, when_done: str):
        super().__init__()
        self.acs = acs
        self.done_transition = when_done
        self.wait_timer = None

    def enter(self):
        """
        Perform additional actions on state enter
        """
        self.wait_timer = StateMachineTimer(self.CONFIG_DELAY_AFTER_BOOT)
        logger.info(
            'Holding off of eNB configuration for %s seconds. ',
            self.CONFIG_DELAY_AFTER_BOOT,
        )

    def exit(self):
        """
        Perform additional actions on state exit
        """
        self.wait_timer = None

    def read_msg(self, message: Any) -> AcsReadMsgResult:
        """
        Read incoming message

        Args:
            message (Any): TR069 message

        Returns:
            AcsReadMsgResult
        """
        if not isinstance(message, models.Inform):
            return AcsReadMsgResult(msg_handled=False, next_state=None)
        process_inform_message(
            message, self.acs.data_model,
            self.acs.device_cfg,
        )
        return AcsReadMsgResult(msg_handled=True, next_state=None)

    def get_msg(self, message: Any) -> AcsMsgAndTransition:
        """
        Send back a message to enb

        Args:
            message (Any): TR069 message

        Returns:
            AcsMsgAndTransition
        """
        if not self.wait_timer:
            logger.error('wait_timer is None.')
            raise ValueError('wait_timer is None.')

        if self.wait_timer.is_done():
            return AcsMsgAndTransition(
                msg=models.DummyInput(),
                next_state=self.done_transition,
            )
        remaining = self.wait_timer.seconds_remaining()
        logger.info(
            'Waiting with eNB configuration for %s more seconds. ',
            remaining,
        )
        return AcsMsgAndTransition(msg=models.DummyInput(), next_state=None)

    def state_description(self) -> str:
        """
        Describe the state

        Returns:
            str
        """
        if not self.wait_timer:
            logger.error('wait_timer is None.')
            raise ValueError('wait_timer is None.')

        remaining = self.wait_timer.seconds_remaining()
        return 'Waiting for eNB REM to run for %d more seconds before ' \
               'resuming with configuration.' % remaining
Beispiel #6
0
class BasicEnodebAcsStateMachine(EnodebAcsStateMachine):
    """
    Most of the EnodebAcsStateMachine classes for each device work about the
    same way. Differences lie mainly in the data model, desired configuration,
    and the state transition map.

    This class specifies the shared implementation between them.
    """

    # eNodeB connection timeout is used to determine whether or not eNodeB is
    # connected to enodebd based on time of last Inform message. By default,
    # periodic inform interval is 30secs, so timeout should be larger than
    # this.
    # Also set timer longer than reboot time, so that an eNodeB reboot does not
    # trigger a connection-timeout alarm.
    ENB_CONNECTION_TIMEOUT = 600  # In seconds

    # If eNodeB is disconnected from MME for an unknown reason for this time,
    # then reboot it. Set to a long time to ensure this doesn't interfere with
    # other enodebd configuration processes - it is just a measure of last
    # resort for an unlikely error case
    MME_DISCONNECT_ENODEB_REBOOT_TIMER = 15 * 60

    # Check the MME connection status every 15 seconds
    MME_CHECK_TIMER = 15

    def __init__(
        self,
        service: MagmaService,
        stats_mgr: StatsManager,
    ) -> None:
        super().__init__()
        self.service = service
        self.stats_manager = stats_mgr
        self.data_model = self.data_model_class()
        # The current known device config has few known parameters
        self.device_cfg = EnodebConfiguration(self.data_model)
        # The desired configuration depends on what the current configuration
        # is. This we don't know fully, yet.
        self.desired_cfg = None
        self.timeout_handler = None
        self.mme_timeout_handler = None
        self.mme_timer = None

        self._init_state_map()
        self.state = self.state_map[self.disconnected_state_name]
        self.state.enter()
        self._reset_timeout()
        self._periodic_check_mme_connection()

    def get_state(self) -> str:
        return self.state.state_description()

    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()
        self._read_tr069_msg(message)
        return self._get_tr069_msg()

    def transition(self, next_state: str) -> Any:
        logging.debug('State transition to <%s>', next_state)
        self.state.exit()
        self.state = self.state_map[next_state]
        self.state.enter()

    def stop_state_machine(self) -> None:
        self.state.exit()
        if self.timeout_handler is not None:
            self.mme_timeout_handler.cancel()
            self.timeout_handler.cancel()

    def _read_tr069_msg(self, message: Any) -> None:
        """ Process incoming message and maybe transition state """
        self._reset_timeout()
        self._handle_unexpected_inform_msg(message)
        next_state = self.state.read_msg(message)
        if next_state is not None:
            self.transition(next_state)

    def _get_tr069_msg(self) -> Any:
        """ Get a new message to send, and maybe transition state """
        msg_and_transition = self.state.get_msg()
        if msg_and_transition.next_state:
            self.transition(msg_and_transition.next_state)
        msg = msg_and_transition.msg
        return msg

    def _handle_unexpected_inform_msg(self, message: Any) -> None:
        """
        eNB devices may send an Inform message in the middle of a provisioning
        session. To deal with this, transition to a state that expects an
        Inform message, but also track the status of the eNB as not having
        been disconnected.
        """
        if isinstance(message, models.Inform):
            if self.is_enodeb_connected():
                self.transition(self.wait_inform_state_name)

    def _reset_timeout(self) -> None:
        if self.timeout_handler is not None:
            self.timeout_handler.cancel()

        def timed_out():
            self.transition(self.disconnected_state_name)

        self.timeout_handler = self.event_loop.call_later(
            self.ENB_CONNECTION_TIMEOUT,
            timed_out,
        )

    def _periodic_check_mme_connection(self) -> None:
        self._check_mme_connection()
        self.mme_timeout_handler = self.event_loop.call_later(
            self.MME_CHECK_TIMER,
            self._periodic_check_mme_connection,
        )

    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.
        """
        logging.info('Checking mme connection')
        status = get_enodeb_status(self)

        reboot_disabled = \
            not self.is_enodeb_connected() \
            or not self.is_enodeb_configured() \
            or status['mme_connected'] == '1' \
            or not self.mconfig.allow_enodeb_transmit

        if reboot_disabled:
            if self.mme_timer is not None:
                logging.info('Clearing eNodeB reboot timer')
            metrics.STAT_ENODEB_REBOOT_TIMER_ACTIVE.set(0)
            self.mme_timer = None
            return

        if self.mme_timer is None:
            logging.info('Set eNodeB reboot timer: %s',
                         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():
            logging.warning('eNodeB reboot timer expired - rebooting!')
            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

    @abstractmethod
    def _init_state_map(self) -> None:
        pass

    @property
    @abstractmethod
    def state_map(self) -> Dict[str, EnodebAcsState]:
        pass

    @property
    @abstractmethod
    def disconnected_state_name(self) -> str:
        pass

    @property
    @abstractmethod
    def wait_inform_state_name(self) -> str:
        """ State to handle unexpected Inform messages """
        pass