Skip to content

Commit 6d3c41d

Browse files
committed
Retryable reads prose tests
1 parent c212cb5 commit 6d3c41d

File tree

1 file changed

+167
-0
lines changed

1 file changed

+167
-0
lines changed
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
3+
namespace MongoDB\Tests\SpecTests\RetryableReads;
4+
5+
use MongoDB\Driver\Exception\CommandException;
6+
use MongoDB\Driver\Monitoring\CommandFailedEvent;
7+
use MongoDB\Driver\Monitoring\CommandStartedEvent;
8+
use MongoDB\Driver\Monitoring\CommandSubscriber;
9+
use MongoDB\Driver\Monitoring\CommandSucceededEvent;
10+
use MongoDB\Driver\Server;
11+
use MongoDB\Tests\SpecTests\FunctionalTestCase;
12+
13+
use function assert;
14+
use function count;
15+
16+
/**
17+
* Prose test 2: Retry on different or same mongos
18+
*
19+
* @see https://github.com/mongodb/specifications/blob/master/source/retryable-writes/tests/README.md
20+
*/
21+
class Prose2_RetryOnMongosTest extends FunctionalTestCase
22+
{
23+
public const HOST_UNREACHABLE = 6;
24+
25+
public function testRetryOnDifferentMongos(): void
26+
{
27+
if (! $this->isMongos()) {
28+
$this->markTestSkipped('Test requires connections to mongos');
29+
}
30+
31+
$this->skipIfServerVersion('<', '4.1.7', 'Test requires mongos support for configureFailPoint');
32+
33+
/* By default, the Manager under test is created with a single-mongos
34+
* URI. Explicitly create a Client with multiple mongoses and invoke
35+
* server selection to initialize SDAM. */
36+
$client = static::createTestClient(static::getUri(true), ['retryReads' => true]);
37+
$client->getManager()->selectServer();
38+
39+
/* Step 1: Select servers for each mongos in the cluster.
40+
*
41+
* TODO: Support topologies with 3+ servers by selecting only two and
42+
* recreating a client URI.
43+
*/
44+
$servers = $client->getManager()->getServers();
45+
assert(count($servers) === 2);
46+
47+
// Step 2: Configure the following fail point on each mongos
48+
foreach ($servers as $server) {
49+
$this->configureFailPoint(
50+
[
51+
'configureFailPoint' => 'failCommand',
52+
'mode' => ['times' => 1],
53+
'data' => [
54+
'failCommands' => ['find'],
55+
'errorCode' => self::HOST_UNREACHABLE,
56+
],
57+
],
58+
$server
59+
);
60+
}
61+
62+
/* Step 3: Use the previously created client with retryReads=true,
63+
* which is connected to a cluster with two mongoses */
64+
65+
// Step 4: Enable failed command event monitoring for client
66+
$subscriber = new class implements CommandSubscriber {
67+
public $commandFailedServers = [];
68+
69+
public function commandStarted(CommandStartedEvent $event): void
70+
{
71+
}
72+
73+
public function commandSucceeded(CommandSucceededEvent $event): void
74+
{
75+
}
76+
77+
public function commandFailed(CommandFailedEvent $event): void
78+
{
79+
$this->commandFailedServers[] = $event->getServer();
80+
}
81+
};
82+
83+
$client->addSubscriber($subscriber);
84+
85+
// Step 5: Execute a find command. Assert that the command failed.
86+
try {
87+
$client->selectCollection($this->getDatabaseName(), $this->getCollectionName())->find(['x' => 1]);
88+
$this->fail('BulkWriteException was not thrown');
89+
} catch (CommandException $e) {
90+
$this->assertSame(self::HOST_UNREACHABLE, $e->getCode());
91+
}
92+
93+
$client->removeSubscriber($subscriber);
94+
95+
/* Step 6: Assert that two failed command events occurred. Assert that
96+
* the failed command events occurred on different mongoses. */
97+
$this->assertCount(2, $subscriber->commandFailedServers);
98+
$this->assertNotEquals($subscriber->commandFailedServers[0], $subscriber->commandFailedServers[1]);
99+
100+
// Step 7: The fail points will be disabled during tearDown()
101+
}
102+
103+
public function testRetryOnSameMongos(): void
104+
{
105+
if (! $this->isMongos()) {
106+
$this->markTestSkipped('Test requires connections to mongos');
107+
}
108+
109+
$this->skipIfServerVersion('<', '4.1.7', 'Test requires mongos support for configureFailPoint');
110+
111+
// Step 1: Create a client that connects to a single mongos
112+
$client = static::createTestClient(null, ['directConnection' => false, 'retryReads' => true]);
113+
$server = $client->getManager()->selectServer();
114+
115+
// Step 2: Configure the following fail point on the mongos
116+
$this->configureFailPoint(
117+
[
118+
'configureFailPoint' => 'failCommand',
119+
'mode' => ['times' => 1],
120+
'data' => [
121+
'failCommands' => ['find'],
122+
'errorCode' => self::HOST_UNREACHABLE,
123+
],
124+
],
125+
$server,
126+
);
127+
128+
// Step 3 is omitted because we can re-use the same client
129+
130+
// Step 4: Enable succeeded and failed command event monitoring
131+
$subscriber = new class implements CommandSubscriber {
132+
public Server $commandSucceededServer;
133+
public Server $commandFailedServer;
134+
135+
public function commandStarted(CommandStartedEvent $event): void
136+
{
137+
}
138+
139+
public function commandSucceeded(CommandSucceededEvent $event): void
140+
{
141+
$this->commandSucceededServer = $event->getServer();
142+
}
143+
144+
public function commandFailed(CommandFailedEvent $event): void
145+
{
146+
$this->commandFailedServer = $event->getServer();
147+
}
148+
};
149+
150+
$client->addSubscriber($subscriber);
151+
152+
// Step 5: Execute a find command. Assert that the command succeeded.
153+
$cursor = $client->selectCollection($this->getDatabaseName(), $this->getCollectionName())->find(['x' => 1]);
154+
$this->assertSame([], $cursor->toArray());
155+
156+
$client->removeSubscriber($subscriber);
157+
158+
/* Step 6: Assert that exactly one failed command event and one
159+
* succeeded command event occurred. Assert that both events occurred on
160+
* the same mongos. */
161+
$this->assertNotNull($subscriber->commandSucceededServer);
162+
$this->assertNotNull($subscriber->commandFailedServer);
163+
$this->assertEquals($subscriber->commandSucceededServer, $subscriber->commandFailedServer);
164+
165+
// Step 7: The fail point will be disabled during tearDown()
166+
}
167+
}

0 commit comments

Comments
 (0)