Commit Graph

449 Commits

Author SHA1 Message Date
Daniele Alessandri 6e8e23279e [tests] Update Predis\Connection\Aggregate\SentinelReplication tests.
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.
2020-09-12 19:44:56 +02:00
Daniele Alessandri d9de9b5df3 Implement role-specific default parameters.
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,
      ],
  ]);
2020-09-12 17:27:32 +02:00
Daniele Alessandri 985343a992 Merge remote-tracking branch 'github/pr/656' into main
Local branch v2.0-clientoptions-aggregateconnections
2020-09-10 21:19:52 +02:00
Daniele Alessandri d954e9f43f Merge branch 'v1.1' into main 2020-09-10 21:16:58 +02:00
Daniele Alessandri 2e76410e65 Support password-based authentication for sentinel instances.
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.
2020-09-05 16:25:06 +02:00
Daniele Alessandri ca468b785c Throw exception on FALSE passed to "replication" option.
While "replication" do accept values evaluating to TRUE, the same cannot
be said for values evaluating to FALSE. TRUE is used to tell the client
that we want replication handled using the default backend for unmanaged
replication setups. For using redis-sentinel the "sentinel" string value
must be passed.

Setting "replication" to FALSE led to a failure (and a PHP warning) on
client initialization because this condition was not handled properly.
Being able to do so would not make sense anyway: when the client does
not need to be set up to rely on replication, users simply have to omit
the option. Furthermore, users must always specify either "replication"
or "cluster" and not both with one of them set to FALSE.

Unfortunately options for aggregate connections in Predis v1.1 are a bit
of a mess, they did not scale well with the addition of new features and
are also quite inconsistent (e.g. "cluster" does not accept TRUE).

This has been largely fixed in Predis v2.0-dev but required implementing
a few breaking changes. It also means that this change does not need to
be ported to the main branch.

Addresses #381 using a different approach.
2020-09-05 14:18:30 +02:00
Daniele Alessandri b1ea4374d4 [tests] Fix the very few remaining warnings emitted by test suite.
Basically assertMatchesRegularExpression() replaces assertRegExp() which
has been deprecated since PHPUnit 9.1 and will be removed in PHPUnit 10,
unfortunately we still depend on PHPUnit 8.4 to support PHP 7.2 and this
version does not have assertMatchesRegularExpression() so we implemented
it in our base testcase class with a fallback to the old assertRegExp()
when tests are executed on older versions of PHPUnit.
2020-09-04 21:00:46 +02:00
Daniele Alessandri 4a7fb55058 [tests] Add utility method to skip tests on CI environment.
We still have disabled all PUB/SUB related tests on CI for now, until we
understand why they fail at random.

Backported from main branch (ref. 5133706, f723f67, eb8a89e)
2020-09-04 18:42:38 +02:00
Daniele Alessandri e5f44c33a2 No need for socket_import_stream() to set TCP_NODELAY since PHP 7.1. 2020-09-04 17:56:19 +02:00
Daniele Alessandri 9e48aef4b0 [tests] Adapt after changes in aggregate connection initialization.
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).
2020-09-03 15:12:34 +02:00
Daniele Alessandri efbe80222e Merge remote-tracking branch 'github/pr/655' into main
Local branch v2.0-commandfactory-raw
2020-09-03 12:41:00 +02:00
Daniele Alessandri eb8a89ee7a [tests] Skip integration tests for DispatcherLoop on CI.
Same reason as of PubSub/Consumer, still need to investigate the random
failures. Anyone can help?
2020-09-03 12:33:12 +02:00
Daniele Alessandri f723f67c3f [tests] Use new utility method to skip PUB/SUB tests on CI. 2020-09-03 12:32:38 +02:00
Daniele Alessandri 6d6c970ae2 [tests] Replace deprecated TestCase::at().
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.
2020-09-01 14:03:19 +02:00
Daniele Alessandri c6f2ce4b83 [tests] Replace generic MockObject::will() with specialized methods. 2020-09-01 14:03:19 +02:00
Daniele Alessandri 36c9bb047e [tests] Start improving test suite.
- 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
2020-09-01 13:58:14 +02:00
Daniele Alessandri 5eccc65d42 Fix handling of username when using "redis://".
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.
2020-09-01 11:40:30 +02:00
Daniele Alessandri c2ede30394 Merge remote-tracking branch 'github/pr/653' into main
Local branch v2.0-connections-phpiredisconfig
2020-08-31 10:54:40 +02:00
Daniele Alessandri c2c1812b87 Merge remote-tracking branch 'github/pr/651' into main
Local branch v2.0-commandfactory-reworkinterface
2020-08-31 10:54:19 +02:00
Daniele Alessandri 743be10f6e Merge remote-tracking branch 'github/pr/650' into main
Local branch v2.0-parameters-filterempty
2020-08-31 10:53:43 +02:00
Daniele Alessandri 7b86a7b54d Merge branch 'v1.1' into main 2020-08-30 23:40:36 +02:00
Daniele Alessandri dc067b3dac Extend "commands" option to support Predis\Command\RawFactory.
The option handler has been extended to accept descriptive string values
in order to create and configure an appropriate command factory.

Accepted string values are:

- "predis" creates the usual command factory
- "raw" creates a raw command factory
- "default" is simply an alias of "predis"
2020-08-30 15:24:12 +02:00
Daniele Alessandri 30a065ad86 Implement Predis\Command\RawFactory.
Any command ID will produce a command instance even for unknown commands
not implemented by Redis (the server will return a -ERR error response).

When using this factory the client does not process arguments before
sending commands to Redis and server responses are not further processed
before being returned to the caller.
2020-08-30 15:01:43 +02:00
Daniele Alessandri b9ebe8a376 [tests] Fix mistype in TODO comments.
... and yay for copypasting it too!
2020-08-30 23:26:42 +02:00
Daniele Alessandri 87452e62d0 Fix authentication to support ACL with Redis 6.0.
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.
2020-08-29 14:11:55 +02:00
Daniele Alessandri 31baa9b241 Do not push AUTH and SELECT on empty "password" and "database".
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.
2020-08-29 13:39:12 +02:00
Daniele Alessandri bd68b55456 Merge remote-tracking branch 'github/pr/649' into main 2020-08-29 11:42:38 +02:00
Daniele Alessandri cc2a7657db Rework Predis\Command\FactoryInterface and related classes.
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.
2020-08-28 14:23:12 +02:00
Daniele Alessandri 5348c4025e Merge remote-tracking branch 'github/pr/648' into main 2020-08-28 10:28:17 +02:00
Daniele Alessandri 6f942400a1 [tests] Skip integration tests for PUB/SUB consumer on CI.
Changes backported from the main branch (ref. 04d5c10, 5afadb5).

This is just a temporary solution, we will revert this change as soon as
the actual cause for the spurious failures is identified.
2020-08-27 17:44:23 +02:00
Daniele Alessandri 5afadb560f [tests] Skip testPubSubAgainstRedisServer on CI environments.
See 04d5c10.
2020-08-27 17:38:38 +02:00
Daniele Alessandri e58d5fceef Add "redis" scheme in configuration for phpiredis.
Forgot about it! While "redis" is perceived simply as an alias for "tcp"
it must be explicitly mapped in the connection factory.
2020-08-27 16:38:38 +02:00
Daniele Alessandri 56d704f5dd Add "default" as accepted string for "connections".
Using "default" returns a connection factory instance with the default
configuration. Basically it is no different than omitting "connections"
in the client options array but it can be useful when applications want
to automatically configure Predis to use phpiredis when it is loaded:

$client = new Predis\Client('tcp://127.0.0.1', [
  'connections' =>
      extension_loaded('phpiredis')
        ? 'phpiredis'
        : 'default'
]);

This is in response to ISSUE #397.
2020-08-27 15:59:14 +02:00
Daniele Alessandri 2c732c46c8 Make configuration easier for phpiredis based connections.
The "connections" client option now accepts certain string values that
are mapped to specific and most used configurations for the connection
factory. This is used to make it easier to configure Predis to load our
phpiredis-based connection backends without having to manually pass a
map of URI schemes and fully-qualified class names.

  - "phpiredis-stream" maps `tcp` and `unix` to the connection backend
    based on PHP streams (Predis\Connection\PhpiredisStreamConnection).
  - "phpiredis-socket" maps `tcp` and `unix` to the connection backend
    based on ext-socket (Predis\Connection\PhpiredisStreamConnection).
  - `phpiredis` is simply an alias of `phpiredis-stream`.

An InvalidArgumentException is thrown on unsupported string values.
2020-08-27 15:09:35 +02:00
Daniele Alessandri accb030eb5 [tests] Minor adjustments for callable connection initalizers. 2020-08-26 00:57:03 +02:00
Daniele Alessandri 04d5c10193 [tests] Skip testPubSubAgainstRedisServerBlocking on CI environments.
NOTE: this specific test fails at random without any apparent reason
when executed on our CI environments and these failures are not tied
to a particular version of PHP or Redis. It is most likely some weird
timing issue on busy systems as it is really rare to get it triggered
locally. The chances it is a bug in the library are pretty low so for
now we just mark this test skipped on our CI environments (but still
enabled for local test runs) and "debug" this issue using a separate
branch to avoid having spurious failures on main development branches
which is utterly annoying.

We will restore this test on CI environments as soon as we understand
what is the reason behind its random failures.
2020-08-25 17:52:19 +02:00
Daniele Alessandri 4e605ea233 Revert "[tests] Try fixing random failures in one test of PUB/SUB abstraction."
Whoops, wrong test :)

This reverts commit 5c2b31c566.
2020-08-25 17:47:23 +02:00
Daniele Alessandri 5c2b31c566 [tests] Try fixing random failures in one test of PUB/SUB abstraction.
These failures are random and rarely reproducible on a local development
environment but sometimes they affect the success of a test run and it's
annoying. I think it is just a weird timing issue on busy hosts so let's
try with a couple of simple usleep() after SUBSCRIBE and PUBLISH and see
if anything changes in the next few test runs.
2020-08-25 17:16:19 +02:00
Daniele Alessandri 15eadb3426 Merge branch 'v1.1' into main 2020-08-25 16:07:06 +02:00
Daniele Alessandri 0eb2633348 Merge remote-tracking branch 'github/pr/554' into v1.1 2020-08-25 15:42:13 +02:00
Daniele Alessandri b014d5de5a Filter NULL and zero-length string values for parameters.
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).
2020-08-25 12:20:53 +02:00
Daniele Alessandri 7113e31954 Merge branch 'v2.0-merge-into-main' into main 2020-08-24 22:41:44 +02:00
Daniele Alessandri 17f0c08cb6 [tests] Fix expected response for COMMAND INFO (Redis >= 6.0).
Starting with Redis 6.0 and the introduction of Access Control Lists,
COMMAND INFO returns an additional array for each specified command in
the request with a list of the ACL categories associated to a command.
2020-08-24 11:45:20 +02:00
Daniele Alessandri 2a932d7292 Merge remote-tracking branch 'github/pr/556' into v1.1 2020-08-23 17:47:26 +02:00
Peter Gribanov 4363345f7c iterate not iterable connections #552 2020-08-23 15:00:22 +03:00
Daniele Alessandri f06a41cfdc Change method to undefine commands in factory.
The previous implementation was not good because we were exposing in the
public API an internal implementation detail of the base factory class,
furthermore it made Predis\Command\Factory::define() confusing. Having a
separate method to undefine commands in the factory is self-explanatory.

We also changed `Predis\Configuration\Option\Commands` accordingly when
a dictionary of $commandID => $classCommand is passed to the "commands"
client option and $classCommand is NULL.

A few minor changes (mostly cosmetic or documentation) were applied too.

From feedback to PR #644.
2020-08-21 14:30:52 +02:00
Daniele Alessandri 5a7548bc81 Remove useless argument in Predis\Client::getClientBy().
There was no real meaning to have a callback here, for the most part it
was just a leftover of a previous approach implemented with ee7104d and
quickly superseded by the current approach that simply returns the new
client instance instead of using callbacks.

From feedback to #644.
2020-08-21 14:00:13 +02:00
Daniele Alessandri 4b2b35202a [tests] Restore new message for failed assertion. 2020-08-18 16:12:45 +02:00
Daniele Alessandri c26909699b Merge remote-tracking branch 'github/pr/639' into main 2020-08-18 16:03:10 +02:00
Daniele Alessandri 592dd91454 Fix class name for OBJECT command.
Starting with PHP 7.2 "object" is a reserved word and cannot be used as
a name for classes, interfaces or traits.
2020-08-17 18:32:14 +02:00