Beispiel #1
0
    def __init__(self,
                 decider: BoboDecider,
                 exchange_name: str,
                 user_id: str,
                 parameters: ConnectionParameters,
                 delay: float,
                 max_sync_attempts: int = 3) -> None:
        super().__init__()

        self.parameters = parameters

        self.outgoing = BoboDistOutgoing(decider=decider,
                                         exchange_name=exchange_name,
                                         user_id=user_id,
                                         parameters=parameters,
                                         max_sync_attempts=max_sync_attempts)

        self.incoming = BoboDistIncoming(outgoing=self.outgoing,
                                         decider=decider,
                                         exchange_name=exchange_name,
                                         user_id=user_id,
                                         parameters=parameters)

        self._incoming_thread = BoboTaskThread(self.incoming, delay)
        self._outgoing_thread = BoboTaskThread(self.outgoing, delay)
Beispiel #2
0
    def _config_forwarder(self) -> None:
        if self._action_forwarder is None:
            self._action_forwarder = NoAction()

        self._forwarder = ActionForwarder(action=self._action_forwarder,
                                          max_queue_size=self._max_queue_size,
                                          active=(not self._distributed))

        self._forwarder_thread = BoboTaskThread(task=self._forwarder,
                                                delay=self._delay)
Beispiel #3
0
    def _config_decider(self) -> None:
        self._decider = BoboDecider(recursive=self._recursive,
                                    max_queue_size=self._max_queue_size,
                                    active=(not self._distributed))

        self._decider_thread = BoboTaskThread(task=self._decider,
                                              delay=self._delay)

        # Receiver -> Decider
        self._receiver.subscribe(self._decider)
Beispiel #4
0
    def _config_receiver(self) -> None:
        if self._validator is None:
            self._validator = AnyValidator()

        self._receiver = BoboReceiver(validator=self._validator,
                                      formatter=PrimitiveEventFormatter(),
                                      max_queue_size=self._max_queue_size,
                                      active=(not self._distributed))

        self._receiver_thread = BoboTaskThread(task=self._receiver,
                                               delay=self._delay)

        if self._req_null_data:
            self._null_data_generator = BoboNullDataGenerator(
                receiver=self._receiver,
                null_data=self._null_data_obj,
                active=(not self._distributed))

            self._null_event_thread = BoboTaskThread(
                task=self._null_data_generator, delay=self._null_data_delay)
Beispiel #5
0
class BoboDistManager:
    """A manager for incoming and outgoing data that helps multiple
    :code:`bobocep` instances synchronise data between each other using an
    external message queue system.

    :param decider: The decider to which synchronisation will occur.
    :type decider: BoboDecider

    :param exchange_name: The exchange name to connect to on the external
                          message queue system.
    :type exchange_name: str

    :param user_id: The user ID to use on the external message queue system.
    :type user_id: str

    :param parameters: Parameters to connect to a message broker.
    :type parameters: ConnectionParameters

    :param delay: The delay for internal BoboTask threads, in seconds.
    :type delay: float

    :param max_sync_attempts: Maximum attempts to sync with other
                              :code:`bobocep` instances before giving up,
                              defaults to 3.
    :type max_sync_attempts: int, optional
    """
    def __init__(self,
                 decider: BoboDecider,
                 exchange_name: str,
                 user_id: str,
                 parameters: ConnectionParameters,
                 delay: float,
                 max_sync_attempts: int = 3) -> None:
        super().__init__()

        self.parameters = parameters

        self.outgoing = BoboDistOutgoing(decider=decider,
                                         exchange_name=exchange_name,
                                         user_id=user_id,
                                         parameters=parameters,
                                         max_sync_attempts=max_sync_attempts)

        self.incoming = BoboDistIncoming(outgoing=self.outgoing,
                                         decider=decider,
                                         exchange_name=exchange_name,
                                         user_id=user_id,
                                         parameters=parameters)

        self._incoming_thread = BoboTaskThread(self.incoming, delay)
        self._outgoing_thread = BoboTaskThread(self.outgoing, delay)

    def start(self):
        """Run the manager threads."""

        self._incoming_thread.start()
        self._outgoing_thread.start()

    def cancel(self):
        """Cancel the manager threads."""

        self._incoming_thread.cancel()
        self._outgoing_thread.cancel()
Beispiel #6
0
class BoboSetup(IDistOutgoingSubscriber):
    """A class to set up a working :code:`bobocep` process.

    :param delay: The delay, in seconds, for internal threads to wait before
                  repeating its processing task, defaults to 0.5.
    :type delay: float, optional

    :param recursive: Whether CompositeEvent instances should be put back
                      into the system on generation, defaults to True.
    :type recursive: bool, optional

    :param max_queue_size: The maximum data queue size,
                           defaults to 0 (infinite).
    :type max_queue_size: int, optional

    :param max_recent: The maximum number of recent CompositeEvent instances
                        generated by this handler to pass to runs.
                        Minimum of 1, defaults to 1.
    :type max_recent: int, optional
    """
    def __init__(self,
                 delay: float = 0.5,
                 recursive: bool = True,
                 max_queue_size: int = 0,
                 max_recent: int = 1) -> None:
        super().__init__()

        self._delay = delay
        self._recursive = recursive
        self._max_queue_size = max_queue_size
        self._max_recent = max(1, max_recent)

        self._lock = RLock()
        self._running = False
        self._cancelled = False
        self._configured = False

        self._validator = None
        self._event_defs = []
        self._action_producer = None
        self._action_forwarder = None

        self._req_null_data = False
        self._null_data_generator = None
        self._null_data_delay = 0
        self._null_data_obj = None

        self._distributed = False
        self._manager = None
        self._exchange_name = None
        self._user_name = None
        self._parameters = None
        self._user_id = None
        self._is_synced = False

        self._receiver = None
        self._decider = None
        self._producer = None
        self._forwarder = None

        self._receiver_thread = None
        self._null_event_thread = None
        self._decider_thread = None
        self._producer_thread = None
        self._forwarder_thread = None

        self._subs_receiver = []
        self._subs_decider = {}
        self._subs_producer = {}
        self._subs_forwarder = []

    def is_ready(self) -> bool:
        """
        :return: True if active and synced, False otherwise.
        """

        with self._lock:
            if self.is_active():
                if self._distributed:
                    return self._is_synced
                else:
                    return True

    def is_active(self) -> bool:
        """
        :return: True if running and not cancelled, False otherwise.
        """

        with self._lock:
            return self._running and not self._cancelled

    def is_inactive(self) -> bool:
        """
        :return: True if not running and not cancelled, False otherwise.
        """

        with self._lock:
            return not self._running and not self._cancelled

    def is_cancelled(self) -> bool:
        """
        :return: True if cancelled, False otherwise.
        """

        with self._lock:
            return self._cancelled

    def is_configured(self) -> bool:
        """
        :return: True if configured, False otherwise.
        """

        with self._lock:
            return self._configured

    def get_receiver(self) -> BoboReceiver:
        """
        :raises RuntimeError: Attempting access before configuration.

        :return: The Receiver.
        """

        with self._lock:
            if not self._configured:
                raise RuntimeError("Setup must be configured first.")

            return self._receiver

    def get_decider(self) -> BoboDecider:
        """
        :raises RuntimeError: Attempting access before configuration.

        :return: The Receiver.
        """

        with self._lock:
            if not self._configured:
                raise RuntimeError("Setup must be configured first.")

            return self._decider

    def get_producer(self) -> ActionProducer:
        """
        :raises RuntimeError: Attempting access before configuration.

        :return: The Receiver.
        """

        with self._lock:
            if not self._configured:
                raise RuntimeError("Setup must be configured first.")

            return self._producer

    def get_forwarder(self) -> ActionForwarder:
        """
        :raises RuntimeError: Attempting access before configuration.

        :return: The Forwarder.
        """

        with self._lock:
            if not self._configured:
                raise RuntimeError("Setup must be configured first.")

            return self._forwarder

    def get_distributed(self) -> BoboDistManager:
        """
        :raises RuntimeError: Attempting access before configuration.
        :raises RuntimeError: Distributed was not configured.

        :return: The distrubted manager.
        """

        with self._lock:
            if not self._configured:
                raise RuntimeError("Setup must be configured first.")
            elif not self._distributed:
                raise RuntimeError("Distributed was not configured.")

            return self._manager

    def get_null_data_generator(self) -> BoboNullDataGenerator:
        """
        :raises RuntimeError: Attempting access before configuration.

        :return: The null data generator, or None if one was not configured
         during setup.
        """

        with self._lock:
            if not self._configured:
                raise RuntimeError("Setup must be configured first.")

            return self._null_data_generator

    def get_complex_events(self) -> List[BoboComplexEvent]:
        """
        :return: A list of complex event definitions currently in setup.
        """

        with self._lock:
            if self.is_inactive():
                return self._event_defs[:]

    def add_complex_event(self, event_def: BoboComplexEvent) -> None:
        """
        Adds a complex event definition to the setup.

        :param event_def: The complex event definition.
        :type event_def: BoboComplexEvent
        """

        with self._lock:
            if self.is_inactive():
                self._event_defs.append(event_def)

    def config_receiver(self, validator: BoboValidator) -> None:
        """
        Configure the Receiver.

        :param validator: The validator to use in the Receiver.
        :type validator: BoboValidator
        """

        with self._lock:
            if self.is_inactive():
                self._validator = validator

    def config_producer(self, action: BoboAction) -> None:
        """
        Configure the Producer.

        :param action: The action to execute in the Producer on CompositeEvent
                       generation. If this action returns True, the
                       CompositeEvent is passed to the Forwarder. Otherwise,
                       it is dropped.
        :type action: BoboAction
        """

        with self._lock:
            if self.is_inactive():
                self._action_producer = action

    def config_forwarder(self, action: BoboAction) -> None:
        """
        Configure the Forwarder.

        :param action: The action to execute in the Forwarder. If this action
                       returns True, the CompositeEvent is passed to the
                       Forwarder's subscribers. Otherwise, it is dropped.
        :type action: BoboAction
        """

        with self._lock:
            if self.is_inactive():
                self._action_forwarder = action

    def config_distributed(self, exchange_name: str, user_name: str,
                           parameters: ConnectionParameters) -> None:
        """
        Configure the connection to the external message broker.

        :param exchange_name: The exchange name.
        :type exchange_name: str

        :param user_name: The user name.
        :type user_name: str

        :param parameters: Parameters to connect to a message broker.
        :type parameters: ConnectionParameters
        """

        with self._lock:
            if self.is_inactive():
                self._distributed = True
                self._exchange_name = exchange_name
                self._user_name = user_name
                self._parameters = parameters

    def config_null_data(self, delay_sec: float,
                         null_data: BoboNullData) -> None:
        """
        Configure static data to be input periodically into the Receiver.

        :param delay_sec: The rate at which to send data. Minimum of 0.1
                          seconds.
        :type delay_sec: float

        :param null_data: The data to send. Ensure that it is able to pass the
                          Receiver's validation criteria. Otherwise, the null
                          data will not enter the system.
        :type null_data: BoboNullData
        """

        with self._lock:
            if self.is_inactive():
                self._req_null_data = True
                self._null_data_delay = max(0.1, delay_sec)
                self._null_data_obj = null_data

    def on_sync(self) -> None:
        with self._lock:
            if self._distributed:
                self._activate_tasks()
            self._is_synced = True

    def start(self) -> None:
        """Start the setup.

        :raises RuntimeError: Running setup when it is already active.
        :raises RuntimeError: No complex event definitions found.
        """

        with self._lock:
            if self.is_active():
                raise RuntimeError("Setup is already active.")

            if not self._configured:
                self.configure()
            self._start_threads()
            self._running = True

            # tasks active by default if not distributed, sync immediately
            if not self._distributed:
                self._is_synced = True

    def configure(self) -> None:
        with self._lock:
            if self._cancelled:
                raise RuntimeError("Setup has been cancelled.")

            if self.is_active():
                raise RuntimeError("Setup is already active.")

            if self._configured:
                raise RuntimeError("Setup is already configured.")

            if len(self._event_defs) == 0:
                raise RuntimeError("No complex event definitions found. "
                                   "At least one must be provided.")

            self._config_receiver()
            self._config_decider()
            self._config_producer()
            self._config_forwarder()

            self._config_distributed()
            self._config_definitions()
            self._config_extra_subscriptions()

            self._configured = True

    def cancel(self) -> None:
        """Cancel the setup."""

        with self._lock:
            if not self._cancelled:
                self._cancelled = True
                if self._running:
                    self._running = False
                    self._deactivate_tasks()
                    self._stop_threads()

    def subscribe_receiver(self, subscriber: IReceiverSubscriber) -> None:
        with self._lock:
            if self.is_inactive():
                if subscriber not in self._subs_receiver:
                    self._subs_receiver.append(subscriber)

    def subscribe_decider(self, nfa_name: str,
                          subscriber: IDeciderSubscriber) -> None:
        with self._lock:
            if self.is_inactive():
                if nfa_name not in self._subs_decider:
                    self._subs_decider[nfa_name] = []

                if subscriber not in self._subs_decider[nfa_name]:
                    self._subs_decider[nfa_name].append(subscriber)

    def subscribe_producer(self, nfa_name: str,
                           subscriber: IProducerSubscriber) -> None:
        with self._lock:
            if self.is_inactive():
                if nfa_name not in self._subs_producer:
                    self._subs_producer[nfa_name] = []

                if subscriber not in self._subs_producer[nfa_name]:
                    self._subs_producer[nfa_name].append(subscriber)

    def subscribe_forwarder(self, subscriber: IForwarderSubscriber) -> None:
        with self._lock:
            if self.is_inactive() and subscriber not in self._subs_forwarder:
                self._subs_forwarder.append(subscriber)

    def _config_receiver(self) -> None:
        if self._validator is None:
            self._validator = AnyValidator()

        self._receiver = BoboReceiver(validator=self._validator,
                                      formatter=PrimitiveEventFormatter(),
                                      max_queue_size=self._max_queue_size,
                                      active=(not self._distributed))

        self._receiver_thread = BoboTaskThread(task=self._receiver,
                                               delay=self._delay)

        if self._req_null_data:
            self._null_data_generator = BoboNullDataGenerator(
                receiver=self._receiver,
                null_data=self._null_data_obj,
                active=(not self._distributed))

            self._null_event_thread = BoboTaskThread(
                task=self._null_data_generator, delay=self._null_data_delay)

    def _config_decider(self) -> None:
        self._decider = BoboDecider(recursive=self._recursive,
                                    max_queue_size=self._max_queue_size,
                                    active=(not self._distributed))

        self._decider_thread = BoboTaskThread(task=self._decider,
                                              delay=self._delay)

        # Receiver -> Decider
        self._receiver.subscribe(self._decider)

    def _config_producer(self) -> None:
        if self._action_producer is None:
            self._action_producer = NoAction()

        self._producer = ActionProducer(action=self._action_producer,
                                        max_queue_size=self._max_queue_size,
                                        active=(not self._distributed))

        self._producer_thread = BoboTaskThread(task=self._producer,
                                               delay=self._delay)

    def _config_forwarder(self) -> None:
        if self._action_forwarder is None:
            self._action_forwarder = NoAction()

        self._forwarder = ActionForwarder(action=self._action_forwarder,
                                          max_queue_size=self._max_queue_size,
                                          active=(not self._distributed))

        self._forwarder_thread = BoboTaskThread(task=self._forwarder,
                                                delay=self._delay)

    def _config_distributed(self) -> None:
        if self._distributed:
            self._user_id = "{}-{}-{}".format(self._user_name, uuid4(),
                                              time_ns())

            self._manager = BoboDistManager(decider=self._decider,
                                            exchange_name=self._exchange_name,
                                            user_id=self._user_id,
                                            parameters=self._parameters,
                                            delay=self._delay)

            # setup will activate tasks on sync
            self._manager.outgoing.subscribe(self)

    def _config_definitions(self) -> None:
        for event_def in self._event_defs:
            handler = BoboNFAHandler(nfa=BoboRuleBuilder.nfa(
                name_nfa=event_def.name, pattern=event_def.pattern),
                                     buffer=SharedVersionedMatchBuffer(),
                                     max_recent=self._max_recent)

            self._decider.add_nfa_handler(handler)

            # Decider -> Producer
            self._decider.subscribe(event_def.name, self._producer)

            if self._recursive:
                # Producer -> Decider
                self._producer.subscribe(event_def.name, self._decider)

            # Producer -> Forwarder
            self._producer.subscribe(event_def.name, self._forwarder)

            if event_def.action is not None:
                # Producer -> Action
                self._producer.subscribe(event_def.name, event_def.action)

                # Action -> Producer
                event_def.action.subscribe(self._producer)

            if self._distributed:
                # Handler -> Outgoing
                handler.subscribe(self._manager.outgoing)

                # Producer -> Outgoing
                self._producer.subscribe(event_def.name,
                                         self._manager.outgoing)

    def _config_extra_subscriptions(self):
        # receiver
        for sub in self._subs_receiver:
            self._receiver.subscribe(sub)

        # decider
        for nfa_name, subs in self._subs_decider.items():
            for sub in subs:
                self._decider.subscribe(nfa_name, sub)

        # producer
        for nfa_name, subs in self._subs_producer.items():
            for sub in subs:
                self._producer.subscribe(nfa_name, sub)

        # forwarder
        for sub in self._subs_forwarder:
            self._forwarder.subscribe(sub)

    def _start_threads(self) -> None:
        self._receiver_thread.start()
        self._decider_thread.start()
        self._producer_thread.start()
        self._forwarder_thread.start()

        if self._null_event_thread is not None:
            self._null_event_thread.start()

        if self._manager is not None:
            self._manager.start()

    def _stop_threads(self) -> None:
        self._receiver_thread.cancel()
        self._decider_thread.cancel()
        self._producer_thread.cancel()
        self._forwarder_thread.cancel()

        if self._null_event_thread is not None:
            self._null_event_thread.cancel()

        if self._manager is not None:
            self._manager.cancel()

    def _activate_tasks(self) -> None:
        self._forwarder.activate()
        self._producer.activate()
        self._decider.activate()
        self._receiver.activate()

        if self._null_data_generator is not None:
            self._null_data_generator.activate()

    def _deactivate_tasks(self) -> None:
        if self._null_data_generator is not None:
            self._null_data_generator.deactivate()

        self._receiver.deactivate()
        self._decider.deactivate()
        self._producer.deactivate()
        self._forwarder.deactivate()