Updated to verify that new sentinels retrieved from "SENTINEL" response
have their role automatically set to "sentinel". Also applied some minor
changes by dropping useless alias.
Until now users could specify a set of default parameters applied to all
connections created by the connection factory when not explicitly set in
the user-supplied set of parameters of each single node connection. This
is definitely handy, but it has some limits especially when dealing with
sentinel nodes since there are times when it is better to use different
defaults (e.g. "timeout") and they do not support certain parameters.
This commit adds the ability to specify role-specific default parameters
that gets applied only to connections targeting specific roles. This is
mostly useful for sentinels as they usually require lower timeouts than
normal Redis nodes and may also have a different password.
Role-specific parameters are passed as part of the "parameters" client
option in the form of named sub-arrays and take precedence over global
parameters, but they still do not override parameters explicitly set by
the user for single nodes.
Supported keys are "role.sentinel", "role.master" and "role.slave". In
regards to "role.sentinel", please note that:
- sentinels do not support ACL authentication or database selection so
so "username" and "database" are always stripped off.
- "password" is never inherited from global defaults because users can
have password-protected Redis nodes but unprotected Redis sentinels.
In such cases, users must explicitly set a password either for each
sentinel connection or just once in "role.sentinel".
Here is a brief example showing how to configure Predis for replication
supervised by redis-sentinel using different timeout and password values
for sentinel nodes compared to normal master and replica nodes.
$client = new Predis\Client($arrayOfSentinels, [
'replication' => 'sentinel',
'service' => $sentinelService,
'parameters' => [
// Set of global default parameters, applied to *any* connection:
'scheme' => true,
'tcp_nodelay' => true,
'timeout' => 5,
'username' => $redisUsername, // Won't be inherited by sentinels.
'password' => $redisPassword, // Won't be inherited by sentinels.
// Set of sentinels-specific default parameters:
'role.sentinel' => [
// For sentinels, "scheme" and "tcp_nodelay" are inherited from
// default parameters and "timeout" is overridden. On the other
// hand both "username" and "password" are never inherited but
// still explicitly set a password for sentinels because, in our
// example, sentinels are indeed password-protected.
'timeout' => 0.200,
'password' => $sentinelPassword,
],
]);
Password-based authentication for sentinels has been added in Redis 5.
Predis was actively ignoring any "password" parameter for sentinels when
creating connections to them to avoid issues when this parameter is set
in the default "parameters" array passed via client options, as they are
applied to **every** connection created by Predis (see #346).
We need to find a better way to specify a common password for sentinels
to be handled in a different way than the ones for Redis nodes. For now
each sentinel node protected by password must have an explicit password
set in its parameters list even if this password, by design, is the same
for all sentinels. Since we cannot use default "parameters" as explained
above but we still need to pass a common value for all sentinels an idea
could be using a dedicated client option like we did with "service", but
we will see later.
In this commit we also explicitly reset any "username" parameter as it
would trigger an `AUTH $username $password` but sentinels do not support
ACL authentication.
Fixes#594.
See commit dd5d665 for the above mentioned changes.
Tests for Option\Aggregate, Option\Replication and Option\Cluster should
be refactored because code is a bit too repetitive and a bunch of tests
are shared among them (replication and cluster options extend aggregate
and share the same logic after all).
The "at" matcher will be removed in PHPUnit 10 but it is not a bad thing
after all because it was cumbersome and error-prone.
Took the opportunity to improve some tests while converting them.
- Make use of more typehints for function parameters
- Make use of typehints for function return values
- Use @var where needed to give proper hints to IDEs and avoid warnings
- Replace MockObject::setMethods() with addMethods() and onlyMethods()
- Rewording of some phpdocs
The username is now correctly retrieved from the userinfo fragment of
the URI when using the "redis" scheme and a "username:password" pair is
present. Values retrieved from the userinfo fragment always override the
ones specified in `username` and `password` if those fields are present
in the query string.
When passing both "username" and "password" to connection parameters the
client now uses the extended AUTH command to support ACL authentication
with Redis 6.0.
The plain old authentication method is still supported like usual simply
by passing only "password" to connection parameters.
NULL or zero-length string values passed to "password" and "database" in
the connection parameters list do not trigger spurious AUTH and SELECT
commands anymore when connecting to Redis.
Fixes#436.
We have renamed most methods to drop the "command" suffix as it is quite
redundant. Due to this change and thanks to variadic methods introduced
with PHP 5.6 we took the opportunity to replace both "supportsCommand()"
and "supportsCommands()" with a single new method "supports()".
Added more stringent typehints for method arguments and typehints for
return values now that we do not need to support anything below PHP 7.2.
Also moved from using array() to [] in source code of class involved.
Having NULL values or zero-length strings for connection parameters does
not make much sense and actually it proved to be an issue with certain
parameters like "password" where an empty string would trigger an AUTH
command with an empty password (and obviously Redis was not happy with
that). The main offenders were a few libraries and frameworks that kept
passing empty values for parameters such as "database" and "password"
even when users left them unconfigured. This fix should make things more
robust and avoid such occurrences in the future.
Related to PR #436 (rejected).
This change does not affect distribution but simply separates the two
concepts of connection ID (ip:port pair) and alias (value set via the
"alias" connection parameter), the method getConnectionByAlias() has
been added to reflect this change.
The method getConnectionBySlot() has also been added.
When using replication backends, now the role of a connection is not
defined by its alias but by the new connection parameter "role" that
can be set to "master", "slave" and (for redis-sentinel) "sentinel".
This also led to a redesign of how connections can be retrieved from
replication backends: the method getConnectionById() now retrieves a
connection only by its ID (ip:port pair), to get a connection by its
alias there is the new method getConnectionByAlias(). This method is
not supported by the redis-sentinel backend due to its dynamic nature
(connections are retrieved and initialized at runtime from sentinels)
but it is still possible to get a single connection from the pool by
using its ID. It is also possible to retrive a connection by its role
using the method getConnectionByRole().
NOTE: the "role" parameter is an hint for the internals of aggregate
connection backends so it is still possible that the actual role of
a connection changes during the execution of a script (e.g. a slave
gets promoted to the role of master): in this case the parameters of
that connection will not be changed as they are immutable, but the
method getConnectionByRole() will return the appropriate connection.
Predis\Client::getClientBy() has been updated with the addition of
"role" and "alias" as supported selectors.
In addition to that, the methods askSlotsMap() and buildSlotsMap() of
the redis-cluster connection backend have been renamed respectively
to askSlotMap() and buildSlotMap().
This change reduces some unnecessary complexity in the library, Redis
commands do not change much after all. Developers can still implement
their own commands factory, inject new commands or override existing
ones. The "profile" client options has been renamed to "commands" and
it accepts instances of Predis\Command\FactoryInterface.
The test suite checks at runtime the version of the running instance
of Redis for integration tests to adapt itself automatically.
Iterating over Predis\Connection\Aggregate\RedisCluster returns all
the connections currently mapped in the slots map instead of just the
ones initialized in the pool.
When the slots map is retrieved from Redis (which by default is done
automatically) this allows to iterate over all of the current master
nodes of the cluster. When the underlying use of "CLUSTER SLOTS" is
disabled the iteration returns only connections with a slots range
associated in their parameters or initialized by `-MOVED` responses
to make the behaviour of the iteration consistent between the two
modes of operation.
When various nodes in the configuration are unreachable while trying
to send a command, we should attempt to contact a reachable node to
fetch an updated slots map up to $retryLimit times or until there are
no more servers in the pool before giving up.
It is possible that the slots map fetched from Redis contains stale
data and points to a dead server, this happens when the nodes still
have to agree that a master server is down before promoting a slave
to the role of master. In this case no further attempts to execute
the command are performed and an exception is thrown.
This still needs some more testing and will delay v1.0.4 a few days
past its scheduled release.