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() ]
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) ]
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