Skip to content

Commit 57f2c2e

Browse files
committed
adding sentinel tests
1 parent 4f42c31 commit 57f2c2e

File tree

7 files changed

+312
-111
lines changed

7 files changed

+312
-111
lines changed

docker-compose.yml

+10
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,21 @@ services:
1414
- redis
1515
environment:
1616
- REDIS_HOST=redis
17+
- REDIS_SENTINEL_HOST=redis-sentinel
1718

1819
redis:
1920
image: redis
2021
ports:
2122
- 6379:6379
2223

24+
redis-sentinel:
25+
image: redis
26+
volumes:
27+
- ./sentinel.conf://etc/sentinel.conf
28+
ports:
29+
- 26379:26379
30+
command: redis-sentinel /etc/sentinel.conf
31+
2332
phpunit:
2433
build: php-fpm/
2534
volumes:
@@ -29,3 +38,4 @@ services:
2938
- nginx
3039
environment:
3140
- REDIS_HOST=redis
41+
- REDIS_SENTINEL_HOST=redis-sentinel

sentinel.conf

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
bind 0.0.0.0
2+
sentinel monitor myprimary redis 6379 2
3+
sentinel resolve-hostnames yes
4+
sentinel down-after-milliseconds myprimary 10000
5+
sentinel failover-timeout myprimary 10000
6+
sentinel parallel-syncs myprimary 1

src/Prometheus/Storage/Redis.php

+40-13
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class Redis implements Adapter
7777
*/
7878
private $redis;
7979

80+
/**
81+
* @var RedisSentinel
82+
*/
83+
private $sentinel;
84+
8085
/**
8186
* @var boolean
8287
*/
@@ -88,34 +93,49 @@ class Redis implements Adapter
8893
*/
8994
public function __construct(array $options = [])
9095
{
91-
$this->options = array_merge(self::$defaultOptions, $options);
96+
$this->options = [...self::$defaultOptions, ...$options];
97+
$this->options['sentinel'] = [...self::$defaultOptions['sentinel'] ?? [], ...$options['sentinel'] ?? []];
9298
$this->redis = new \Redis();
99+
if(isset($this->options['sentinel']) && boolval($this->options['sentinel']['enable'])){
100+
$options['sentinel']['host'] = $options['sentinel']['host'] ?? $options['host'];
101+
$this->sentinel = new RedisSentinel($options['sentinel']);
102+
}
93103
}
94104

95105
/**
96-
* Sentinels descoverMaster
97-
* @param mixed[] $options
106+
* Sentinels discoverMaster
98107
* @return mixed[]
99108
*/
100-
public function getSentinelPrimary(array $options = []) : array
101-
{
102-
$sentinel = new RedisSentinelConnector();
103-
$options['sentinel']['host'] = $options['sentinel']['host'] ?? $options['host'];
104-
$master = $sentinel->getMaster($options['sentinel']);
109+
public function getSentinelPrimary(): array
110+
{
111+
$master = $this->sentinel->getMaster();
112+
105113
if (is_array($master)) {
106114
$options['host'] = $master['ip'];
107115
$options['port'] = $master['port'];
108116
}
109117
return $options;
110118
}
111119

120+
/**
121+
* @return \RedisSentinel
122+
*/
123+
public function getRedisSentinel() : \RedisSentinel {
124+
return $this->sentinel->getRedisSentinel();
125+
}
126+
112127
/**
113128
* @param \Redis $redis
129+
* @param \RedisSentinel $redisSentinel
114130
* @return self
115131
* @throws StorageException
116132
*/
117-
public static function fromExistingConnection(\Redis $redis): self
133+
public static function fromExistingConnection(\Redis $redis, \RedisSentinel $redisSentinel = null): self
118134
{
135+
if($redisSentinel) {
136+
RedisSentinel::fromExistingConnection($redisSentinel);
137+
}
138+
119139
if ($redis->isConnected() === false) {
120140
throw new StorageException('Connection to Redis server not established');
121141
}
@@ -263,13 +283,13 @@ private function ensureOpenConnection(): void
263283
if ($this->connectionInitialized === true) {
264284
return;
265285
}
266-
267-
if (isset($this->options['sentinel']) && boolval($this->options['sentinel']['enable'])) {
286+
287+
if ($this->sentinel) {
268288
$reconnect = $this->options['sentinel']['reconnect'];
269289
$retries = 0;
270290
while ($retries <= $reconnect) {
271291
try {
272-
$this->options = $this->getSentinelPrimary($this->options);
292+
$this->options = $this->getSentinelPrimary();
273293
$this->connectToServer();
274294
break;
275295
} catch (\RedisException $e) {
@@ -324,7 +344,14 @@ private function connectToServer(): void
324344
(float) $this->options['timeout']
325345
);
326346
} else {
327-
$connection_successful = $this->redis->connect($this->options['host'], (int) $this->options['port'], (float) $this->options['timeout']);
347+
try {
348+
$connection_successful = $this->redis->connect($this->options['host'], (int) $this->options['port'], (float) $this->options['timeout']);
349+
} catch(\RedisException $ex){
350+
throw new StorageException(
351+
sprintf("Can't connect to Redis server. %s", $ex->getMessage()),
352+
$ex->getCode()
353+
);
354+
}
328355
}
329356
if (!$connection_successful) {
330357
throw new StorageException(
+161
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
namespace Prometheus\Storage;
4+
5+
use Prometheus\Exception\StorageException;
6+
7+
class RedisSentinel
8+
{
9+
/**
10+
* @var \RedisSentinel
11+
*/
12+
private $sentinel;
13+
14+
/**
15+
* @var mixed[]
16+
*/
17+
private $options = [];
18+
19+
/**
20+
* @var mixed[]
21+
*/
22+
private static $defaultOptions = [
23+
'enable' => false, // if enabled uses sentinel to get the master before connecting to redis
24+
'host' => '127.0.0.1', // phpredis sentinel address of the redis
25+
'port' => 26379, // phpredis sentinel port of the primary redis server, default 26379 if empty.
26+
'service' => 'myprimary', //, phpredis sentinel primary name, default myprimary
27+
'timeout' => 0, // phpredis sentinel connection timeout
28+
'persistent' => null, // phpredis sentinel persistence parameter
29+
'retry_interval' => 0, // phpredis sentinel retry interval
30+
'read_timeout' => 0, // phpredis sentinel read timeout
31+
'reconnect' => 0, // retries after losing connection to redis asking for a new primary, if -1 will retry indefinetely
32+
'username' => '', // phpredis sentinel auth username
33+
'password' => '', // phpredis sentinel auth password
34+
'ssl' => null,
35+
];
36+
37+
/**
38+
* @param \RedisSentinel $redisSentinel
39+
* @return self
40+
* @throws StorageException
41+
*/
42+
public static function fromExistingConnection(\RedisSentinel $redisSentinel) : self {
43+
$sentinel = new self();
44+
$sentinel->sentinel = $redisSentinel;
45+
$sentinel->getMaster();
46+
return $sentinel;
47+
}
48+
49+
/**
50+
* Redis constructor.
51+
* @param mixed[] $options
52+
*/
53+
public function __construct(array $options = [])
54+
{
55+
$this->options = [...self::$defaultOptions, ...$options];
56+
$this->sentinel = $this->connectToSentinel($this->options);
57+
}
58+
59+
/**
60+
* {@inheritdoc}
61+
* @param mixed[] $config
62+
* @return mixed[]|bool
63+
* @throws StorageException|\RedisException
64+
*/
65+
public function getMaster(): array|bool
66+
{
67+
$service = $this->options['service'];
68+
69+
try {
70+
if(!$this->sentinel) {
71+
$this->sentinel = $this->connectToSentinel($this->options);
72+
}
73+
74+
$master = $this->sentinel->master($service);
75+
} catch (\RedisException $e){
76+
throw new StorageException(
77+
sprintf("Can't connect to RedisSentinel server. %s", $e->getMessage()),
78+
$e->getCode(),
79+
$e
80+
);
81+
}
82+
83+
if (! $this->isValidMaster($master)) {
84+
throw new StorageException(sprintf("No master found for service '%s'.", $service));
85+
}
86+
87+
return $master;
88+
}
89+
90+
/**
91+
* Check whether master is valid or not.
92+
* @param mixed[]|bool $master
93+
* @return bool
94+
*/
95+
protected function isValidMaster(array|bool $master): bool
96+
{
97+
return is_array($master) && isset($master['ip']) && isset($master['port']);
98+
}
99+
100+
/**
101+
* Connect to the configured Redis Sentinel instance.
102+
* @return \RedisSentinel
103+
* @throws StorageException
104+
*/
105+
private function connectToSentinel(): \RedisSentinel
106+
{
107+
$host = $this->options['host'] ?? '';
108+
$port = $this->options['port'] ?? 26379;
109+
$timeout = $this->options['timeout'] ?? 0.2;
110+
$persistent = $this->options['persistent'] ?? null;
111+
$retryInterval = $this->options['retry_interval'] ?? 0;
112+
$readTimeout = $this->options['read_timeout'] ?? 0;
113+
$username = $this->options['username'] ?? '';
114+
$password = $this->options['password'] ?? '';
115+
$ssl = $this->options['ssl'] ?? null;
116+
117+
if (strlen(trim($host)) === 0) {
118+
throw new StorageException('No host has been specified for the Redis Sentinel connection.');
119+
}
120+
121+
$auth = null;
122+
if (strlen(trim($username)) !== 0 && strlen(trim($password)) !== 0) {
123+
$auth = [$username, $password];
124+
} elseif (strlen(trim($password)) !== 0) {
125+
$auth = $password;
126+
}
127+
128+
if (version_compare((string)phpversion('redis'), '6.0', '>=')) {
129+
$options = [
130+
'host' => $host,
131+
'port' => $port,
132+
'connectTimeout' => $timeout,
133+
'persistent' => $persistent,
134+
'retryInterval' => $retryInterval,
135+
'readTimeout' => $readTimeout,
136+
];
137+
138+
if ($auth !== null) {
139+
$options['auth'] = $auth;
140+
}
141+
142+
if (version_compare((string)phpversion('redis'), '6.1', '>=') && $ssl !== null) {
143+
$options['ssl'] = $ssl;
144+
}
145+
// @phpstan-ignore arguments.count, argument.type
146+
return new \RedisSentinel($options);
147+
}
148+
149+
if ($auth !== null) {
150+
/**
151+
* @phpstan-ignore arguments.count
152+
**/
153+
return new \RedisSentinel($host, $port, $timeout, $persistent, $retryInterval, $readTimeout, $auth);
154+
}
155+
return new \RedisSentinel($host, $port, $timeout, $persistent, $retryInterval, $readTimeout);
156+
}
157+
158+
public function getSentinel() : \RedisSentinel {
159+
return $this->sentinel;
160+
}
161+
}

src/Prometheus/Storage/RedisSentinelConnector.php

-98
This file was deleted.

0 commit comments

Comments
 (0)