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:
meirzamoodle 2023-12-27 09:28:47 +07:00
parent 39b8e198ff
commit 6a28e0641e
6 changed files with 506 additions and 82 deletions

View File

@ -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');

View File

@ -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.';

View File

@ -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;

View File

@ -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'),

View 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());
}
}

View 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());
}
}