Skip to content

Commit a8e43bb

Browse files
committed
Retryable reads prose tests
1 parent 8a484d9 commit a8e43bb

File tree

1 file changed

+160
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)