Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Adding redis and hiredis to store OpenID association handles

Fixes #110
Alternative to memcached
  • Loading branch information...
commit 03356c3bcbb98e60520c9d8ff822c0432bc7b0c8 1 parent a7daf60
@ozten ozten authored
View
16 docs/MONITOR.md
@@ -1,14 +1,16 @@
Monitoring Notes
================
-Memcached
----------
+Redis
+-----
The following counters are set:
Association Handles:
-* assoc_store.memcached.get.ok - A handle was successfully retrieved
-* assoc_store.memcached.get.error - A memcache error occured, see logs
-* assoc_store.memcached.set.ok - A handle was successfully saved
-* assoc_store.memcached.set.error - A memcache error occured, see logs
-* assoc_store.memcached.set.failure - A memcache library error occured
+* assoc_store.redis.get.ok - A handle was successfully retrieved
+* assoc_store.redis.get.error - A redis error occured, see logs
+* assoc_store.redis.setandexpire.ok - A handle was successfully saved
+* assoc_store.redis.set.error - A redis SET error occured, see logs
+* assoc_store.redis.set.failure - A redis SET library error occured
+* assoc_store.redis.expire.error - A redis EXPIRE error occured, see logs
+* assoc_store.redis.expire.failure - A redis EXPIRE library error occured
View
14 docs/OPS_NOTES.md
@@ -68,17 +68,15 @@ BigTent will alter the environment variables to add the following:
These are used by lower level libraries during OpenID and OAuth flows.
-### Memcached Config
+### Redis Config
-Memcache is used to store association handles during OpenID transactions.
+Redis is used to store association handles during OpenID transactions.
-These association handles are cached based on the provider's expiration, which is usually around 12 seconds.
+These association handles are cached based on the provider's expiration, which is usually around 4 hours.
-It is best to have more than one memcached server running for failover.
-
-* memcached.ip_port_list - An array of strings. Each string is an ip address ':' and port number. Examples:
-
- "memcached.ip_port_list": ["10.0.3.14:11211", "10.0.3.15:11211"]
+* redis
+ * host - String hostname or IP of the Redis master (defaults to 127.0.0.1)
+ * port - Port Redis is running on (defaults to 6379)
Per Service Deployment
View
6 package.json
@@ -20,7 +20,7 @@
"express": "2.5.8",
"i18n-abide": "0.0.7",
"jwcrypto": "0.1.1",
- "memcached": "0.1.5",
+ "redis": "0.8.2",
"passport": "0.1.8",
"passport-google": "git://github.com/ozten/passport-google.git",
"passport-windowslive": "git://github.com/ozten/passport-windowslive.git",
@@ -39,7 +39,9 @@
"loady": "git://github.com/mozilla/loady.git",
"vows": "0.5.13"
},
- "optionalDependencies": {},
+ "optionalDependencies": {
+ "hiredis": "0.1.14"
+ },
"engines": {
"node": ">= 0.8.11"
}
View
5 server/config/local.json-dist
@@ -1,7 +1,8 @@
{
"browserid_server": "http://127.0.0.1:10002",
- "memcached": {
- "ip_port_list": ["127.0.0.1:11211", "10.0.10.13:11211"]
+ "redis": {
+ "host": "127.0.0.1",
+ "port": 6379
},
"certifier_host": "localhost",
"certifier_port": 8000,
View
71 server/lib/association_store.js
@@ -1,19 +1,33 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
var config = require('./configuration'),
logger = require('./logging').logger,
- Memcached = require('memcached'),
+ redis = require('redis'),
statsd = require('./statsd');
-var ip_ports = config.get('memcached.ip_port_list');
+var redisConf = config.get('redis');
+var client;
+
+function handleRedisError(err) {
+ logger.error('Redis error, re-connecting');
+ logger.error(err);
+}
+
+function initRedis() {
+ client = redis.createClient();
+ client.on('error', handleRedisError);
+}
+
+initRedis();
exports.health = function(cb) {
- var _m = new Memcached(ip_ports, {timeout: 1000, retry: 500, retries: 0});
- _m.connect(ip_ports, function(err, conn) {
- _m.end();
- if (err || ! conn) {
- cb(err || "Unable to connect to memcached");
+ client.ping(function(err, pong) {
+ if (err) {
+ cb(err);
+ } else if (pong !== 'PONG') {
+ cb('REDIS failed health check. Expected PONG got ' + JSON.stringify(pong));
} else {
cb(null);
}
@@ -30,21 +44,29 @@ exports.saveAssociation = function(handle, provider, algorithm, secret, expiresI
return cb("Bad input to saveAssociation");
}
- var lifetime = Math.round(expiresIn / 1000);
- var memcached = new Memcached(ip_ports, {timeout: 1000});
var value = JSON.stringify({provider: provider, algorithm: algorithm, secret: secret});
- memcached.set(handle, value, lifetime, function (err, ok) {
- memcached.end();
+ client.set(handle, value, function(err, reply) {
if (err) {
- statsd.increment('assoc_store.memcached.set.error');
+ statsd.increment('assoc_store.redis.set.error');
logger.error(err);
cb(err);
- } else if (! ok) {
- statsd.increment('assoc_store.memcached.set.failure');
- cb('Unable to set assoc handle in memcached');
+ } else if (! reply) {
+ statsd.increment('assoc_store.redis.set.failure');
+ cb('Unable to set assoc handle in redis');
} else {
- statsd.increment('assoc_store.memcached.set.ok');
- cb();
+ client.expire(handle, expiresIn, function(err, reply) {
+ if (err) {
+ statsd.increment('assoc_store.redis.expire.error');
+ logger.error(err);
+ cb(err);
+ } else if (! reply) {
+ statsd.increment('assoc_store.redis.expire.failure');
+ cb('Unable to set assoc handle in redis');
+ } else {
+ statsd.increment('assoc_store.redis.setandexpire.ok');
+ cb();
+ }
+ });
}
});
};
@@ -55,17 +77,22 @@ exports.loadAssociation = function(handle, cb) {
return cb("Bad input to loadAssociation");
}
- var memcached = new Memcached(ip_ports, {timeout: 1000});
- memcached.get(handle, function (err, res) {
- memcached.end();
+ client.get(handle, function(err, res) {
if (err) {
- statsd.increment('assoc_store.memcached.get.error');
+ statsd.increment('assoc_store.redis.get.error');
logger.error(err);
cb(err);
} else {
- statsd.increment('assoc_store.memcached.get.ok');
+ statsd.increment('assoc_store.redis.get.ok');
var data = JSON.parse(res);
cb(null, data.provider, data.algorithm, data.secret);
}
});
+};
+
+// Our Vows tests need this... but not anything else...
+exports.quit = function(cb) {
+ client.quit(function(err, reply) {
+ cb(err, reply);
+ });
};
View
5 server/lib/configuration.js
@@ -72,8 +72,9 @@ var conf = module.exports = convict({
format: 'string?',
env: 'VAR_PATH'
},
- memcached: {
- ip_port_list: 'array { string }* = ["127.0.0.1:11211"]'
+ redis: {
+ host: 'string = "127.0.0.1"',
+ port: 'integer = 6379'
},
windows_live: {
client_id: 'string = "00000000440BCC94"',
View
4 server/lib/startup_health_check.js
@@ -11,8 +11,8 @@ var config = require('../lib/configuration'),
module.exports = function () {
assocStore.health(function (err) {
if (err) {
- var ip_ports = config.get('memcached.ip_port_list');
- console.error('Unable to connect to memcached:' + JSON.stringify(ip_ports));
+ var redisConf = config.get('redis');
+ console.error('Unable to connect to redis:' + JSON.stringify(redisConf));
console.error(err);
}
var host = config.get('certifier_host'),
View
22 server/tests/association-store-test.js
@@ -12,14 +12,13 @@ util = require('util');
var desc = 'association-store-test';
-console.log('Doing health check with ', config.get('memcached.ip_port_list'));
assocStore.health(function(err) {
- console.log('Health check err=', err);
if (err) {
- var ip_ports = config.get('memcached.ip_port_list');
- console.error('Skipping ' + desc + ' test suite. Unable to connect to memcached:' + JSON.stringify(ip_ports));
+ var redisConf = config.get('redis');
+ console.error('Skipping ' + desc + ' test suite. Unable to connect to redis:' + JSON.stringify(redisConf));
console.error(err);
} else {
+
// TODO: This "optional vows suite" pattern doesn't work.
// You must run these test via node, not the vows bin. Fix Me.
setupAndRun();
@@ -98,11 +97,26 @@ var setupAndRun = function() {
[5, handle, provider, algorithm, secret, '17000'],
[6, expires, handle, provider, algorithm, secret]
];
+
badSaveTests.forEach(function(bad, i) {
suite.addBatch(badSaveBatch.apply(badSaveBatch, bad));
});
suite.addBatch(badLoadBatch(0, null));
+
+ suite.addBatch({
+ 'Shut down redis': {
+ topic: function (){
+ assocStore.quit(this.callback);
+
+ },
+ "all good": function (err, reply) {
+ assert.ifError(err);
+ assert.equal('OK', reply);
+ }
+ }
+ });
+
start_stop.addShutdownBatches(suite);
if (process.argv[1] === __filename) {
Please sign in to comment.
Something went wrong with that request. Please try again.