Skip to content

Commit

Permalink
Added extension to configure redis as a persistent store for PHP sess…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
Daniel Mikusa authored and davidjahn committed Apr 11, 2016
1 parent 24b37fa commit 2e4148a
Show file tree
Hide file tree
Showing 6 changed files with 340 additions and 0 deletions.
Empty file added extensions/sessions/__init__.py
Empty file.
101 changes: 101 additions & 0 deletions extensions/sessions/extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Session Config Extension
Configures redis for session sharing
"""
from extension_helpers import PHPExtensionHelper


class BaseSetup(object):
def __init__(self, ctx, info):
self._ctx = ctx
self._info = info
self.creds = self._info.get('credentials', {})

def session_store_key(self):
key_name = self.DEFAULT_SESSION_STORE_TRIGGER
if self.CUSTOM_SESSION_STORE_KEY_NAME in self._ctx:
key_name = self._ctx[self.CUSTOM_SESSION_STORE_KEY_NAME]
return key_name

def custom_config_php_ini(self, php_ini):
pass


class RedisSetup(BaseSetup):
DEFAULT_SESSION_STORE_TRIGGER = 'redis-sessions'
CUSTOM_SESSION_STORE_KEY_NAME = 'REDIS_SESSION_STORE_SERVICE_NAME'
EXTENSION_NAME = 'redis'

def __init__(self, ctx, info):
BaseSetup.__init__(self, ctx, info)

def session_save_path(self):
return "tcp://%s:%s?auth=%s" % (
self.creds.get('hostname',
self.creds.get('host', 'not-found')),
self.creds.get('port', 'not-found'),
self.creds.get('password', ''))


class SessionStoreConfig(PHPExtensionHelper):
def __init__(self, ctx):
PHPExtensionHelper.__init__(self, ctx)
self.service = None

def _should_compile(self):
if self.service is None:
self.service = self._load_session()
return self.service is not None

def _load_session(self):
# load search keys
session_types = [
RedisSetup,
]
# search for an appropriately name session store
vcap_services = self._ctx.get('VCAP_SERVICES', {})
for provider, services in vcap_services.iteritems():
for service in services:
service_name = service.get('name', '')
for session_type in session_types:
session = session_type(self._ctx, service)
if service_name.find(session.session_store_key()) != -1:
return session

def _configure(self):
# load the PHP extension that provides session save handler
if self.service is not None:
self._ctx.get('PHP_EXTENSIONS',
[]).append(self.service.EXTENSION_NAME)

def _compile(self, install):
# modify php.ini to contain the right session config
self.load_config()
self._php_ini.update_lines(
'^session\.name = JSESSIONID$',
'session.name = PHPSESSIONID')
self._php_ini.update_lines(
'^session\.save_handler = files$',
'session.save_handler = %s' % self.service.EXTENSION_NAME)
self._php_ini.update_lines(
'^session\.save_path = "@{TMPDIR}"$',
'session.save_path = "%s"' % self.service.session_save_path())
self.service.custom_config_php_ini(self._php_ini)
self._php_ini.save(self._php_ini_path)


SessionStoreConfig.register(__name__)
51 changes: 51 additions & 0 deletions tests/data/sessions/vcap_services_alt_name.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"VCAP_SERVICES": {
"newrelic": [
{
"credentials": {
"licenseKey": "newrelic-key"
},
"label": "newrelic",
"name": "newrelic",
"plan": "standard",
"tags": [
"Monitoring"
]
}
],
"redis": [
{
"credentials": {
"host": "redis-host",
"password": "redis-pass",
"port": 45629
},
"label": "redis",
"name": "php-session-db",
"plan": "shared-vm",
"tags": [
"pivotal",
"redis"
]
}
],
"sendgrid": [
{
"credentials": {
"hostname": "smtp.sendgrid.net",
"password": "sendgrid-pass",
"username": "sendgrid-user"
},
"label": "sendgrid",
"name": "sendgrid",
"plan": "free",
"tags": [
"Retail",
"Email",
"smtp",
"Inventory management"
]
}
]
}
}
51 changes: 51 additions & 0 deletions tests/data/sessions/vcap_services_redis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"VCAP_SERVICES": {
"newrelic": [
{
"credentials": {
"licenseKey": "newrelic-key"
},
"label": "newrelic",
"name": "newrelic",
"plan": "standard",
"tags": [
"Monitoring"
]
}
],
"redis": [
{
"credentials": {
"host": "redis-host",
"password": "redis-pass",
"port": 45629
},
"label": "redis",
"name": "redis-sessions",
"plan": "shared-vm",
"tags": [
"pivotal",
"redis"
]
}
],
"sendgrid": [
{
"credentials": {
"hostname": "smtp.sendgrid.net",
"password": "sendgrid-pass",
"username": "sendgrid-user"
},
"label": "sendgrid",
"name": "sendgrid",
"plan": "free",
"tags": [
"Retail",
"Email",
"smtp",
"Inventory management"
]
}
]
}
}
52 changes: 52 additions & 0 deletions tests/data/sessions/vcap_services_with_redis_not_for_sessions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"VCAP_SERVICES": {
"newrelic": [
{
"credentials": {
"licenseKey": "newrelic-key"
},
"label": "newrelic",
"name": "newrelic",
"plan": "standard",
"tags": [
"Monitoring"
]
}
],
"redis": [
{
"credentials": {
"host": "redis-host",
"password": "redis-pass",
"port": 45629
},
"label": "redis",
"name": "p-redis-db",
"plan": "shared-vm",
"tags": [
"pivotal",
"redis"
]
}
],
"sendgrid": [
{
"credentials": {
"hostname": "smtp.sendgrid.net",
"password": "sendgrid-pass",
"username": "sendgrid-user"
},
"label": "sendgrid",
"name": "sendgrid",
"plan": "free",
"tags": [
"Retail",
"Email",
"smtp",
"Inventory management"
]
}
]
}
}

85 changes: 85 additions & 0 deletions tests/test_sessions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
from dingus import Dingus
from nose.tools import eq_
from build_pack_utils import utils


class TestSessions(object):

def __init__(self):
self.extension_module = utils.load_extension('extensions/sessions')

def test_load_session_name_contains_redis(self):
ctx = json.load(open('tests/data/sessions/vcap_services_redis.json'))
sessions = self.extension_module.SessionStoreConfig(ctx)
eq_(self.extension_module.RedisSetup, type(sessions._load_session()))

def test_load_session_no_service(self):
sessions = self.extension_module.SessionStoreConfig({})
eq_(None, sessions._load_session())

def test_alt_name_logic_redis(self):
redis = self.extension_module.RedisSetup({}, {})
eq_('redis-sessions', redis.session_store_key())
redis = self.extension_module.RedisSetup({
'REDIS_SESSION_STORE_SERVICE_NAME': 'my-redis-db'
}, {})
eq_('my-redis-db', redis.session_store_key())

def test_load_session_alt_name(self):
ctx = json.load(
open('tests/data/sessions/vcap_services_alt_name.json'))
sessions = self.extension_module.SessionStoreConfig(ctx)
eq_(None, sessions._load_session())
ctx['REDIS_SESSION_STORE_SERVICE_NAME'] = 'php-session-db'
eq_(self.extension_module.RedisSetup, type(sessions._load_session()))

def test_should_compile(self):
sessions = self.extension_module.SessionStoreConfig({})
sessions._load_session = Dingus(return_value=object())
eq_(True, sessions._should_compile())

def test_load_session_redis_but_not_for_sessions(self):
ctx = json.load(open('tests/data/sessions/'
'vcap_services_with_redis_not_for_sessions.json'))
sessions = self.extension_module.SessionStoreConfig(ctx)
eq_(None, sessions._load_session())

def test_configure_adds_redis_extension(self):
ctx = json.load(open('tests/data/sessions/vcap_services_redis.json'))
ctx['PHP_EXTENSIONS'] = []
sessions = self.extension_module.SessionStoreConfig(ctx)
sessions._php_ini = Dingus()
sessions.configure()
eq_(True, 'redis' in ctx['PHP_EXTENSIONS'])

def test_configure_adds_redis_config_to_php_ini(self):
ctx = json.load(open('tests/data/sessions/vcap_services_redis.json'))
sessions = self.extension_module.SessionStoreConfig(ctx)
sessions.load_config = Dingus()
php_ini = Dingus()
sessions._php_ini = php_ini
sessions._php_ini_path = '/tmp/staged/app/php/etc/php.ini'
sessions.compile(None)
eq_(1, len(sessions.load_config.calls()))
eq_(3, len(php_ini.update_lines.calls()))
eq_(1, len(php_ini.save.calls()))
eq_(4, len(php_ini.calls()))
eq_('session.save_handler = redis',
php_ini.update_lines.calls()[1].args[1])
eq_('session.save_path = "tcp://redis-host:45629?auth=redis-pass"',
php_ini.update_lines.calls()[2].args[1])

0 comments on commit 2e4148a

Please sign in to comment.