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)
예제 #2
0
    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)
예제 #4
0
    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()
예제 #7
0
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()
예제 #8
0
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'
예제 #9
0
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
예제 #10
0
 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
예제 #15
0
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
예제 #18
0
    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.")
예제 #19
0
    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.")