diff --git a/app/cloudfoundry_config.py b/app/cloudfoundry_config.py index 84af68c74..d1418e1a0 100644 --- a/app/cloudfoundry_config.py +++ b/app/cloudfoundry_config.py @@ -16,6 +16,9 @@ def set_config_env_vars(vcap_services): # Postgres config os.environ['SQLALCHEMY_DATABASE_URI'] = vcap_services['postgres'][0]['credentials']['uri'].replace('postgres', 'postgresql') + # Redis config + if 'redis' in vcap_services: + os.environ['REDIS_URL'] = vcap_services['redis'][0]['credentials']['uri'] vcap_application = json.loads(os.environ['VCAP_APPLICATION']) os.environ['NOTIFY_ENVIRONMENT'] = vcap_application['space_name'] diff --git a/gunicorn_config.py b/gunicorn_config.py index 62223af54..c4844df4b 100644 --- a/gunicorn_config.py +++ b/gunicorn_config.py @@ -2,6 +2,8 @@ import os import sys import traceback import gunicorn +import eventlet +import socket from gds_metrics.gunicorn import child_exit # noqa @@ -30,3 +32,22 @@ def on_exit(server): def worker_int(worker): worker.log.info("worker: received SIGINT {}".format(worker.pid)) + + +def fix_ssl_monkeypatching(): + """ + eventlet works by monkey-patching core IO libraries (such as ssl) to be non-blocking. However, there's currently + a bug: In the normal socket library it may throw a timeout error as a `socket.timeout` exception. However + eventlet.green.ssl's patch raises an ssl.SSLError('timed out',) instead. redispy handles socket.timeout but not + ssl.SSLError, so we solve this by monkey patching the monkey patching code to raise the correct exception type + :scream: + + https://github.com/eventlet/eventlet/issues/692 + """ + # this has probably already been called somewhere in gunicorn internals, however, to be sure, we invoke it again. + # eventlet.monkey_patch can be called multiple times without issue + eventlet.monkey_patch() + eventlet.green.ssl.timeout_exc = socket.timeout + + +fix_ssl_monkeypatching() diff --git a/tests/app/test_cloudfoundry_config.py b/tests/app/test_cloudfoundry_config.py index 6a8a128e4..9013bd0bf 100644 --- a/tests/app/test_cloudfoundry_config.py +++ b/tests/app/test_cloudfoundry_config.py @@ -10,39 +10,38 @@ from app.cloudfoundry_config import ( @pytest.fixture -def postgres_config(): - return [ - { +def cloudfoundry_config(): + return { + 'postgres': [{ 'credentials': { 'uri': 'postgres uri' } - } - ] - - -@pytest.fixture -def cloudfoundry_config(postgres_config): - return { - 'postgres': postgres_config, + }], + 'redis': [{ + 'credentials': { + 'uri': 'redis uri' + } + }], 'user-provided': [] } @pytest.fixture -def cloudfoundry_environ(os_environ, cloudfoundry_config): - os.environ['VCAP_SERVICES'] = json.dumps(cloudfoundry_config) +def vcap_application(os_environ): os.environ['VCAP_APPLICATION'] = '{"space_name": "🚀🌌"}' -def test_extract_cloudfoundry_config_populates_other_vars(cloudfoundry_environ): +def test_extract_cloudfoundry_config_populates_other_vars(cloudfoundry_config, vcap_application): + os.environ['VCAP_SERVICES'] = json.dumps(cloudfoundry_config) extract_cloudfoundry_config() assert os.environ['SQLALCHEMY_DATABASE_URI'] == 'postgresql uri' + assert os.environ['REDIS_URL'] == 'redis uri' assert os.environ['NOTIFY_ENVIRONMENT'] == '🚀🌌' assert os.environ['NOTIFY_LOG_PATH'] == '/home/vcap/logs/app.log' -def test_set_config_env_vars_ignores_unknown_configs(cloudfoundry_config, cloudfoundry_environ): +def test_set_config_env_vars_ignores_unknown_configs(cloudfoundry_config, vcap_application): cloudfoundry_config['foo'] = {'credentials': {'foo': 'foo'}} cloudfoundry_config['user-provided'].append({ 'name': 'bar', 'credentials': {'bar': 'bar'} @@ -52,3 +51,9 @@ def test_set_config_env_vars_ignores_unknown_configs(cloudfoundry_config, cloudf assert 'foo' not in os.environ assert 'bar' not in os.environ + + +def test_set_config_env_vars_copes_if_redis_not_set(cloudfoundry_config, vcap_application): + del cloudfoundry_config['redis'] + set_config_env_vars(cloudfoundry_config) + assert 'REDIS_URL' not in os.environ