forked from moodle/moodle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add openlms Redis Cluster cache store
- Loading branch information
Showing
19 changed files
with
3,589 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
# RedisCluster Cache Store for Moodle™ | ||
|
||
A Moodle cache store plugin for [RedisCluster](https://redis.io/topics/cluster-tutorial). | ||
|
||
## Requirements | ||
|
||
* A Redis Cluster (version 4.0 or better) | ||
* [PhpRedis](https://github.com/phpredis/phpredis) extension (version 4.0 or better) | ||
* php-igbinary if using igbinary as the serialization method (recommended) | ||
|
||
## Features | ||
|
||
### Cache store | ||
|
||
This is the main use of this plugin - providing a way to use a Redis Cluster as a Moodle™ cache. | ||
|
||
Configurable options: | ||
* failover mode: how phpredis handles reads/writes, one of: | ||
- none | ||
- error (reads from a slave on error) | ||
- distribute (distributes reads over masters/slaves) | ||
* serializer: igbinary or php | ||
* compression: compresses data stored in redis - (de)compression occuring within phpredis | ||
|
||
### Session handler | ||
|
||
Moodle™ can be configured to use the Redis Cluster (or a different one) as the session store by setting: | ||
|
||
`$CFG->session_handler_class = '\cachestore_rediscluster\session';` | ||
|
||
Configuration options can be set with: | ||
|
||
``` | ||
$CFG->session_rediscluster = [ | ||
'server' => '192.168.1.100:6379', | ||
'serversecondary' => '192.168.1.101:6379:, | ||
'prefix' => "mdlsession_{$CFG->dbname}:", | ||
'acquire_lock_timeout' => 60, | ||
'lock_expire' => 600, | ||
'max_waiters' => 10, | ||
]; | ||
``` | ||
|
||
The only required setting in the above array is `server`. | ||
|
||
The following options govern session locking: | ||
|
||
* acquire_lock_timeout: How long to wait for a lock to be released before giving up | ||
* lock_expire: How long before a lock is released automatically | ||
* max_waiters: How many threads can a session have waiting for a lock | ||
|
||
Max waiters lets you define how many how many php threads a single user is allowed to have waiting for a session lock. Requests that don't take a session lock are unaffected. | ||
|
||
By default, max_waiters is set to 10. Set it to 0 to use default Moodle™ behaviour. | ||
|
||
Scripts with the `NO_SESSION_LOCK` define set to `true` ignore the max waiter behaviour. | ||
|
||
### auth_saml2 session handler | ||
|
||
If you use the `auth_saml2` plugin, you can configure it to use Redis Cluster for session storage by setting: | ||
|
||
`$CFG->auth_saml2_store = '\\cachestore_rediscluster\\auth_saml2_store';` | ||
|
||
Configuration options can then be set with: | ||
``` | ||
$CFG->auth_saml2_rediscluster = [ | ||
// The same options as are available in the standard session handler are supported here. | ||
]; | ||
``` | ||
|
||
## License | ||
Copyright (c) 2021 Open LMS (https://www.openlms.net) | ||
|
||
This program is free software: you can redistribute it and/or modify it under | ||
the terms of the GNU General Public License as published by the Free Software | ||
Foundation, either version 3 of the License, or (at your option) any later | ||
version. | ||
|
||
This program is distributed in the hope that it will be useful, but WITHOUT ANY | ||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A | ||
PARTICULAR PURPOSE. See the GNU General Public License for more details. | ||
|
||
You should have received a copy of the GNU General Public License along with | ||
this program. If not, see <http://www.gnu.org/licenses/>. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
/** | ||
* RedisCluster Cache Store - Add instance form | ||
* | ||
* @package cachestore_rediscluster | ||
* @copyright 2013 Adam Durana | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
|
||
defined('MOODLE_INTERNAL') || die(); | ||
|
||
require_once($CFG->dirroot.'/cache/forms.php'); | ||
|
||
/** | ||
* Form for adding instance of RedisCluster Cache Store. | ||
* | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class cachestore_rediscluster_addinstance_form extends cachestore_addinstance_form { | ||
/** | ||
* Builds the form for creating an instance. | ||
*/ | ||
protected function configuration_definition() { | ||
$form = $this->_form; | ||
|
||
if (!class_exists('RedisCluster')) { | ||
return; | ||
} | ||
|
||
$form->addElement('text', 'server', get_string('server', 'cachestore_rediscluster')); | ||
$form->addHelpButton('server', 'server', 'cachestore_rediscluster'); | ||
$form->addRule('server', get_string('required'), 'required'); | ||
$form->setType('server', PARAM_TEXT); | ||
|
||
$form->addElement('text', 'serversecondary', get_string('serversecondary', 'cachestore_rediscluster')); | ||
$form->addHelpButton('serversecondary', 'serversecondary', 'cachestore_rediscluster'); | ||
$form->addRule('serversecondary', get_string('required'), 'required'); | ||
$form->setType('serversecondary', PARAM_TEXT); | ||
|
||
$form->addElement('text', 'prefix', get_string('prefix', 'cachestore_rediscluster'), array('size' => 16)); | ||
$form->setType('prefix', PARAM_TEXT); // We set to text but we have a rule to limit to alphanumext. | ||
$form->addHelpButton('prefix', 'prefix', 'cachestore_rediscluster'); | ||
$form->addRule('prefix', get_string('prefixinvalid', 'cachestore_rediscluster'), 'regex', '#^[a-zA-Z0-9\-_]+$#'); | ||
|
||
$opts = [ | ||
RedisCluster::FAILOVER_NONE => get_string('failovernone', 'cachestore_rediscluster'), | ||
RedisCluster::FAILOVER_ERROR => get_string('failovererror', 'cachestore_rediscluster'), | ||
RedisCluster::FAILOVER_DISTRIBUTE => get_string('failoverdistribute', 'cachestore_rediscluster'), | ||
]; | ||
// Experimental setting, only add it if its available. | ||
// https://github.com/phpredis/phpredis/pull/1896 . | ||
if (defined('RedisCluster::FAILOVER_PREFERRED')) { | ||
$opts[RedisCluster::FAILOVER_PREFERRED] = get_string('failoverpreferred', 'cachestore_rediscluster'); | ||
} | ||
$form->addElement('select', 'failover', get_string('failover', 'cachestore_rediscluster'), $opts); | ||
$form->addHelpButton('failover', 'failover', 'cachestore_rediscluster'); | ||
$form->setDefault('failover', RedisCluster::FAILOVER_NONE); | ||
|
||
if (defined('RedisCluster::FAILOVER_PREFERRED')) { | ||
$form->addElement('text', 'preferrednodes', get_string('preferrednodes', 'cachestore_rediscluster')); | ||
$form->addHelpButton('preferrednodes', 'preferrednodes', 'cachestore_rediscluster'); | ||
$form->disabledIf('preferrednodes', 'failover', 'neq', RedisCluster::FAILOVER_PREFERRED); | ||
$form->setType('preferrednodes', PARAM_TEXT); | ||
$form->setDefault('preferrednodes', ''); | ||
} | ||
|
||
$form->addElement('checkbox', 'persist', get_string('persist', 'cachestore_rediscluster')); | ||
$form->addHelpButton('persist', 'persist', 'cachestore_rediscluster'); | ||
$form->setDefault('persist', 0); | ||
$form->setType('persist', PARAM_BOOL); | ||
|
||
$form->addElement('text', 'timeout', get_string('timeout', 'cachestore_rediscluster')); | ||
$form->addHelpButton('timeout', 'timeout', 'cachestore_rediscluster'); | ||
$form->setType('timeout', PARAM_FLOAT); | ||
$form->setDefault('timeout', '3.0'); | ||
|
||
$form->addElement('text', 'readtimeout', get_string('readtimeout', 'cachestore_rediscluster')); | ||
$form->addHelpButton('readtimeout', 'readtimeout', 'cachestore_rediscluster'); | ||
$form->setType('readtimeout', PARAM_FLOAT); | ||
$form->setDefault('readtimeout', '3.0'); | ||
|
||
$opts = [ | ||
Redis::SERIALIZER_PHP => get_string('serializerphp', 'cachestore_rediscluster'), | ||
Redis::SERIALIZER_IGBINARY => get_string('serializerigbinary', 'cachestore_rediscluster'), | ||
]; | ||
$form->addElement('select', 'serializer', get_string('serializer', 'cachestore_rediscluster'), $opts); | ||
$form->addHelpButton('serializer', 'serializer', 'cachestore_rediscluster'); | ||
$form->setDefault('serializer', Redis::SERIALIZER_PHP); | ||
|
||
$opts = [ | ||
Redis::COMPRESSION_NONE => get_string('compressionnone', 'cachestore_rediscluster'), | ||
]; | ||
|
||
if (defined('Redis::COMPRESSION_LZ4')) { | ||
$opts[Redis::COMPRESSION_LZ4] = get_string('compressionlz4', 'cachestore_rediscluster'); | ||
} | ||
|
||
if (defined('Redis::COMPRESSION_LZF')) { | ||
$opts[Redis::COMPRESSION_LZF] = get_string('compressionlzf', 'cachestore_rediscluster'); | ||
} | ||
if (defined('Redis::COMPRESSION_ZSTD')) { | ||
$opts[Redis::COMPRESSION_ZSTD] = get_string('compressionzstd', 'cachestore_rediscluster'); | ||
} | ||
$form->addElement('select', 'compression', get_string('compression', 'cachestore_rediscluster'), $opts); | ||
$form->addHelpButton('compression', 'compression', 'cachestore_rediscluster'); | ||
$form->setDefault('compression', Redis::COMPRESSION_NONE); | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
<?php | ||
// This file is part of Moodle - http://moodle.org/ | ||
// | ||
// Moodle is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU General Public License as published by | ||
// the Free Software Foundation, either version 3 of the License, or | ||
// (at your option) any later version. | ||
// | ||
// Moodle is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU General Public License | ||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
/** | ||
* RedisCluster store simpleSAMLphp class for auth/saml2. | ||
* | ||
* @package cachestore_rediscluster | ||
* @author Adam Olley | ||
* @copyright Copyright (c) 2021 Open LMS (https://www.openlms.net) | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
|
||
namespace cachestore_rediscluster; | ||
|
||
defined('MOODLE_INTERNAL') || die(); | ||
|
||
require_once($CFG->dirroot . '/auth/saml2/extlib/simplesamlphp/lib/SimpleSAML/Store.php'); | ||
|
||
/** | ||
* RedisCluster store simpleSAMLphp class for auth/saml2. | ||
* | ||
* @package cachestore_rediscluster | ||
* @copyright Copyright (c) 2021 Open LMS (https://www.openlms.net) | ||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | ||
*/ | ||
class auth_saml2_store extends \SimpleSAML\Store { | ||
|
||
/** | ||
* The connection config for RedisCluster. | ||
* | ||
* @var string | ||
*/ | ||
protected $config; | ||
|
||
/** | ||
* The RedisCluster cachestore object. | ||
* | ||
* @var cachestore_rediscluster | ||
*/ | ||
protected $connection = null; | ||
|
||
/** | ||
* Create new instance of handler. | ||
*/ | ||
public function __construct() { | ||
global $CFG; | ||
|
||
$this->config = [ | ||
'compression' => \Redis::COMPRESSION_NONE, | ||
'failover' => \RedisCluster::FAILOVER_NONE, | ||
'persist' => false, | ||
'preferrednodes' => null, | ||
'prefix' => 'simpleSAMLphp.' . $CFG->dbname . '.', | ||
'readtimeout' => 3.0, | ||
'serializer' => \Redis::SERIALIZER_PHP, | ||
'server' => null, | ||
'serversecondary' => null, | ||
'session' => false, | ||
'timeout' => 3.0, | ||
]; | ||
|
||
foreach (array_keys($this->config) as $key) { | ||
if (!empty($CFG->auth_saml2_rediscluster[$key])) { | ||
$this->config[$key] = $CFG->auth_saml2_rediscluster[$key]; | ||
} | ||
} | ||
|
||
if (!$this->init()) { | ||
throw new \coding_exception("Could not configure rediscluster for auth_saml2"); | ||
} | ||
} | ||
|
||
/** | ||
* Init connection. | ||
*/ | ||
protected function init() { | ||
global $CFG; | ||
|
||
require_once("{$CFG->dirroot}/cache/stores/rediscluster/lib.php"); | ||
|
||
if (!extension_loaded('redis') || empty($this->config['server']) || !class_exists('RedisCluster')) { | ||
return false; | ||
} | ||
|
||
$this->connection = new \cachestore_rediscluster(null, $this->config); | ||
return true; | ||
} | ||
|
||
/** | ||
* @param string $type | ||
* @param string $key | ||
* @param mixed $value | ||
* @param int|null $expire | ||
*/ | ||
public function set($type, $key, $value, $expire = null) { | ||
$now = time(); | ||
if ($expire !== null && $expire > $now) { | ||
$this->connection->command('setex', $this->make_key($type, $key), $expire - $now, $value); | ||
} else { | ||
$this->connection->command('set', $this->make_key($type, $key), $value); | ||
} | ||
} | ||
|
||
/** | ||
* @param string $type | ||
* @param string $key | ||
* @return mixed|null | ||
*/ | ||
public function get($type, $key) { | ||
$value = $this->connection->command('get', $this->make_key($type, $key)); | ||
if ($value === false) { | ||
$value = null; | ||
} | ||
|
||
return $value; | ||
} | ||
|
||
/** | ||
* @param string $type | ||
* @param string $key | ||
*/ | ||
public function delete($type, $key) { | ||
$this->connection->command('unlink', $this->make_key($type, $key)); | ||
} | ||
|
||
/** | ||
* @param string $type | ||
* @param string $key | ||
* @return string | ||
*/ | ||
protected function make_key($type, $key) { | ||
return $type . '.' . $key; | ||
} | ||
} |
Oops, something went wrong.