def test_receives_patch_events(): store = InMemoryFeatureStore() ready = Event() flagv1 = {'key': 'flagkey', 'version': 1} flagv2 = {'key': 'flagkey', 'version': 2} segmentv1 = {'key': 'segkey', 'version': 1} segmentv2 = {'key': 'segkey', 'version': 1} with start_server() as server: with stream_content(make_put_event([flagv1], [segmentv1])) as stream: config = Config(sdk_key='sdk-key', stream_uri=server.uri) server.for_path('/all', stream) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() ready.wait(start_wait) assert sp.initialized() expect_item(store, FEATURES, flagv1) expect_item(store, SEGMENTS, segmentv1) stream.push(make_patch_event(FEATURES, flagv2)) expect_update(store, FEATURES, flagv2) stream.push(make_patch_event(SEGMENTS, segmentv2)) expect_update(store, SEGMENTS, segmentv2)
def _make_update_processor(self, config, store, ready, diagnostic_accumulator): if config.update_processor_class: log.info("Using user-specified update processor: " + str(config.update_processor_class)) return config.update_processor_class(config, store, ready) if config.offline or config.use_ldd: return NullUpdateProcessor(config, store, ready) if config.feature_requester_class: feature_requester = config.feature_requester_class(config) else: feature_requester = FeatureRequesterImpl(config) """ :type: FeatureRequester """ if config.stream: return StreamingUpdateProcessor(config, feature_requester, store, ready, diagnostic_accumulator) log.info("Disabling streaming API") log.warning( "You should only disable the streaming API if instructed to do so by LaunchDarkly support" ) return PollingUpdateProcessor(config, feature_requester, store, ready)
def test_reconnects_if_stream_is_broken(): store = InMemoryFeatureStore() ready = Event() flagv1 = {'key': 'flagkey', 'version': 1} flagv2 = {'key': 'flagkey', 'version': 2} with start_server() as server: with stream_content(make_put_event([flagv1])) as stream1: with stream_content(make_put_event([flagv2])) as stream2: config = Config(sdk_key='sdk-key', stream_uri=server.uri, initial_reconnect_delay=brief_delay) server.for_path('/all', SequentialHandler(stream1, stream2)) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() server.await_request ready.wait(start_wait) assert sp.initialized() expect_item(store, FEATURES, flagv1) stream1.close() server.await_request expect_update(store, FEATURES, flagv2)
def __init__(self, sdk_key, config=None, start_wait=5): check_uwsgi() self._sdk_key = sdk_key self._config = config or Config.default() self._session = CacheControl(requests.Session()) self._queue = queue.Queue(self._config.events_max_pending) self._event_consumer = None self._lock = Lock() self._store = self._config.feature_store """ :type: FeatureStore """ if self._config.offline: self._config.events_enabled = False log.info("Started LaunchDarkly Client in offline mode") return if self._config.events_enabled: self._event_consumer = self._config.event_consumer_class( self._queue, self._sdk_key, self._config) self._event_consumer.start() if self._config.use_ldd: if self._store.__class__ == "RedisFeatureStore": log.info("Started LaunchDarkly Client in LDD mode") return log.error("LDD mode requires a RedisFeatureStore.") return if self._config.feature_requester_class: self._feature_requester = self._config.feature_requester_class( sdk_key, self._config) else: self._feature_requester = FeatureRequesterImpl(sdk_key, self._config) """ :type: FeatureRequester """ update_processor_ready = threading.Event() if self._config.update_processor_class: self._update_processor = self._config.update_processor_class( sdk_key, self._config, self._feature_requester, self._store, update_processor_ready) else: if self._config.stream: self._update_processor = StreamingUpdateProcessor( sdk_key, self._config, self._feature_requester, self._store, update_processor_ready) else: self._update_processor = PollingUpdateProcessor( sdk_key, self._config, self._feature_requester, self._store, update_processor_ready) """ :type: UpdateProcessor """ self._update_processor.start() log.info("Waiting up to " + str(start_wait) + " seconds for LaunchDarkly client to initialize...") update_processor_ready.wait(start_wait) if self._update_processor.initialized: log.info("Started LaunchDarkly Client: OK") else: log.info("Initialization timeout exceeded for LaunchDarkly Client. Feature Flags may not yet be available.")
def _verify_https_proxy_is_used(server, config): store = InMemoryFeatureStore() ready = Event() with stream_content(make_put_event()) as stream: server.for_path(config.stream_base_uri + '/all', stream) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() # Our simple stub server implementation can't really do HTTPS proxying, so the request will fail, but # it can still record that it *got* the request, which proves that the request went to the proxy. req = server.await_request() assert req.method == 'CONNECT'
def _verify_http_proxy_is_used(server, config): store = InMemoryFeatureStore() ready = Event() with stream_content(make_put_event()) as stream: server.for_path(config.stream_base_uri + '/all', stream) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() # For an insecure proxy request, our stub server behaves enough like the real thing to satisfy the # HTTP client, so we should be able to see the request go through. Note that the URI path will # actually be an absolute URI for a proxy request. req = server.await_request() assert req.method == 'GET' ready.wait(start_wait) assert sp.initialized()
def test_uses_stream_uri(): store = InMemoryFeatureStore() ready = Event() with start_server() as server: config = Config(sdk_key='sdk-key', stream_uri=server.uri) server.setup_response('/all', 200, fake_event, response_headers) with StreamingUpdateProcessor(config, None, store, ready, None) as sp: sp.start() req = server.await_request() assert req.method == 'GET' ready.wait(1) assert sp.initialized()
def test_sends_wrapper_header_without_version(): store = InMemoryFeatureStore() ready = Event() with start_server() as server: config = Config(sdk_key='sdk-key', stream_uri=server.uri, wrapper_name='Flask') server.setup_response('/all', 200, fake_event, response_headers) with StreamingUpdateProcessor(config, None, store, ready, None) as sp: sp.start() req = server.await_request() assert req.headers.get('X-LaunchDarkly-Wrapper') == 'Flask'
def test_sends_headers(): store = InMemoryFeatureStore() ready = Event() with start_server() as server: config = Config(sdk_key='sdk-key', stream_uri=server.uri) server.setup_response('/all', 200, fake_event, response_headers) with StreamingUpdateProcessor(config, None, store, ready, None) as sp: sp.start() req = server.await_request() assert req.headers.get('Authorization') == 'sdk-key' assert req.headers.get('User-Agent') == 'PythonClient/' + VERSION assert req.headers.get('X-LaunchDarkly-Wrapper') is None
def _stream_processor_proxy_test(server, config, secure): store = InMemoryFeatureStore() ready = Event() with stream_content(make_put_event()) as stream: server.for_path(config.stream_base_uri + '/all', stream) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() # Wait till the server has received a request. We need to do this even though do_proxy_tests also # does it, because if we return too soon out of this block, the object returned by stream_content # could be closed and the test server would no longer work. server.wait_until_request_received() if not secure: # We only do this part with HTTP, because with HTTPS we don't have a real enough proxy server # for the stream connection to work correctly - we can only detect the request. ready.wait(start_wait) assert sp.initialized()
def test_unrecoverable_http_error(status): error_handler = BasicResponse(status) store = InMemoryFeatureStore() ready = Event() with start_server() as server: with stream_content(make_put_event()) as stream: error_then_success = SequentialHandler(error_handler, stream) config = Config(sdk_key='sdk-key', stream_uri=server.uri, initial_reconnect_delay=brief_delay) server.for_path('/all', error_then_success) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() ready.wait(5) assert not sp.initialized() server.should_have_requests(1)
def test_sends_wrapper_header(): store = InMemoryFeatureStore() ready = Event() with start_server() as server: with stream_content(make_put_event()) as stream: config = Config(sdk_key='sdk-key', stream_uri=server.uri, wrapper_name='Flask', wrapper_version='0.1.0') server.for_path('/all', stream) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() req = server.await_request() assert req.headers.get( 'X-LaunchDarkly-Wrapper') == 'Flask/0.1.0'
def test_request_properties(): store = InMemoryFeatureStore() ready = Event() with start_server() as server: with stream_content(make_put_event()) as stream: config = Config(sdk_key='sdk-key', stream_uri=server.uri) server.for_path('/all', stream) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() req = server.await_request() assert req.method == 'GET' assert req.headers.get('Authorization') == 'sdk-key' assert req.headers.get( 'User-Agent') == 'PythonClient/' + VERSION assert req.headers.get('X-LaunchDarkly-Wrapper') is None
def test_records_diagnostic_on_stream_init_success(): store = InMemoryFeatureStore() ready = Event() with start_server() as server: with stream_content(make_put_event()) as stream: config = Config(sdk_key='sdk-key', stream_uri=server.uri) server.for_path('/all', stream) diag_accum = _DiagnosticAccumulator(1) with StreamingUpdateProcessor(config, store, ready, diag_accum) as sp: sp.start() ready.wait(start_wait) recorded_inits = diag_accum.create_event_and_reset( 0, 0)['streamInits'] assert len(recorded_inits) == 1 assert recorded_inits[0]['failed'] is False
def test_records_diagnostic_on_stream_init_failure(): store = InMemoryFeatureStore() ready = Event() with start_server() as server: config = Config(sdk_key='sdk-key', stream_uri=server.uri) server.setup_response('/all', 200, 'event:put\ndata: {\n\n', response_headers) diag_accum = _DiagnosticAccumulator(1) with StreamingUpdateProcessor(config, None, store, ready, diag_accum) as sp: sp.start() server.await_request() server.await_request() recorded_inits = diag_accum.create_event_and_reset( 0, 0)['streamInits'] assert recorded_inits[0]['failed'] is True
def test_retries_on_network_error(): error_handler = CauseNetworkError() store = InMemoryFeatureStore() ready = Event() with start_server() as server: with stream_content(make_put_event()) as stream: two_errors_then_success = SequentialHandler( error_handler, error_handler, stream) config = Config(sdk_key='sdk-key', stream_uri=server.uri, initial_reconnect_delay=brief_delay) server.for_path('/all', two_errors_then_success) with StreamingUpdateProcessor(config, store, ready, None) as sp: sp.start() ready.wait(start_wait) assert sp.initialized() server.await_request server.await_request
def test_records_diagnostic_on_stream_init_failure(): store = InMemoryFeatureStore() ready = Event() with start_server() as server: with stream_content(make_put_event()) as stream: error_then_success = SequentialHandler(BasicResponse(503), stream) config = Config(sdk_key='sdk-key', stream_uri=server.uri, initial_reconnect_delay=brief_delay) server.for_path('/all', error_then_success) diag_accum = _DiagnosticAccumulator(1) with StreamingUpdateProcessor(config, store, ready, diag_accum) as sp: sp.start() ready.wait(start_wait) recorded_inits = diag_accum.create_event_and_reset( 0, 0)['streamInits'] assert len(recorded_inits) == 2 assert recorded_inits[0]['failed'] is True assert recorded_inits[1]['failed'] is False
def __init__(self, sdk_key=None, config=None, start_wait=5): """Constructs a new LDClient instance. :param string sdk_key: the SDK key for your LaunchDarkly environment :param ldclient.config.Config config: optional custom configuration :param float start_wait: the number of seconds to wait for a successful connection to LaunchDarkly """ check_uwsgi() if config is not None and config.sdk_key is not None and sdk_key is not None: raise Exception( "LaunchDarkly client init received both sdk_key and config with sdk_key. " "Only one of either is expected") if sdk_key is not None: log.warn( "Deprecated sdk_key argument was passed to init. Use config object instead." ) self._config = Config(sdk_key=sdk_key) else: self._config = config or Config.default() self._config._validate() self._event_processor = None self._lock = Lock() self._store = _FeatureStoreClientWrapper(self._config.feature_store) """ :type: FeatureStore """ if self._config.offline or not self._config.send_events: self._event_processor = NullEventProcessor() else: self._event_processor = self._config.event_processor_class( self._config) if self._config.offline: log.info("Started LaunchDarkly Client in offline mode") return if self._config.use_ldd: log.info("Started LaunchDarkly Client in LDD mode") return update_processor_ready = threading.Event() if self._config.update_processor_class: log.info("Using user-specified update processor: " + str(self._config.update_processor_class)) self._update_processor = self._config.update_processor_class( self._config, self._store, update_processor_ready) else: if self._config.feature_requester_class: feature_requester = self._config.feature_requester_class( self._config) else: feature_requester = FeatureRequesterImpl(self._config) """ :type: FeatureRequester """ if self._config.stream: self._update_processor = StreamingUpdateProcessor( self._config, feature_requester, self._store, update_processor_ready) else: log.info("Disabling streaming API") log.warn( "You should only disable the streaming API if instructed to do so by LaunchDarkly support" ) self._update_processor = PollingUpdateProcessor( self._config, feature_requester, self._store, update_processor_ready) """ :type: UpdateProcessor """ self._update_processor.start() log.info("Waiting up to " + str(start_wait) + " seconds for LaunchDarkly client to initialize...") update_processor_ready.wait(start_wait) if self._update_processor.initialized() is True: log.info("Started LaunchDarkly Client: OK") else: log.warn( "Initialization timeout exceeded for LaunchDarkly Client or an error occurred. " "Feature Flags may not yet be available.")
def __init__(self, sdk_key=None, config=None, start_wait=5): check_uwsgi() if config is not None and config.sdk_key is not None and sdk_key is not None: raise Exception( "LaunchDarkly client init received both sdk_key and config with sdk_key. " "Only one of either is expected") if sdk_key is not None: log.warn( "Deprecated sdk_key argument was passed to init. Use config object instead." ) self._config = Config(sdk_key=sdk_key) else: self._config = config or Config.default() self._config._validate() self._session = CacheControl(requests.Session()) self._queue = queue.Queue(self._config.events_max_pending) self._event_consumer = None self._lock = Lock() self._store = self._config.feature_store """ :type: FeatureStore """ if self._config.offline: log.info("Started LaunchDarkly Client in offline mode") return if self._config.events_enabled: self._event_consumer = self._config.event_consumer_class( self._queue, self._config) self._event_consumer.start() if self._config.use_ldd: log.info("Started LaunchDarkly Client in LDD mode") return if self._config.feature_requester_class: self._feature_requester = self._config.feature_requester_class( self._config) else: self._feature_requester = FeatureRequesterImpl(self._config) """ :type: FeatureRequester """ update_processor_ready = threading.Event() if self._config.update_processor_class: log.info("Using user-specified update processor: " + str(self._config.update_processor_class)) self._update_processor = self._config.update_processor_class( self._config, self._feature_requester, self._store, update_processor_ready) else: if self._config.stream: self._update_processor = StreamingUpdateProcessor( self._config, self._feature_requester, self._store, update_processor_ready) else: self._update_processor = PollingUpdateProcessor( self._config, self._feature_requester, self._store, update_processor_ready) """ :type: UpdateProcessor """ self._update_processor.start() log.info("Waiting up to " + str(start_wait) + " seconds for LaunchDarkly client to initialize...") update_processor_ready.wait(start_wait) if self._update_processor.initialized() is True: log.info("Started LaunchDarkly Client: OK") else: log.warn( "Initialization timeout exceeded for LaunchDarkly Client or an error occurred. " "Feature Flags may not yet be available.")