mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 04:22:07 +02:00
MDL-63128 cachestore_redis: Support Redis cluster as cache store
Co-authored-by: Daniel Thee Roperto <daniel@theeroperto.com> Co-authored-by: Avi Levy <avi@sysbind.co.il>
This commit is contained in:
parent
39b8e198ff
commit
6a28e0641e
9
cache/stores/redis/addinstanceform.php
vendored
9
cache/stores/redis/addinstanceform.php
vendored
@ -24,7 +24,7 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot.'/cache/forms.php');
|
||||
require_once($CFG->dirroot . '/cache/forms.php');
|
||||
|
||||
/**
|
||||
* Form for adding instance of Redis Cache Store.
|
||||
@ -39,7 +39,12 @@ class cachestore_redis_addinstance_form extends cachestore_addinstance_form {
|
||||
protected function configuration_definition() {
|
||||
$form = $this->_form;
|
||||
|
||||
$form->addElement('text', 'server', get_string('server', 'cachestore_redis'), array('size' => 24));
|
||||
$form->addElement('advcheckbox', 'clustermode', get_string('clustermode', 'cachestore_redis'), '',
|
||||
cache_helper::is_cluster_available() ? '' : 'disabled');
|
||||
$form->addHelpButton('clustermode', 'clustermode', 'cachestore_redis');
|
||||
$form->setType('clustermode', PARAM_BOOL);
|
||||
|
||||
$form->addElement('textarea', 'server', get_string('server', 'cachestore_redis'), ['cols' => 6, 'rows' => 10]);
|
||||
$form->setType('server', PARAM_TEXT);
|
||||
$form->addHelpButton('server', 'server', 'cachestore_redis');
|
||||
$form->addRule('server', get_string('required'), 'required');
|
||||
|
42
cache/stores/redis/lang/en/cachestore_redis.php
vendored
42
cache/stores/redis/lang/en/cachestore_redis.php
vendored
@ -24,13 +24,18 @@
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['ca_file'] = 'CA file path';
|
||||
$string['ca_file_help'] = 'Location of Certificate Authority file on local filesystem';
|
||||
$string['clustermode'] = 'Cluster Mode';
|
||||
$string['clustermode_help'] = 'Enabling it will run the Redis cluster function, allowing your server to serve multiple servers to handle concurrent requests simultaneously.';
|
||||
$string['clustermodeunavailable'] = 'Redis Cluster is currently unavailable. Please ensure that the PHP Redis extension supports Redis Cluster functionality.';
|
||||
$string['compressor_none'] = 'No compression.';
|
||||
$string['compressor_php_gzip'] = 'Use gzip compression.';
|
||||
$string['compressor_php_zstd'] = 'Use Zstandard compression.';
|
||||
$string['encrypt_connection'] = 'Use TLS encryption.';
|
||||
$string['encrypt_connection_help'] = 'Use TLS to connect to Redis. Do not use \'tls://\' in the hostname for Redis, use this option instead.';
|
||||
$string['ca_file'] = 'CA file path';
|
||||
$string['ca_file_help'] = 'Location of Certificate Authority file on local filesystem';
|
||||
$string['password'] = 'Password';
|
||||
$string['password_help'] = 'This sets the password of the Redis server.';
|
||||
$string['pluginname'] = 'Redis';
|
||||
$string['prefix'] = 'Key prefix';
|
||||
$string['prefix_help'] = 'This prefix is used for all key names on the Redis server.
|
||||
@ -41,8 +46,8 @@ $string['privacy:metadata:redis'] = 'The Redis cachestore plugin stores data bri
|
||||
$string['privacy:metadata:redis:data'] = 'The various data stored in the cache';
|
||||
$string['serializer_igbinary'] = 'The igbinary serializer.';
|
||||
$string['serializer_php'] = 'The default PHP serializer.';
|
||||
$string['server'] = 'Server';
|
||||
$string['server_help'] = 'This sets the hostname, IP address or Unix socket path of the Redis server to use.
|
||||
$string['server'] = 'Server(s)';
|
||||
$string['server_help'] = 'Redis server to use for testing.
|
||||
|
||||
Some example values:
|
||||
|
||||
@ -52,12 +57,20 @@ Some example values:
|
||||
* 1.2.3.4:1234 - To connect to a Redis server by IP address with a specific port.
|
||||
* unix:///var/redis.sock - To connect to a Redis server using a Unix socket.
|
||||
* /var/redis.sock - To connect to a Redis server using a Unix socket (alternative format).
|
||||
* If cluster mode is enabled, please specify servers separated by a new line:<br>
|
||||
172.23.0.11<br>
|
||||
172.23.0.12<br>
|
||||
172.23.0.13<br>
|
||||
Refer to the above examples to write a server.
|
||||
|
||||
See <a href="https://redis.io/docs/reference/clients/#accepting-client-connections" target="_new">Accepting Client Connections</a> and <a href="https://redis.io/resources/clients/#php" target="_new">Redis PHP clients</a> for more information.
|
||||
';
|
||||
$string['password'] = 'Password';
|
||||
$string['password_help'] = 'This sets the password of the Redis server.';
|
||||
See <a href="https://redis.io/docs/reference/clients/#accepting-client-connections" target="_new">Accepting Client Connections</a> and <a href="https://redis.io/resources/clients/#php" target="_new">Redis PHP clients</a> for more information.';
|
||||
$string['task_ttl'] = 'Free up memory used by expired entries in Redis caches';
|
||||
$string['test_clustermode'] = 'Cluster Mode';
|
||||
$string['test_clustermode_desc'] = 'Enable Test in Redis cluster mode.';
|
||||
$string['test_password'] = 'Test server password';
|
||||
$string['test_password_desc'] = 'Redis test server password.';
|
||||
$string['test_serializer'] = 'Serializer';
|
||||
$string['test_serializer_desc'] = 'Serializer to use for testing.';
|
||||
$string['test_server'] = 'Test server';
|
||||
$string['test_server_desc'] = 'Redis server to use for testing.
|
||||
|
||||
@ -69,17 +82,18 @@ Some example values:
|
||||
* 1.2.3.4:1234 - To connect to a Redis server by IP address with a specific port.
|
||||
* unix:///var/redis.sock - To connect to a Redis server using a Unix socket.
|
||||
* /var/redis.sock - To connect to a Redis server using a Unix socket (alternative format).
|
||||
* If cluster mode is enabled, please specify servers separated by a new line:<br>
|
||||
172.23.0.11<br>
|
||||
172.23.0.12<br>
|
||||
172.23.0.13<br>
|
||||
Refer to the above examples to write a server.
|
||||
|
||||
See <a href="https://redis.io/docs/reference/clients/#accepting-client-connections" target="_new">Accepting Client Connections</a> and <a href="https://redis.io/resources/clients/#php" target="_new">Redis PHP clients</a> for more information.';
|
||||
$string['test_password'] = 'Test server password';
|
||||
$string['test_password_desc'] = 'Redis test server password.';
|
||||
$string['test_serializer'] = 'Serializer';
|
||||
$string['test_serializer_desc'] = 'Serializer to use for testing.';
|
||||
$string['test_ttl'] = 'Testing TTL';
|
||||
$string['test_ttl_desc'] = 'Run the performance test using a cache that requires TTL (slower sets).';
|
||||
$string['usecompressor'] = 'Use compressor';
|
||||
$string['usecompressor_help'] = 'Specifies the compressor to use after serializing. It is done at Moodle Cache API level, not at php-redis level.';
|
||||
$string['useserializer'] = 'Use serializer';
|
||||
$string['useserializer_help'] = 'Specifies the serializer to use for serializing.
|
||||
The valid serializers are Redis::SERIALIZER_PHP or Redis::SERIALIZER_IGBINARY.
|
||||
The latter is supported only when phpredis is configured with --enable-redis-igbinary option and the igbinary extension is loaded.';
|
||||
$string['usecompressor'] = 'Use compressor';
|
||||
$string['usecompressor_help'] = 'Specifies the compressor to use after serializing. It is done at Moodle Cache API level, not at php-redis level.';
|
||||
|
165
cache/stores/redis/lib.php
vendored
165
cache/stores/redis/lib.php
vendored
@ -94,7 +94,7 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
/**
|
||||
* Connection to Redis for this store.
|
||||
*
|
||||
* @var Redis
|
||||
* @var Redis|RedisCluster
|
||||
*/
|
||||
protected $redis;
|
||||
|
||||
@ -177,7 +177,10 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
* @param string $name
|
||||
* @param array $configuration
|
||||
*/
|
||||
public function __construct($name, array $configuration = array()) {
|
||||
public function __construct(
|
||||
$name,
|
||||
array $configuration = [],
|
||||
) {
|
||||
$this->name = $name;
|
||||
|
||||
if (!array_key_exists('server', $configuration) || empty($configuration['server'])) {
|
||||
@ -199,75 +202,112 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Redis instance and
|
||||
* connect to the server.
|
||||
* Create a new Redis or RedisCluster instance and connect to the server.
|
||||
*
|
||||
* @param array $configuration The server configuration
|
||||
* @return Redis
|
||||
* @param array $configuration The redis instance configuration.
|
||||
* @return Redis|RedisCluster|null
|
||||
*/
|
||||
protected function new_redis(array $configuration): \Redis {
|
||||
global $CFG;
|
||||
|
||||
$redis = new Redis();
|
||||
|
||||
$server = $configuration['server'];
|
||||
protected function new_redis(array $configuration): Redis|RedisCluster|null {
|
||||
$encrypt = (bool) ($configuration['encryption'] ?? false);
|
||||
$clustermode = (bool) ($configuration['clustermode'] ?? false);
|
||||
$password = !empty($configuration['password']) ? $configuration['password'] : '';
|
||||
$prefix = !empty($configuration['prefix']) ? $configuration['prefix'] : '';
|
||||
// Check if it isn't a Unix socket to set default port.
|
||||
$port = null;
|
||||
$opts = [];
|
||||
// Unix sockets can start with / or with unix://.
|
||||
if ($server[0] === '/' || strpos($server, 'unix://') === 0) {
|
||||
$port = 0;
|
||||
} else {
|
||||
$port = 6379; // No Unix socket so set default port.
|
||||
if (strpos($server, ':')) { // Check for custom port.
|
||||
list($server, $port) = explode(':', $server);
|
||||
}
|
||||
|
||||
// We can encrypt if we aren't unix socket.
|
||||
if ($encrypt) {
|
||||
$server = 'tls://' . $server;
|
||||
if (empty($configuration['cafile'])) {
|
||||
$sslopts = [
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
];
|
||||
// Set Redis server(s).
|
||||
$servers = explode("\n", $configuration['server']);
|
||||
$trimmedservers = [];
|
||||
// print_r($configuration);
|
||||
// print_r($servers);
|
||||
foreach ($servers as $server) {
|
||||
$server = strtolower(trim($server));
|
||||
if (!empty($server)) {
|
||||
if ($server[0] === '/' || str_starts_with($server, 'unix://')) {
|
||||
$port = 0;
|
||||
$trimmedservers[] = $server;
|
||||
} else {
|
||||
$sslopts = ['cafile' => $configuration['cafile']];
|
||||
$port = 6379; // No Unix socket so set default port.
|
||||
if (strpos($server, ':')) { // Check for custom port.
|
||||
list($server, $port) = explode(':', $server);
|
||||
}
|
||||
if (!$clustermode && $encrypt) {
|
||||
$server = 'tls://' . $server;
|
||||
}
|
||||
$trimmedservers[] = $server.':'.$port;
|
||||
}
|
||||
|
||||
// We only need the first record for the single redis.
|
||||
if (!$clustermode) {
|
||||
break;
|
||||
}
|
||||
$opts['stream'] = $sslopts;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if ($redis->connect($server, $port, 1, null, 100, 1, $opts)) {
|
||||
// TLS/SSL Configuration.
|
||||
$exceptionclass = $clustermode ? 'RedisClusterException' : 'RedisException';
|
||||
$opts = [];
|
||||
if ($encrypt) {
|
||||
$opts = empty($configuration['cafile']) ?
|
||||
['verify_peer' => false, 'verify_peer_name' => false] :
|
||||
['cafile' => $configuration['cafile']];
|
||||
|
||||
// For a single (non-cluster) Redis, the TLS/SSL config must be added to the 'stream' key.
|
||||
if (!$clustermode) {
|
||||
$opts['stream'] = $opts;
|
||||
}
|
||||
}
|
||||
// Connect to redis.
|
||||
$redis = null;
|
||||
// print_r($trimmedservers);
|
||||
// exit;
|
||||
try {
|
||||
// Create a $redis object of a RedisCluster or Redis class.
|
||||
if ($clustermode) {
|
||||
$redis = new RedisCluster(
|
||||
name: null,
|
||||
seeds: $trimmedservers,
|
||||
timeout: 1,
|
||||
read_timeout: 1,
|
||||
persistent: true,
|
||||
auth: $password,
|
||||
context: !empty($opts) ? $opts : null,
|
||||
);
|
||||
} else {
|
||||
// We only need the first record for the single redis.
|
||||
list($server, $port) = explode(':', $trimmedservers[0]);
|
||||
$redis = new Redis();
|
||||
$redis->connect(
|
||||
host: $server,
|
||||
port: $port,
|
||||
timeout: 1,
|
||||
retry_interval: 100,
|
||||
read_timeout: 1,
|
||||
context: $opts,
|
||||
);
|
||||
if (!empty($password)) {
|
||||
$redis->auth($password);
|
||||
}
|
||||
// If using compressor, serialisation will be done at cachestore level, not php-redis.
|
||||
if ($this->compressor == self::COMPRESSOR_NONE) {
|
||||
$redis->setOption(Redis::OPT_SERIALIZER, $this->serializer);
|
||||
}
|
||||
if (!empty($prefix)) {
|
||||
$redis->setOption(Redis::OPT_PREFIX, $prefix);
|
||||
}
|
||||
if ($encrypt && !$redis->ping()) {
|
||||
/*
|
||||
* In case of a TLS connection, if phpredis client does not
|
||||
* communicate immediately with the server the connection hangs.
|
||||
* See https://github.com/phpredis/phpredis/issues/2332 .
|
||||
*/
|
||||
throw new \RedisException("Ping failed");
|
||||
}
|
||||
$this->isready = true;
|
||||
} else {
|
||||
$this->isready = false;
|
||||
}
|
||||
} catch (\RedisException $e) {
|
||||
debugging("redis $server: $e", DEBUG_NORMAL);
|
||||
|
||||
// In case of a TLS connection,
|
||||
// if phpredis client does not communicate immediately with the server the connection hangs.
|
||||
// See https://github.com/phpredis/phpredis/issues/2332.
|
||||
if ($encrypt && !$redis->ping('Ping')) {
|
||||
throw new $exceptionclass("Ping failed");
|
||||
}
|
||||
|
||||
// If using compressor, serialisation will be done at cachestore level, not php-redis.
|
||||
if ($this->compressor === self::COMPRESSOR_NONE) {
|
||||
$redis->setOption(Redis::OPT_SERIALIZER, $this->serializer);
|
||||
}
|
||||
|
||||
// Set the prefix.
|
||||
$prefix = !empty($configuration['prefix']) ? $configuration['prefix'] : '';
|
||||
if (!empty($prefix)) {
|
||||
$redis->setOption(Redis::OPT_PREFIX, $prefix);
|
||||
}
|
||||
$this->isready = true;
|
||||
} catch (RedisException | RedisClusterException $e) {
|
||||
$server = $clustermode ? implode(',', $trimmedservers) : $trimmedservers[0].':'.$port;
|
||||
debugging("Failed to connect to Redis at {$server}, the error returned was: {$e->getMessage()}");
|
||||
$this->isready = false;
|
||||
}
|
||||
|
||||
@ -277,10 +317,10 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
/**
|
||||
* See if we can ping Redis server
|
||||
*
|
||||
* @param Redis $redis
|
||||
* @param RedisCluster|Redis $redis
|
||||
* @return bool
|
||||
*/
|
||||
protected function ping(Redis $redis) {
|
||||
protected function ping(RedisCluster|Redis $redis): bool {
|
||||
try {
|
||||
if ($redis->ping() === false) {
|
||||
return false;
|
||||
@ -763,7 +803,7 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
public function store_total_size(): ?int {
|
||||
try {
|
||||
$details = $this->redis->info('MEMORY');
|
||||
} catch (\RedisException $e) {
|
||||
} catch (RedisException $e) {
|
||||
return null;
|
||||
}
|
||||
if (empty($details['used_memory'])) {
|
||||
@ -789,6 +829,7 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
'compressor' => $data->compressor,
|
||||
'encryption' => $data->encryption,
|
||||
'cafile' => $data->cafile,
|
||||
'clustermode' => $data->clustermode,
|
||||
);
|
||||
}
|
||||
|
||||
@ -816,6 +857,9 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
if (!empty($config['cafile'])) {
|
||||
$data['cafile'] = $config['cafile'];
|
||||
}
|
||||
if (!empty($config['clustermode'])) {
|
||||
$data['clustermode'] = $config['clustermode'];
|
||||
}
|
||||
$editform->set_data($data);
|
||||
}
|
||||
|
||||
@ -847,6 +891,9 @@ class cachestore_redis extends cache_store implements cache_is_key_aware, cache_
|
||||
if (!empty($config->test_cafile)) {
|
||||
$configuration['cafile'] = $config->test_cafile;
|
||||
}
|
||||
if (!empty($config->test_clustermode)) {
|
||||
$configuration['clustermode'] = $config->test_clustermode;
|
||||
}
|
||||
// Make it possible to test TTL performance by hacking a copy of the cache definition.
|
||||
if (!empty($config->test_ttl)) {
|
||||
$definition = clone $definition;
|
||||
|
25
cache/stores/redis/settings.php
vendored
25
cache/stores/redis/settings.php
vendored
@ -25,15 +25,26 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$settings->add(
|
||||
new admin_setting_configtext(
|
||||
'cachestore_redis/test_server',
|
||||
get_string('test_server', 'cachestore_redis'),
|
||||
get_string('test_server_desc', 'cachestore_redis'),
|
||||
'',
|
||||
PARAM_TEXT,
|
||||
16
|
||||
new admin_setting_configcheckbox(
|
||||
name: 'cachestore_redis/test_clustermode',
|
||||
visiblename: get_string('clustermode', 'cachestore_redis'),
|
||||
description: cache_helper::is_cluster_available() ?
|
||||
get_string('clustermode_help', 'cachestore_redis') :
|
||||
get_string('clustermodeunavailable', 'cachestore_redis'),
|
||||
defaultsetting: 0,
|
||||
)
|
||||
);
|
||||
|
||||
$settings->add(
|
||||
new admin_setting_configtextarea(
|
||||
name: 'cachestore_redis/test_server',
|
||||
visiblename: get_string('test_server', 'cachestore_redis'),
|
||||
description: get_string('test_server_desc', 'cachestore_redis'),
|
||||
defaultsetting: '',
|
||||
paramtype: PARAM_TEXT,
|
||||
)
|
||||
);
|
||||
|
||||
$settings->add(new admin_setting_configcheckbox(
|
||||
'cachestore_redis/test_encryption',
|
||||
get_string('encrypt_connection', 'cachestore_redis'),
|
||||
|
193
cache/stores/redis/tests/cachestore_cluster_redis_test.php
vendored
Normal file
193
cache/stores/redis/tests/cachestore_cluster_redis_test.php
vendored
Normal file
@ -0,0 +1,193 @@
|
||||
<?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/>.
|
||||
|
||||
namespace cachestore_redis;
|
||||
|
||||
use cache_definition;
|
||||
use cache_store;
|
||||
use cachestore_redis;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__ . '/../../../tests/fixtures/stores.php');
|
||||
require_once(__DIR__ . '/../lib.php');
|
||||
|
||||
/**
|
||||
* Redis cluster test.
|
||||
*
|
||||
* If you wish to use these unit tests all you need to do is add the following definition to
|
||||
* your config.php file:
|
||||
*
|
||||
* define('TEST_CACHESTORE_REDIS_SERVERSCLUSTER', 'localhost:7000,localhost:7001');
|
||||
* define('TEST_CACHESTORE_REDIS_ENCRYPTCLUSTER', true);
|
||||
* define('TEST_CACHESTORE_REDIS_AUTHCLUSTER', 'foobared');
|
||||
* define('TEST_CACHESTORE_REDIS_CASCLUSTER', '/cafile/dir/ca.crt');
|
||||
*
|
||||
* @package cachestore_redis
|
||||
* @author Daniel Thee Roperto <daniel.roperto@catalyst-au.net>
|
||||
* @copyright 2017 Catalyst IT Australia {@link http://www.catalyst-au.net}
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*
|
||||
* @coversDefaultClass \cachestore_redis
|
||||
*/
|
||||
class cachestore_cluster_redis_test extends \advanced_testcase {
|
||||
/**
|
||||
* Create a cache store for testing the Redis cluster.
|
||||
*
|
||||
* @param string|null $seed The redis cluster servers.
|
||||
* @return cachestore_redis The created cache store instance.
|
||||
*/
|
||||
public function create_store(?string $seed = null): cachestore_redis {
|
||||
global $DB;
|
||||
|
||||
$definition = cache_definition::load_adhoc(
|
||||
mode: cache_store::MODE_APPLICATION,
|
||||
component: 'cachestore_redis',
|
||||
area: 'phpunit_test',
|
||||
);
|
||||
|
||||
$servers = $seed ?? str_replace(",", "\n", TEST_CACHESTORE_REDIS_SERVERSCLUSTER);
|
||||
|
||||
$config = [
|
||||
'server' => $servers,
|
||||
'prefix' => $DB->get_prefix(),
|
||||
'clustermode' => true,
|
||||
];
|
||||
|
||||
if (defined('TEST_CACHESTORE_REDIS_ENCRYPTCLUSTER') && TEST_CACHESTORE_REDIS_ENCRYPTCLUSTER === true) {
|
||||
$config['encryption'] = true;
|
||||
}
|
||||
if (defined('TEST_CACHESTORE_REDIS_AUTHCLUSTER') && TEST_CACHESTORE_REDIS_AUTHCLUSTER) {
|
||||
$config['password'] = TEST_CACHESTORE_REDIS_AUTHCLUSTER;
|
||||
}
|
||||
if (defined('TEST_CACHESTORE_REDIS_CASCLUSTER') && TEST_CACHESTORE_REDIS_CASCLUSTER) {
|
||||
$config['cafile'] = TEST_CACHESTORE_REDIS_CASCLUSTER;
|
||||
}
|
||||
|
||||
$store = new cachestore_redis('TestCluster', $config);
|
||||
$store->initialise($definition);
|
||||
$store->purge();
|
||||
|
||||
return $store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the test environment.
|
||||
*/
|
||||
public function setUp(): void {
|
||||
if (!cachestore_redis::are_requirements_met()) {
|
||||
$this->markTestSkipped('Could not test cachestore_redis with cluster, missing requirements.');
|
||||
} else if (!\cache_helper::is_cluster_available()) {
|
||||
$this->markTestSkipped('Could not test cachestore_redis with cluster, class RedisCluster is not available.');
|
||||
} else if (!defined('TEST_CACHESTORE_REDIS_SERVERSCLUSTER')) {
|
||||
$this->markTestSkipped('Could not test cachestore_redis with cluster, missing configuration. ' .
|
||||
"Example: define('TEST_CACHESTORE_REDIS_SERVERSCLUSTER', " .
|
||||
"'localhost:7000,localhost:7001,localhost:7002');");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the cache store can be created successfully.
|
||||
*
|
||||
* @covers ::is_ready
|
||||
*/
|
||||
public function test_it_can_create(): void {
|
||||
$store = $this->create_store();
|
||||
$this->assertNotNull($store);
|
||||
$this->assertTrue($store->is_ready());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the cache store trims server names correctly.
|
||||
*
|
||||
* @covers ::new_redis
|
||||
*/
|
||||
public function test_it_trims_server_names(): void {
|
||||
// Add a time before and spaces after the first server. Also adds a blank line before second server.
|
||||
$servers = explode(',', TEST_CACHESTORE_REDIS_SERVERSCLUSTER);
|
||||
$servers[0] = "\t" . $servers[0] . " \n";
|
||||
$servers = implode("\n", $servers);
|
||||
|
||||
$store = $this->create_store($servers);
|
||||
|
||||
$this->assertTrue($store->is_ready());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the cache store can successfully set and get a value.
|
||||
*
|
||||
* @covers ::set
|
||||
* @covers ::get
|
||||
*/
|
||||
public function test_it_can_setget(): void {
|
||||
$store = $this->create_store();
|
||||
$store->set('the key', 'the value');
|
||||
$actual = $store->get('the key');
|
||||
|
||||
$this->assertSame('the value', $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the cache store can successfully set and get multiple values.
|
||||
*
|
||||
* @covers ::set_many
|
||||
* @covers ::get_many
|
||||
*/
|
||||
public function test_it_can_setget_many(): void {
|
||||
$store = $this->create_store();
|
||||
|
||||
// Create values.
|
||||
$values = [];
|
||||
$keys = [];
|
||||
$expected = [];
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
$key = "getkey_{$i}";
|
||||
$value = "getvalue #{$i}";
|
||||
$keys[] = $key;
|
||||
$values[] = [
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
];
|
||||
$expected[$key] = $value;
|
||||
}
|
||||
|
||||
$store->set_many($values);
|
||||
$actual = $store->get_many($keys);
|
||||
$this->assertSame($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the cache store is marked as not ready if it fails to connect.
|
||||
*
|
||||
* @covers ::is_ready
|
||||
*/
|
||||
public function test_it_is_marked_not_ready_if_failed_to_connect(): void {
|
||||
global $DB;
|
||||
|
||||
$config = [
|
||||
'server' => "abc:123",
|
||||
'prefix' => $DB->get_prefix(),
|
||||
'clustermode' => true,
|
||||
];
|
||||
$store = new cachestore_redis('TestCluster', $config);
|
||||
$debugging = $this->getDebuggingMessages();
|
||||
// Failed to connect should show a debugging message.
|
||||
$this->assertCount(1, \phpunit_util::get_debugging_messages() );
|
||||
$this->assertStringContainsString('Couldn\'t map cluster keyspace using any provided seed', $debugging[0]->message);
|
||||
$this->resetDebugging();
|
||||
$this->assertFalse($store->is_ready());
|
||||
}
|
||||
}
|
154
cache/stores/redis/tests/cachestore_redis_test.php
vendored
Normal file
154
cache/stores/redis/tests/cachestore_redis_test.php
vendored
Normal file
@ -0,0 +1,154 @@
|
||||
<?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/>.
|
||||
|
||||
namespace cachestore_redis;
|
||||
|
||||
use cache_definition;
|
||||
use cache_store;
|
||||
use cachestore_redis;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once(__DIR__.'/../../../tests/fixtures/stores.php');
|
||||
require_once(__DIR__.'/../lib.php');
|
||||
|
||||
/**
|
||||
* Redis cache store test.
|
||||
*
|
||||
* If you wish to use these unit tests all you need to do is add the following definition to
|
||||
* your config.php file.
|
||||
*
|
||||
* define('TEST_CACHESTORE_REDIS_TESTSERVERS', '127.0.0.1');
|
||||
*
|
||||
* @package cachestore_redis
|
||||
* @copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com)
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*
|
||||
* @coversDefaultClass \cachestore_redis
|
||||
*/
|
||||
class cachestore_redis_test extends \cachestore_tests {
|
||||
/** @var cachestore_redis $store Redis Cache Store. */
|
||||
protected $store;
|
||||
|
||||
/**
|
||||
* Returns the class name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_class_name(): string {
|
||||
return 'cachestore_redis';
|
||||
}
|
||||
|
||||
public function setUp(): void {
|
||||
if (!cachestore_redis::are_requirements_met() || !defined('TEST_CACHESTORE_REDIS_TESTSERVERS')) {
|
||||
$this->markTestSkipped('Could not test cachestore_redis. Requirements are not met.');
|
||||
}
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
protected function tearDown(): void {
|
||||
parent::tearDown();
|
||||
|
||||
if ($this->store instanceof cachestore_redis) {
|
||||
$this->store->purge();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the required cachestore for the tests to run against Redis.
|
||||
*
|
||||
* @return cachestore_redis
|
||||
*/
|
||||
protected function create_cachestore_redis(): cachestore_redis {
|
||||
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_redis', 'phpunit_test');
|
||||
$store = new cachestore_redis('Test', cachestore_redis::unit_test_configuration());
|
||||
$store->initialise($definition);
|
||||
$this->store = $store;
|
||||
$store->purge();
|
||||
return $store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test methods for various operations (set and has) in the cachestore_redis class.
|
||||
*
|
||||
* @covers ::set
|
||||
* @covers ::has
|
||||
*/
|
||||
public function test_has(): void {
|
||||
$store = $this->create_cachestore_redis();
|
||||
|
||||
$this->assertTrue($store->set('foo', 'bar'));
|
||||
$this->assertTrue($store->has('foo'));
|
||||
$this->assertFalse($store->has('bat'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test methods for the 'has_any' operation in the cachestore_redis class.
|
||||
*
|
||||
* @covers ::set
|
||||
* @covers ::has_any
|
||||
*/
|
||||
public function test_has_any(): void {
|
||||
$store = $this->create_cachestore_redis();
|
||||
|
||||
$this->assertTrue($store->set('foo', 'bar'));
|
||||
$this->assertTrue($store->has_any(['bat', 'foo']));
|
||||
$this->assertFalse($store->has_any(['bat', 'baz']));
|
||||
}
|
||||
|
||||
/**
|
||||
* PHPUnit test methods for the 'has_all' operation in the cachestore_redis class.
|
||||
*
|
||||
* @covers ::set
|
||||
* @covers ::has_all
|
||||
*/
|
||||
public function test_has_all(): void {
|
||||
$store = $this->create_cachestore_redis();
|
||||
|
||||
$this->assertTrue($store->set('foo', 'bar'));
|
||||
$this->assertTrue($store->set('bat', 'baz'));
|
||||
$this->assertTrue($store->has_all(['foo', 'bat']));
|
||||
$this->assertFalse($store->has_all(['foo', 'bat', 'this']));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test methods for the 'lock' operations in the cachestore_redis class.
|
||||
*
|
||||
* @covers ::acquire_lock
|
||||
* @covers ::check_lock_state
|
||||
* @covers ::release_lock
|
||||
*/
|
||||
public function test_lock(): void {
|
||||
$store = $this->create_cachestore_redis();
|
||||
|
||||
$this->assertTrue($store->acquire_lock('lock', '123'));
|
||||
$this->assertTrue($store->check_lock_state('lock', '123'));
|
||||
$this->assertFalse($store->check_lock_state('lock', '321'));
|
||||
$this->assertNull($store->check_lock_state('notalock', '123'));
|
||||
$this->assertFalse($store->release_lock('lock', '321'));
|
||||
$this->assertTrue($store->release_lock('lock', '123'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method to check if the cachestore_redis instance is ready after connecting.
|
||||
*
|
||||
* @covers ::is_ready
|
||||
*/
|
||||
public function test_it_is_ready_after_connecting(): void {
|
||||
$store = $this->create_cachestore_redis();
|
||||
$this::assertTrue($store->is_ready());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user