From 46ccafe4670b570b46e4c105fb92d2bda9d044a7 Mon Sep 17 00:00:00 2001 From: romanpravda <59819062+romanpravda@users.noreply.github.com> Date: Tue, 22 Sep 2020 13:24:45 +0300 Subject: [PATCH] Adding the ability to use tags servers (#15) * adding ability to use proxy servers * added tests for changes * code style fixes * trying to fix building * another try * fix dependencies and tests * change readme * fix readme * grammar fix * remove proxy servers and add tags for servers * code style fixes * code style fix * add missing tests * fix test * add missing tests * fix method name in readme * some refactoring * refactor variables name with random server hostname --- .travis.yml | 2 +- README.md | 23 ++++++++ composer.json | 7 +-- src/Client.php | 20 +++++++ src/Cluster.php | 33 +++++++++++- src/Common/File.php | 2 +- src/Common/FileFromString.php | 2 +- src/Common/MergedFiles.php | 2 +- src/Common/ServerOptions.php | 49 +++++++++++++++++ src/Common/TempTable.php | 2 +- src/Exceptions/ClusterException.php | 10 ++++ src/Exceptions/ServerProviderException.php | 10 ++++ src/Interfaces/FileInterface.php | 2 +- src/Query/QueryStatistic.php | 2 +- src/Server.php | 6 +-- src/ServerProvider.php | 37 +++++++++++-- src/Transport/HttpTransport.php | 16 +++--- tests/ClientTest.php | 56 +++++++++++++++++-- tests/ClusterTest.php | 30 +++++++++++ tests/HttpTransportTest.php | 12 ++--- tests/ServerOptionsTest.php | 43 ++++++++++++++- tests/ServerProviderTest.php | 63 ++++++++++++++++++++++ 22 files changed, 396 insertions(+), 33 deletions(-) diff --git a/.travis.yml b/.travis.yml index 75e6e16..1558b54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -23,7 +23,7 @@ before_script: - make && make install - cd ../../ -script: phpunit --coverage-clover ./tests/logs/clover.xml +script: vendor/bin/phpunit --coverage-clover ./tests/logs/clover.xml after_script: - php vendor/bin/php-coveralls -v diff --git a/README.md b/README.md index 860a2e7..18d2e57 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,29 @@ By default client will use random server in given list of servers or in specifie $client->using('server-2')->select('select * from table'); ``` +## Server tags + +```php +$firstServerOptionsWithTag = (new \Tinderbox\Clickhouse\Common\ServerOptions())->setTag('tag'); +$secondServerOptionsWithAnotherTag = (new \Tinderbox\Clickhouse\Common\ServerOptions())->setTag('another-tag'); + +$server = new Tinderbox\Clickhouse\Server('127.0.0.1', '8123', 'default', 'user', 'pass', $firstServerOptionsWithTag); + +$cluster = new Tinderbox\Clickhouse\Cluster('cluster', [ + new Tinderbox\Clickhouse\Server('127.0.0.2', '8123', 'default', 'user', 'pass', $secondServerOptionsWithAnotherTag) +]); + +$serverProvider = (new Tinderbox\Clickhouse\ServerProvider())->addServer($server)->addCluster($cluster); + +$client = (new Tinderbox\Clickhouse\Client($serverProvider)); +``` + +To use server with tag, you should call ```usingServerWithTag``` function before execute any query. + +```php +$client->usingServerWithTag('tag'); +``` + ## Select queries Any SELECT query will return instance of `Result`. This class implements interfaces `\ArrayAccess`, `\Countable` и `\Iterator`, diff --git a/composer.json b/composer.json index 49e9ded..7e582ef 100644 --- a/composer.json +++ b/composer.json @@ -19,7 +19,8 @@ }, "require": { "php": "~7.1", - "guzzlehttp/guzzle": "~6.0" + "guzzlehttp/guzzle": "~6.0", + "satooshi/php-coveralls": "^2.2" }, "bin": [ "bin/ccat_linux", @@ -27,8 +28,8 @@ ], "require-dev": { "mockery/mockery": "^0.9.9", - "phpunit/php-code-coverage": "^5.2", - "phpunit/phpunit": "~4.0||~5.0||~6.0" + "phpunit/php-code-coverage": "^6.0", + "phpunit/phpunit": "~7.0" }, "scripts": { "test": "phpunit --coverage-text --colors=never" diff --git a/src/Client.php b/src/Client.php index bc92661..4725da6 100644 --- a/src/Client.php +++ b/src/Client.php @@ -147,6 +147,26 @@ public function usingRandomServer() return $this; } + /** + * Client will use server with tag as server for queries. + * + * @var string + * + * @return $this + */ + public function usingServerWithTag(string $tag) + { + $this->serverHostname = function () use ($tag) { + if ($this->isOnCluster()) { + return $this->serverProvider->getRandomServerFromClusterByTag($this->getClusterName(), $tag); + } else { + return $this->serverProvider->getRandomServerWithTag($tag); + } + }; + + return $this; + } + /** * Returns true if cluster selected. * diff --git a/src/Cluster.php b/src/Cluster.php index 4dbfb36..3873b21 100644 --- a/src/Cluster.php +++ b/src/Cluster.php @@ -23,6 +23,13 @@ class Cluster */ protected $servers = []; + /** + * Servers in cluster by tags. + * + * @var \Tinderbox\Clickhouse\Server[][] + */ + protected $serversByTags = []; + /** * Cluster constructor. * @@ -86,6 +93,12 @@ public function addServer(string $hostname, Server $server) } $this->servers[$hostname] = $server; + + $serverTags = $server->getOptions()->getTags(); + + foreach ($serverTags as $serverTag) { + $this->serversByTags[$serverTag][$hostname] = true; + } } /** @@ -98,6 +111,24 @@ public function getServers(): array return $this->servers; } + /** + * Returns servers in cluster by tag. + * + * @param string $tag + * + * @throws ClusterException + * + * @return \Tinderbox\Clickhouse\Server[] + */ + public function getServersByTag(string $tag): array + { + if (!isset($this->serversByTags[$tag])) { + throw ClusterException::tagNotFound($tag); + } + + return $this->serversByTags[$tag]; + } + /** * Returns server by specified hostname. * @@ -121,7 +152,7 @@ public function getServerByHostname(string $hostname): Server * * @return string */ - public function getName() : string + public function getName(): string { return $this->name; } diff --git a/src/Common/File.php b/src/Common/File.php index 8fde4af..5ea14e0 100644 --- a/src/Common/File.php +++ b/src/Common/File.php @@ -8,7 +8,7 @@ class File extends AbstractFile implements FileInterface { - public function open(bool $gzip = true) : StreamInterface + public function open(bool $gzip = true): StreamInterface { $handle = fopen($this->getSource(), 'r'); diff --git a/src/Common/FileFromString.php b/src/Common/FileFromString.php index 33441ef..cad4be3 100644 --- a/src/Common/FileFromString.php +++ b/src/Common/FileFromString.php @@ -8,7 +8,7 @@ class FileFromString extends AbstractFile implements FileInterface { - public function open(bool $gzip = true) : StreamInterface + public function open(bool $gzip = true): StreamInterface { $handle = fopen('php://memory', 'r+'); fwrite($handle, $this->source); diff --git a/src/Common/MergedFiles.php b/src/Common/MergedFiles.php index 71154a1..380253d 100644 --- a/src/Common/MergedFiles.php +++ b/src/Common/MergedFiles.php @@ -14,7 +14,7 @@ protected function getCcatPath(): string return __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'bin'.DIRECTORY_SEPARATOR.'ccat_'.strtolower(PHP_OS); } - public function open(bool $gzip = true) : StreamInterface + public function open(bool $gzip = true): StreamInterface { $descriptorspec = [ 0 => ['pipe', 'r'], diff --git a/src/Common/ServerOptions.php b/src/Common/ServerOptions.php index 212a1a5..ee97fde 100644 --- a/src/Common/ServerOptions.php +++ b/src/Common/ServerOptions.php @@ -14,6 +14,13 @@ class ServerOptions */ protected $protocol = 'http'; + /** + * Tags. + * + * @var array + */ + protected $tags = []; + /** * Sets protocol. * @@ -37,4 +44,46 @@ public function getProtocol(): string { return $this->protocol; } + + /** + * Set tags. + * + * @param array $tags + * + * @return ServerOptions + */ + public function setTags(array $tags): self + { + $this->tags = []; + + foreach ($tags as $tag) { + $this->addTag($tag); + } + + return $this; + } + + /** + * Adds tag. + * + * @param string $tag + * + * @return ServerOptions + */ + public function addTag(string $tag): self + { + $this->tags[$tag] = true; + + return $this; + } + + /** + * Returns tags. + * + * @return array + */ + public function getTags(): array + { + return array_keys($this->tags); + } } diff --git a/src/Common/TempTable.php b/src/Common/TempTable.php index 8fb4245..9aad633 100644 --- a/src/Common/TempTable.php +++ b/src/Common/TempTable.php @@ -94,7 +94,7 @@ public function getFormat(): string return $this->format; } - public function open(bool $gzip = true) : StreamInterface + public function open(bool $gzip = true): StreamInterface { return $this->source->open($gzip); } diff --git a/src/Exceptions/ClusterException.php b/src/Exceptions/ClusterException.php index 18f3f25..a0ba037 100644 --- a/src/Exceptions/ClusterException.php +++ b/src/Exceptions/ClusterException.php @@ -21,4 +21,14 @@ public static function serverNotFound($hostname) { return new static('Server with hostname ['.$hostname.'] is not found in cluster'); } + + public static function tagNotFound($tag) + { + return new static('There are no servers with tag ['.$tag.'] in cluster'); + } + + public static function serverNotFoundByTag($tag, $hostname) + { + return new static('Server with hostname ['.$hostname.'] and tag ['.$tag.'] is not found in cluster'); + } } diff --git a/src/Exceptions/ServerProviderException.php b/src/Exceptions/ServerProviderException.php index 2672a65..ac56f85 100644 --- a/src/Exceptions/ServerProviderException.php +++ b/src/Exceptions/ServerProviderException.php @@ -26,4 +26,14 @@ public static function serverHostnameNotFound($hostname) { return new static('Can not find server with hostname ['.$hostname.']'); } + + public static function serverTagNotFound($tag) + { + return new static('Can not find servers with tag ['.$tag.']'); + } + + public static function serverHostnameNotFoundForTag($tag, $hostname) + { + return new static('Can not find servers with hostname ['.$hostname.'] and tag ['.$tag.']'); + } } diff --git a/src/Interfaces/FileInterface.php b/src/Interfaces/FileInterface.php index 7fa13a0..5bfe387 100644 --- a/src/Interfaces/FileInterface.php +++ b/src/Interfaces/FileInterface.php @@ -6,5 +6,5 @@ interface FileInterface { - public function open(bool $gzip = true) : StreamInterface; + public function open(bool $gzip = true): StreamInterface; } diff --git a/src/Query/QueryStatistic.php b/src/Query/QueryStatistic.php index bd18718..fd0c3cf 100644 --- a/src/Query/QueryStatistic.php +++ b/src/Query/QueryStatistic.php @@ -14,7 +14,7 @@ * @property int $rows * @property int $bytes * @property float $time - * @property int $rowsBeforeLimitAtLeast + * @property int $rowsBeforeLimitAtLeast */ class QueryStatistic { diff --git a/src/Server.php b/src/Server.php index ea7ff29..7c49306 100644 --- a/src/Server.php +++ b/src/Server.php @@ -64,9 +64,9 @@ class Server public function __construct( string $host, string $port = '8123', - string $database = 'default', - string $username = null, - string $password = null, + ?string $database = 'default', + ?string $username = null, + ?string $password = null, ServerOptions $options = null ) { $this->setHost($host); diff --git a/src/ServerProvider.php b/src/ServerProvider.php index c8250f7..d279a18 100644 --- a/src/ServerProvider.php +++ b/src/ServerProvider.php @@ -23,6 +23,13 @@ class ServerProvider */ protected $servers = []; + /** + * Servers by tags. + * + * @var Server[][] + */ + protected $serversByTags = []; + /** * Current server to perform queries. * @@ -60,6 +67,12 @@ public function addServer(Server $server) $this->servers[$serverHostname] = $server; + $serverTags = $server->getOptions()->getTags(); + + foreach ($serverTags as $serverTag) { + $this->serversByTags[$serverTag][$serverHostname] = true; + } + return $this; } @@ -68,12 +81,30 @@ public function getRandomServer(): Server return $this->getServer(array_rand($this->servers, 1)); } + public function getRandomServerWithTag(string $tag): Server + { + if (!isset($this->serversByTags[$tag])) { + throw ServerProviderException::serverTagNotFound($tag); + } + + return $this->getServer(array_rand($this->serversByTags[$tag], 1)); + } + public function getRandomServerFromCluster(string $cluster): Server { $cluster = $this->getCluster($cluster); - $randomServerIndex = array_rand($cluster->getServers(), 1); + $randomServerHostname = array_rand($cluster->getServers(), 1); + + return $cluster->getServerByHostname($randomServerHostname); + } + + public function getRandomServerFromClusterByTag(string $cluster, string $tag): Server + { + $cluster = $this->getCluster($cluster); + + $randomServerHostname = array_rand($cluster->getServersByTag($tag), 1); - return $cluster->getServerByHostname($randomServerIndex); + return $cluster->getServerByHostname($randomServerHostname); } public function getServerFromCluster(string $cluster, string $serverHostname) @@ -92,7 +123,7 @@ public function getServer(string $serverHostname): Server return $this->servers[$serverHostname]; } - public function getCluster(string $cluster) : Cluster + public function getCluster(string $cluster): Cluster { if (!isset($this->clusters[$cluster])) { throw ServerProviderException::clusterNotFound($cluster); diff --git a/src/Transport/HttpTransport.php b/src/Transport/HttpTransport.php index 3baa82d..efd01cb 100644 --- a/src/Transport/HttpTransport.php +++ b/src/Transport/HttpTransport.php @@ -62,7 +62,7 @@ public function __construct(Client $client = null, array $options = []) } /** - * Returns flag to enable / disable queries and data compression + * Returns flag to enable / disable queries and data compression. * * @return bool */ @@ -121,7 +121,7 @@ protected function createHttpClient() * * @return array */ - public function write(array $queries, int $concurrency = 5) : array + public function write(array $queries, int $concurrency = 5): array { $result = []; $openedStreams = []; @@ -159,9 +159,11 @@ public function write(array $queries, int $concurrency = 5) : array $queryResult = []; $pool = new Pool( - $this->httpClient, $requests($query), [ + $this->httpClient, + $requests($query), + [ 'concurrency' => $concurrency, - 'fulfilled' => function ($response, $index) use (&$queryResult, $query) { + 'fulfilled' => function ($response, $index) use (&$queryResult) { $queryResult[$index] = true; }, 'rejected' => $this->parseReason($query), @@ -195,7 +197,7 @@ public function write(array $queries, int $concurrency = 5) : array return $result; } - public function read(array $queries, int $concurrency = 5) : array + public function read(array $queries, int $concurrency = 5): array { $openedStreams = []; @@ -241,7 +243,9 @@ public function read(array $queries, int $concurrency = 5) : array $result = []; $pool = new Pool( - $this->httpClient, $requests($queries), [ + $this->httpClient, + $requests($queries), + [ 'concurrency' => $concurrency, 'fulfilled' => function ($response, $index) use (&$result, $queries) { $result[$index] = $this->assembleResult($queries[$index], $response); diff --git a/tests/ClientTest.php b/tests/ClientTest.php index 9f9ec2c..d16c1a0 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\TestCase; use Tinderbox\Clickhouse\Common\FileFromString; use Tinderbox\Clickhouse\Common\Format; +use Tinderbox\Clickhouse\Common\ServerOptions; use Tinderbox\Clickhouse\Common\TempTable; use Tinderbox\Clickhouse\Exceptions\ClusterException; use Tinderbox\Clickhouse\Exceptions\ServerProviderException; @@ -55,7 +56,8 @@ public function testTransports() public function testClusters() { $cluster = new Cluster( - 'test', [ + 'test', + [ new Server('127.0.0.1'), new Server('127.0.0.2'), new Server('127.0.0.3'), @@ -63,7 +65,8 @@ public function testClusters() ); $cluster2 = new Cluster( - 'test2', [ + 'test2', + [ new Server('127.0.0.4'), new Server('127.0.0.5'), new Server('127.0.0.6'), @@ -152,10 +155,57 @@ public function testServers() $client->using('127.0.0.0')->getServer(); } + public function testServersWithTags() + { + $serverOptionsWithTag = (new ServerOptions())->addTag('tag'); + + $serverWithTag = (new Server('127.0.0.1', 8123))->setOptions($serverOptionsWithTag); + $serverWithoutTag = new Server('127.0.0.2', 8123); + + $serverProvider = new ServerProvider(); + $serverProvider->addServer($serverWithTag)->addServer($serverWithoutTag); + + $client = new Client($serverProvider); + $client->usingServerWithTag('tag'); + + $server = $client->getServer(); + + $this->assertEquals('127.0.0.1', $server->getHost()); + $this->assertEquals(8123, $server->getPort()); + } + + public function testServersWithTagsOnCluster() + { + $serverOptionsWithTag = (new ServerOptions())->addTag('tag'); + + $serverWithTag = (new Server('127.0.0.1', 8123))->setOptions($serverOptionsWithTag); + $serverWithoutTag = new Server('127.0.0.2', 8123); + + $cluster = new Cluster( + 'test', + [ + $serverWithTag, + $serverWithoutTag, + ] + ); + + $serverProvider = new ServerProvider(); + $serverProvider->addCluster($cluster); + + $client = new Client($serverProvider); + $client->onCluster('test')->usingServerWithTag('tag'); + + $server = $client->getServer(); + + $this->assertEquals('127.0.0.1', $server->getHost()); + $this->assertEquals(8123, $server->getPort()); + } + public function testClusterAndServersTogether() { $cluster = new Cluster( - 'test', [ + 'test', + [ new Server('127.0.0.1'), new Server('127.0.0.2'), new Server('127.0.0.3'), diff --git a/tests/ClusterTest.php b/tests/ClusterTest.php index 5413150..5beb154 100644 --- a/tests/ClusterTest.php +++ b/tests/ClusterTest.php @@ -87,4 +87,34 @@ public function testServersDuplicates() new Cluster('test_cluster', $servers); } + + public function testServersWithTags() + { + $server = [ + 'host' => '127.0.0.1', + 'port' => 8123, + 'database' => 'default', + 'username' => 'default', + 'password' => '', + 'options' => (new ServerOptions())->addTag('tag'), + ]; + + $cluster = new Cluster('test', [ + $server, + ]); + + $servers = $cluster->getServersByTag('tag'); + + $this->assertEquals(array_keys($servers)[0], $server['host']); + } + + public function testServerTagNotFound() + { + $cluster = new Cluster('test', []); + + $this->expectException(ClusterException::class); + $this->expectExceptionMessage('There are no servers with tag [tag] in cluster'); + + $cluster->getServersByTag('tag'); + } } diff --git a/tests/HttpTransportTest.php b/tests/HttpTransportTest.php index 2bd4dd9..19d18a3 100644 --- a/tests/HttpTransportTest.php +++ b/tests/HttpTransportTest.php @@ -19,7 +19,7 @@ */ class HttpTransportTest extends TestCase { - protected function getMockedTransport(array $responses) : HttpTransport + protected function getMockedTransport(array $responses): HttpTransport { $mock = new MockHandler($responses); @@ -30,17 +30,17 @@ protected function getMockedTransport(array $responses) : HttpTransport ])); } - protected function getQuery() : Query + protected function getQuery(): Query { return new Query($this->getServer(), 'select * from table'); } - protected function getServer() : Server + protected function getServer(): Server { return new Server(CLICKHOUSE_SERVER_HOST, CLICKHOUSE_SERVER_PORT, 'default', 'default'); } - protected function getTransport() : HttpTransport + protected function getTransport(): HttpTransport { return new HttpTransport(); } @@ -377,7 +377,7 @@ public function testConnectionWithPassword() $transport = $this->getTransport(); $this->expectException(TransportException::class); - $this->expectExceptionMessageRegExp('/Wrong password for user default/'); + $this->expectExceptionMessageRegExp('/Authentication failed: password is incorrect/'); $transport->read([ new Query(new Server('127.0.0.1', 8123, 'default', 'default', 'pass'), 'select 1', [ @@ -391,7 +391,7 @@ public function testConnectionWithPasswordOnWrite() $transport = $this->getTransport(); $this->expectException(TransportException::class); - $this->expectExceptionMessageRegExp('/Wrong password for user default/'); + $this->expectExceptionMessageRegExp('/Authentication failed: password is incorrect/'); $transport->write([ new Query(new Server('127.0.0.1', 8123, 'default', 'default', 'pass'), 'insert into table 1', [ diff --git a/tests/ServerOptionsTest.php b/tests/ServerOptionsTest.php index 3dd3a3f..b40cc84 100644 --- a/tests/ServerOptionsTest.php +++ b/tests/ServerOptionsTest.php @@ -10,7 +10,7 @@ */ class ServerOptionsTest extends TestCase { - public function testServerOptions() + public function testProtocolFromServerOptions() { $options = new ServerOptions(); @@ -20,4 +20,45 @@ public function testServerOptions() $this->assertEquals('https', $options->getProtocol(), 'Sets correct protocol'); } + + public function testTagsFromServerOptions() + { + $options = new ServerOptions(); + + $options->addTag('test'); + + $this->assertTrue( + in_array('test', $options->getTags(), true), + 'Sets correct tags' + ); + + $options->addTag('tag'); + + $this->assertTrue( + in_array('test', $options->getTags(), true), + 'Sets correct tags' + ); + $this->assertTrue( + in_array('tag', $options->getTags(), true), + 'Sets correct tags' + ); + + $options->setTags(['other']); + + $this->assertTrue( + in_array('other', $options->getTags(), true), + 'Sets correct tags' + ); + + $options->addTag('tag'); + + $this->assertTrue( + in_array('other', $options->getTags(), true), + 'Sets correct tags' + ); + $this->assertTrue( + in_array('tag', $options->getTags(), true), + 'Sets correct tags' + ); + } } diff --git a/tests/ServerProviderTest.php b/tests/ServerProviderTest.php index a29d330..eb462d0 100644 --- a/tests/ServerProviderTest.php +++ b/tests/ServerProviderTest.php @@ -3,6 +3,8 @@ namespace Tinderbox\Clickhouse; use PHPUnit\Framework\TestCase; +use Tinderbox\Clickhouse\Common\ServerOptions; +use Tinderbox\Clickhouse\Exceptions\ClusterException; use Tinderbox\Clickhouse\Exceptions\ServerProviderException; /** @@ -107,4 +109,65 @@ public function testServerNotFound() $provider->getServer('127.0.0.1'); } + + public function testServersWithTags() + { + $serverOptionsWithTag = (new ServerOptions())->addTag('tag'); + + $serverWithTag = new Server('127.0.0.1', 8123, 'default', 'default', '', $serverOptionsWithTag); + $serverWithoutTag = new Server('127.0.0.2', 8123); + + $provider = new ServerProvider(); + $provider->addServer($serverWithTag); + $provider->addServer($serverWithoutTag); + + $server = $provider->getRandomServerWithTag('tag'); + $this->assertEquals($server->getHost(), $serverWithTag->getHost(), 'Correctly adds server with tag and returns it'); + } + + public function testServerTagNotFound() + { + $provider = new ServerProvider(); + + $this->expectException(ServerProviderException::class); + $this->expectExceptionMessage('Can not find servers with tag [tag]'); + + $provider->getRandomServerWithTag('tag'); + } + + public function testClustersWithServersWithTag() + { + $serverOptionsWithTag = (new ServerOptions())->addTag('tag'); + + $serverWithTag = new Server('127.0.0.1', 8123, 'default', 'default', '', $serverOptionsWithTag); + $serverWithoutTag = new Server('127.0.0.2'); + + $servers = [ + $serverWithTag, + $serverWithoutTag, + ]; + + $cluster = new Cluster('test', $servers); + + $provider = new ServerProvider(); + $provider->addCluster($cluster); + + $this->assertEquals($serverWithTag, $provider->getRandomServerFromClusterByTag('test', 'tag'), 'Correctly returns server from cluster by tag'); + } + + public function testServerTagNotFoundInCluster() + { + $servers = [ + new Server('127.0.0.1'), + ]; + $cluster = new Cluster('test', $servers); + + $provider = new ServerProvider(); + $provider->addCluster($cluster); + + $this->expectException(ClusterException::class); + $this->expectExceptionMessage('There are no servers with tag [tag] in cluster'); + + $provider->getRandomServerFromClusterByTag('test', 'tag'); + } }