Exemple #1
0
    def test_connection_failure(self, mocker):
        """Test the connection fails to be established."""
        api_mock = mocker.Mock()
        api_mock.authenticate.return_value = Token(True, 'abc', {}, 2000000,
                                                   1000000)

        sse_mock = mocker.Mock(spec=SplitSSEClient)
        sse_constructor_mock = mocker.Mock()
        sse_constructor_mock.return_value = sse_mock
        timer_mock = mocker.Mock()
        mocker.patch('splitio.push.manager.Timer', new=timer_mock)
        mocker.patch('splitio.push.manager.SplitSSEClient',
                     new=sse_constructor_mock)
        feedback_loop = Queue()
        manager = PushManager(api_mock, mocker.Mock(), feedback_loop,
                              mocker.Mock())

        def new_start(*args, **kwargs):  # pylint: disable=unused-argument
            """splitsse.start mock."""
            thread = Thread(target=manager._handle_connection_end)
            thread.setDaemon(True)
            thread.start()
            return False

        sse_mock.start.side_effect = new_start

        manager.start()
        assert feedback_loop.get() == Status.PUSH_RETRYABLE_ERROR
        assert timer_mock.mock_calls == [mocker.call(0, Any())]
    def test_connection_success(self, mocker):
        """Test the initial status is ok and reset() works as expected."""
        api_mock = mocker.Mock()
        api_mock.authenticate.return_value = Token(True, 'abc', {}, 2000000,
                                                   1000000)

        sse_mock = mocker.Mock(spec=SplitSSEClient)
        sse_constructor_mock = mocker.Mock()
        sse_constructor_mock.return_value = sse_mock
        timer_mock = mocker.Mock()
        mocker.patch('splitio.push.manager.Timer', new=timer_mock)
        mocker.patch('splitio.push.manager.SplitSSEClient',
                     new=sse_constructor_mock)
        feedback_loop = Queue()
        manager = PushManager(api_mock, mocker.Mock(), feedback_loop)

        def new_start(*args, **kwargs):  # pylint: disable=unused-argument
            """splitsse.start mock."""
            thread = Thread(target=manager._handle_connection_ready)
            thread.setDaemon(True)
            thread.start()
            return True

        sse_mock.start.side_effect = new_start

        manager.start()
        assert feedback_loop.get() == Status.PUSH_SUBSYSTEM_UP
        assert timer_mock.mock_calls == [
            mocker.call(0, Any()),
            mocker.call().cancel(),
            mocker.call(1000000 - _TOKEN_REFRESH_GRACE_PERIOD,
                        manager._token_refresh),
            mocker.call().setName('TokenRefresh'),
            mocker.call().start()
        ]
Exemple #3
0
    def __init__(self,
                 ready_flag,
                 synchronizer,
                 auth_api,
                 streaming_enabled,
                 sse_url=None):  # pylint:disable=too-many-arguments
        """
        Construct Manager.

        :param ready_flag: Flag to set when splits initial sync is complete.
        :type ready_flag: threading.Event

        :param split_synchronizers: synchronizers for performing start/stop logic
        :type split_synchronizers: splitio.sync.synchronizer.Synchronizer

        :param auth_api: Authentication api client
        :type auth_api: splitio.api.auth.AuthAPI

        :param streaming_enabled: whether to use streaming or not
        :type streaming_enabled: bool
        """
        self._streaming_enabled = streaming_enabled
        self._ready_flag = ready_flag
        self._synchronizer = synchronizer
        if self._streaming_enabled:
            self._push_status_handler_active = True
            self._backoff = Backoff()
            self._queue = Queue()
            self._push = PushManager(auth_api, synchronizer, self._queue,
                                     sse_url)
            self._push_status_handler = Thread(
                target=self._streaming_feedback_handler,
                name='PushStatusHandler')
            self._push_status_handler.setDaemon(True)
    def test_push_disabled(self, mocker):
        """Test the initial status is ok and reset() works as expected."""
        api_mock = mocker.Mock()
        api_mock.authenticate.return_value = Token(False, 'abc', {}, 1, 2)

        sse_mock = mocker.Mock(spec=SplitSSEClient)
        sse_constructor_mock = mocker.Mock()
        sse_constructor_mock.return_value = sse_mock
        timer_mock = mocker.Mock()
        mocker.patch('splitio.push.manager.Timer', new=timer_mock)
        mocker.patch('splitio.push.manager.SplitSSEClient',
                     new=sse_constructor_mock)
        feedback_loop = Queue()
        manager = PushManager(api_mock, mocker.Mock(), feedback_loop)
        manager.start()
        assert feedback_loop.get() == Status.PUSH_NONRETRYABLE_ERROR
        assert timer_mock.mock_calls == [mocker.call(0, Any())]
        assert sse_mock.mock_calls == []
    def test_auth_apiexception(self, mocker):
        """Test the initial status is ok and reset() works as expected."""
        api_mock = mocker.Mock()
        api_mock.authenticate.side_effect = APIException('something')

        sse_mock = mocker.Mock(spec=SplitSSEClient)
        sse_constructor_mock = mocker.Mock()
        sse_constructor_mock.return_value = sse_mock
        timer_mock = mocker.Mock()
        mocker.patch('splitio.push.manager.Timer', new=timer_mock)
        mocker.patch('splitio.push.manager.SplitSSEClient',
                     new=sse_constructor_mock)

        feedback_loop = Queue()
        manager = PushManager(api_mock, mocker.Mock(), feedback_loop)
        manager.start()
        assert feedback_loop.get() == Status.PUSH_RETRYABLE_ERROR
        assert timer_mock.mock_calls == [mocker.call(0, Any())]
        assert sse_mock.mock_calls == []
    def test_segment_change(self, mocker):
        """Test update-type messages are properly forwarded to the processor."""
        sse_event = SSEEvent('1', EventType.MESSAGE, '', '{}')
        update_message = SegmentChangeUpdate('chan', 123, 456, 'some_segment')
        parse_event_mock = mocker.Mock(spec=parse_incoming_event)
        parse_event_mock.return_value = update_message
        mocker.patch('splitio.push.manager.parse_incoming_event',
                     new=parse_event_mock)

        processor_mock = mocker.Mock(spec=MessageProcessor)
        mocker.patch('splitio.push.manager.MessageProcessor',
                     new=processor_mock)

        manager = PushManager(mocker.Mock(), mocker.Mock(), mocker.Mock())
        manager._event_handler(sse_event)
        assert parse_event_mock.mock_calls == [mocker.call(sse_event)]
        assert processor_mock.mock_calls == [
            mocker.call(Any()),
            mocker.call().handle(update_message)
        ]
    def test_occupancy_message(self, mocker):
        """Test control mesage is forwarded to status tracker."""
        sse_event = SSEEvent('1', EventType.MESSAGE, '', '{}')
        occupancy_message = OccupancyMessage(
            '[?occupancy=metrics.publishers]control_pri', 123, 2)
        parse_event_mock = mocker.Mock(spec=parse_incoming_event)
        parse_event_mock.return_value = occupancy_message
        mocker.patch('splitio.push.manager.parse_incoming_event',
                     new=parse_event_mock)

        status_tracker_mock = mocker.Mock(spec=PushStatusTracker)
        mocker.patch('splitio.push.manager.PushStatusTracker',
                     new=status_tracker_mock)

        manager = PushManager(mocker.Mock(), mocker.Mock(), mocker.Mock())
        manager._event_handler(sse_event)
        assert parse_event_mock.mock_calls == [mocker.call(sse_event)]
        assert status_tracker_mock.mock_calls == [
            mocker.call(),
            mocker.call().handle_occupancy(occupancy_message)
        ]
    def test_control_message(self, mocker):
        """Test control mesage is forwarded to status tracker."""
        sse_event = SSEEvent('1', EventType.MESSAGE, '', '{}')
        control_message = ControlMessage('chan', 123,
                                         ControlType.STREAMING_ENABLED)
        parse_event_mock = mocker.Mock(spec=parse_incoming_event)
        parse_event_mock.return_value = control_message
        mocker.patch('splitio.push.manager.parse_incoming_event',
                     new=parse_event_mock)

        status_tracker_mock = mocker.Mock(spec=PushStatusTracker)
        mocker.patch('splitio.push.manager.PushStatusTracker',
                     new=status_tracker_mock)

        manager = PushManager(mocker.Mock(), mocker.Mock(), mocker.Mock())
        manager._event_handler(sse_event)
        assert parse_event_mock.mock_calls == [mocker.call(sse_event)]
        assert status_tracker_mock.mock_calls == [
            mocker.call(),
            mocker.call().handle_control_message(control_message)
        ]
Exemple #9
0
class Manager(object):  # pylint:disable=too-many-instance-attributes
    """Manager Class."""

    _CENTINEL_EVENT = object()

    def __init__(self, ready_flag, synchronizer, auth_api, streaming_enabled, sdk_metadata, sse_url=None, client_key=None):  # pylint:disable=too-many-arguments
        """
        Construct Manager.

        :param ready_flag: Flag to set when splits initial sync is complete.
        :type ready_flag: threading.Event

        :param split_synchronizers: synchronizers for performing start/stop logic
        :type split_synchronizers: splitio.sync.synchronizer.Synchronizer

        :param auth_api: Authentication api client
        :type auth_api: splitio.api.auth.AuthAPI

        :param sdk_metadata: SDK version & machine name & IP.
        :type sdk_metadata: splitio.client.util.SdkMetadata

        :param streaming_enabled: whether to use streaming or not
        :type streaming_enabled: bool

        :param sse_url: streaming base url.
        :type sse_url: str

        :param client_key: client key.
        :type client_key: str
        """
        self._streaming_enabled = streaming_enabled
        self._ready_flag = ready_flag
        self._synchronizer = synchronizer
        if self._streaming_enabled:
            self._push_status_handler_active = True
            self._backoff = Backoff()
            self._queue = Queue()
            self._push = PushManager(auth_api, synchronizer, self._queue, sdk_metadata, sse_url, client_key)
            self._push_status_handler = Thread(target=self._streaming_feedback_handler,
                                               name='PushStatusHandler')
            self._push_status_handler.setDaemon(True)

    def recreate(self):
        """Recreate poolers for forked processes."""
        self._synchronizer._split_synchronizers._segment_sync.recreate()

    def start(self):
        """Start the SDK synchronization tasks."""
        try:
            self._synchronizer.sync_all()
            self._ready_flag.set()
            self._synchronizer.start_periodic_data_recording()
            if self._streaming_enabled:
                self._push_status_handler.start()
                self._push.start()
            else:
                self._synchronizer.start_periodic_fetching()

        except (APIException, RuntimeError):
            _LOGGER.error('Exception raised starting Split Manager')
            _LOGGER.debug('Exception information: ', exc_info=True)
            raise

    def stop(self, blocking):
        """
        Stop manager logic.

        :param blocking: flag to wait until tasks are stopped
        :type blocking: bool
        """
        _LOGGER.info('Stopping manager tasks')
        if self._streaming_enabled:
            self._push_status_handler_active = False
            self._queue.put(self._CENTINEL_EVENT)
            self._push.stop()
        self._synchronizer.shutdown(blocking)

    def _streaming_feedback_handler(self):
        """
        Handle status updates from the streaming subsystem.

        :param status: current status of the streaming pipeline.
        :type status: splitio.push.status_stracker.Status
        """
        while self._push_status_handler_active:
            status = self._queue.get()
            if status == self._CENTINEL_EVENT:
                continue

            if status == Status.PUSH_SUBSYSTEM_UP:
                self._synchronizer.stop_periodic_fetching()
                self._synchronizer.sync_all()
                self._push.update_workers_status(True)
                self._backoff.reset()
                _LOGGER.info('streaming up and running. disabling periodic fetching.')
            elif status == Status.PUSH_SUBSYSTEM_DOWN:
                self._push.update_workers_status(False)
                self._synchronizer.sync_all()
                self._synchronizer.start_periodic_fetching()
                _LOGGER.info('streaming temporarily down. starting periodic fetching')
            elif status == Status.PUSH_RETRYABLE_ERROR:
                self._push.update_workers_status(False)
                self._push.stop(True)
                self._synchronizer.sync_all()
                self._synchronizer.start_periodic_fetching()
                how_long = self._backoff.get()
                _LOGGER.info('error in streaming. restarting flow in %d seconds', how_long)
                time.sleep(how_long)
                self._push.start()
            elif status == Status.PUSH_NONRETRYABLE_ERROR:
                self._push.update_workers_status(False)
                self._push.stop(False)
                self._synchronizer.sync_all()
                self._synchronizer.start_periodic_fetching()
                _LOGGER.info('non-recoverable error in streaming. switching to polling.')
                return