Skip to content

Commit 81e026d

Browse files
rfblue2Roland Fong
authored and
Roland Fong
committed
Add Retryable Writes Tests
GODRIVER-53 Change-Id: Ie6a5c9488e1c439026cc394d0ef9b259bf9bad64
1 parent 157e037 commit 81e026d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+6955
-90
lines changed

core/topology/topology_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"time"
1515

1616
"github.com/mongodb/mongo-go-driver/core/address"
17+
"github.com/mongodb/mongo-go-driver/core/command"
1718
"github.com/mongodb/mongo-go-driver/core/description"
1819
)
1920

@@ -223,6 +224,74 @@ func TestServerSelection(t *testing.T) {
223224
t.Errorf("findServer does not properly set the topology description kind. got %v; want %v", ss.Kind, description.Single)
224225
}
225226
})
227+
t.Run("Update on not master error", func(t *testing.T) {
228+
topo, err := New()
229+
noerr(t, err)
230+
topo.cfg.cs.HeartbeatInterval = time.Minute
231+
atomic.StoreInt32(&topo.connectionstate, connected)
232+
233+
addr1 := address.Address("one")
234+
addr2 := address.Address("two")
235+
addr3 := address.Address("three")
236+
desc := description.Topology{
237+
Servers: []description.Server{
238+
{Addr: addr1, Kind: description.RSPrimary},
239+
{Addr: addr2, Kind: description.RSSecondary},
240+
{Addr: addr3, Kind: description.RSSecondary},
241+
},
242+
}
243+
244+
// manually add the servers to the topology
245+
for _, srv := range desc.Servers {
246+
s, err := NewServer(srv.Addr)
247+
noerr(t, err)
248+
topo.servers[srv.Addr] = s
249+
}
250+
251+
// Send updated description
252+
desc = description.Topology{
253+
Servers: []description.Server{
254+
{Addr: addr1, Kind: description.RSSecondary},
255+
{Addr: addr2, Kind: description.RSPrimary},
256+
{Addr: addr3, Kind: description.RSSecondary},
257+
},
258+
}
259+
260+
subCh := make(chan description.Topology, 1)
261+
subCh <- desc
262+
263+
// send a not master error to the server forcing an update
264+
serv, err := topo.FindServer(desc.Servers[0])
265+
noerr(t, err)
266+
err = serv.pool.Connect(context.Background())
267+
noerr(t, err)
268+
atomic.StoreInt32(&serv.connectionstate, connected)
269+
sc := &sconn{s: serv.Server}
270+
sc.processErr(command.Error{Message: "not master"})
271+
272+
resp := make(chan []description.Server)
273+
274+
go func() {
275+
// server selection should discover the new topology
276+
srvs, err := topo.selectServer(context.Background(), subCh, description.WriteSelector(), nil)
277+
noerr(t, err)
278+
resp <- srvs
279+
}()
280+
281+
var srvs []description.Server
282+
select {
283+
case srvs = <-resp:
284+
case <-time.After(100 * time.Millisecond):
285+
t.Errorf("Timed out while trying to retrieve selected servers")
286+
}
287+
288+
if len(srvs) != 1 {
289+
t.Errorf("Incorrect number of descriptions returned. got %d; want %d", len(srvs), 1)
290+
}
291+
if srvs[0].Addr != desc.Servers[1].Addr {
292+
t.Errorf("Incorrect sever selected. got %s; want %s", srvs[0].Addr, desc.Servers[1].Addr)
293+
}
294+
})
226295
}
227296

228297
func TestSessionTimeout(t *testing.T) {

data/retryable-writes/README.rst

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
=====================
2+
Retryable Write Tests
3+
=====================
4+
5+
.. contents::
6+
7+
----
8+
9+
Introduction
10+
============
11+
12+
The YAML and JSON files in this directory tree are platform-independent tests
13+
that drivers can use to prove their conformance to the Retryable Writes spec.
14+
15+
Several prose tests, which are not easily expressed in YAML, are also presented
16+
in this file. Those tests will need to be manually implemented by each driver.
17+
18+
Tests will require a MongoClient created with options defined in the tests.
19+
Integration tests will require a running MongoDB cluster with server versions
20+
3.6.0 or later. The ``{setFeatureCompatibilityVersion: 3.6}`` admin command
21+
will also need to have been executed to enable support for retryable writes on
22+
the cluster.
23+
24+
Server Fail Point
25+
=================
26+
27+
The tests depend on a server fail point, ``onPrimaryTransactionalWrite``, which
28+
allows us to force a network error before the server would return a write result
29+
to the client. The fail point also allows control whether the server will
30+
successfully commit the write via its ``failBeforeCommitExceptionCode`` option.
31+
Keep in mind that the fail point only triggers for transaction writes (i.e. write
32+
commands including ``txnNumber`` and ``lsid`` fields). See `SERVER-29606`_ for
33+
more information.
34+
35+
.. _SERVER-29606: https://jira.mongodb.org/browse/SERVER-29606
36+
37+
The fail point may be configured like so::
38+
39+
db.runCommand({
40+
configureFailPoint: "onPrimaryTransactionalWrite",
41+
mode: <string|document>,
42+
data: <document>
43+
});
44+
45+
``mode`` is a generic fail point option and may be assigned a string or document
46+
value. The string values ``"alwaysOn"`` and ``"off"`` may be used to enable or
47+
disable the fail point, respectively. A document may be used to specify either
48+
``times`` or ``skip``, which are mutually exclusive:
49+
50+
- ``{ times: <integer> }`` may be used to limit the number of times the fail
51+
point may trigger before transitioning to ``"off"``.
52+
- ``{ skip: <integer> }`` may be used to defer the first trigger of a fail
53+
point, after which it will transition to ``"alwaysOn"``.
54+
55+
The ``data`` option is a document that may be used to specify options that
56+
control the fail point's behavior. As noted in `SERVER-29606`_,
57+
``onPrimaryTransactionalWrite`` supports the following ``data`` options, which
58+
may be combined if desired:
59+
60+
- ``closeConnection``: Boolean option, which defaults to ``true``. If ``true``,
61+
the connection on which the write is executed will be closed before a result
62+
can be returned.
63+
- ``failBeforeCommitExceptionCode``: Integer option, which is unset by default.
64+
If set, the specified exception code will be thrown and the write will not be
65+
committed. If unset, the write will be allowed to commit.
66+
67+
Disabling Fail Point after Test Execution
68+
-----------------------------------------
69+
70+
After each test that configures a fail point, drivers should disable the
71+
``onPrimaryTransactionalWrite`` fail point to avoid spurious failures in
72+
subsequent tests. The fail point may be disabled like so::
73+
74+
db.runCommand({
75+
configureFailPoint: "onPrimaryTransactionalWrite",
76+
mode: "off"
77+
});
78+
79+
Network Error Tests
80+
===================
81+
82+
Network error tests are expressed in YAML and should be run against a replica
83+
set. These tests cannot be run against a shard cluster because mongos does not
84+
support the necessary fail point.
85+
86+
The tests exercise the following scenarios:
87+
88+
- Single-statement write operations
89+
90+
- Each test expecting a write result will encounter at-most one network error
91+
for the write command. Retry attempts should return without error and allow
92+
operation to succeed. Observation of the collection state will assert that
93+
the write occurred at-most once.
94+
95+
- Each test expecting an error will encounter successive network errors for
96+
the write command. Observation of the collection state will assert that the
97+
write was never committed on the server.
98+
99+
- Multi-statement write operations
100+
101+
- Each test expecting a write result will encounter at-most one network error
102+
for some write command(s) in the batch. Retry attempts should return without
103+
error and allow the batch to ultimately succeed. Observation of the
104+
collection state will assert that each write occurred at-most once.
105+
106+
- Each test expecting an error will encounter successive network errors for
107+
some write command in the batch. The batch will ultimately fail with an
108+
error, but observation of the collection state will assert that the failing
109+
write was never committed on the server. We may observe that earlier writes
110+
in the batch occurred at-most once.
111+
112+
We cannot test a scenario where the first and second attempts both encounter
113+
network errors but the write does actually commit during one of those attempts.
114+
This is because (1) the fail point only triggers when a write would be committed
115+
and (2) the skip and times options are mutually exclusive. That said, such a
116+
test would mainly assert the server's correctness for at-most once semantics and
117+
is not essential to assert driver correctness.
118+
119+
Test Format
120+
-----------
121+
122+
Each YAML file has the following keys:
123+
124+
- ``data``: The data that should exist in the collection under test before each
125+
test run.
126+
127+
- ``minServerVersion`` (optional): The minimum server version (inclusive)
128+
required to successfully run the test. If this field is not present, it should
129+
be assumed that there is no lower bound on the required server version.
130+
131+
- ``maxServerVersion`` (optional): The maximum server version (exclusive)
132+
against which this test can run successfully. If this field is not present,
133+
it should be assumed that there is no upper bound on the required server
134+
version.
135+
136+
- ``tests``: An array of tests that are to be run independently of each other.
137+
Each test will have some or all of the following fields:
138+
139+
- ``description``: The name of the test.
140+
141+
- ``clientOptions``: Parameters to pass to MongoClient().
142+
143+
- ``failPoint``: The ``configureFailPoint`` command document to run to
144+
configure a fail point on the primary server. Drivers must ensure that
145+
``configureFailPoint`` is the first field in the command.
146+
147+
- ``operation``: Document describing the operation to be executed. The
148+
operation should be executed through a collection object derived from a
149+
client that has been created with ``clientOptions``. The operation will have
150+
some or all of the following fields:
151+
152+
- ``name``: The name of the operation as defined in the CRUD specification.
153+
154+
- ``arguments``: The names and values of arguments from the CRUD
155+
specification.
156+
157+
- ``outcome``: Document describing the return value and/or expected state of
158+
the collection after the operation is executed. This will have some or all
159+
of the following fields:
160+
161+
- ``error``: If ``true``, the test should expect an error or exception. Note
162+
that some drivers may report server-side errors as a write error within a
163+
write result object.
164+
165+
- ``result``: The return value from the operation. This will correspond to
166+
an operation's result object as defined in the CRUD specification. This
167+
field may be omitted if ``error`` is ``true``. If this field is present
168+
and ``error`` is ``true`` (generally for multi-statement tests), the
169+
result reports information about operations that succeeded before an
170+
unrecoverable failure. In that case, drivers may choose to check the
171+
result object if their BulkWriteException (or equivalent) provides access
172+
to a write result object.
173+
174+
- ``collection``:
175+
176+
- ``name`` (optional): The name of the collection to verify. If this isn't
177+
present then use the collection under test.
178+
179+
- ``data``: The data that should exist in the collection after the
180+
operation has been run.
181+
182+
Split Batch Tests
183+
=================
184+
185+
The YAML tests specify bulk write operations that are split by command type
186+
(e.g. sequence of insert, update, and delete commands). Multi-statement write
187+
operations may also be split due to ``maxWriteBatchSize``,
188+
``maxBsonObjectSize``, or ``maxMessageSizeBytes``.
189+
190+
For instance, an insertMany operation with five 10 MB documents executed using
191+
OP_MSG payload type 0 (i.e. entire command in one document) would be split into
192+
five insert commands in order to respect the 16 MB ``maxBsonObjectSize`` limit.
193+
The same insertMany operation executed using OP_MSG payload type 1 (i.e. command
194+
arguments pulled out into a separate payload vector) would be split into two
195+
insert commands in order to respect the 48 MB ``maxMessageSizeBytes`` limit.
196+
197+
Noting when a driver might split operations, the ``onPrimaryTransactionalWrite``
198+
fail point's ``skip`` option may be used to control when the fail point first
199+
triggers. Once triggered, the fail point will transition to the ``alwaysOn``
200+
state until disabled. Driver authors should also note that the server attempts
201+
to process all documents in a single insert command within a single commit (i.e.
202+
one insert command with five documents may only trigger the fail point once).
203+
This behavior is unique to insert commands (each statement in an update and
204+
delete command is processed independently).
205+
206+
If testing an insert that is split into two commands, a ``skip`` of one will
207+
allow the fail point to trigger on the second insert command (because all
208+
documents in the first command will be processed in the same commit). When
209+
testing an update or delete that is split into two commands, the ``skip`` should
210+
be set to the number of statements in the first command to allow the fail point
211+
to trigger on the second command.
212+
213+
Replica Set Failover Test
214+
=========================
215+
216+
In addition to network errors, writes should also be retried in the event of a
217+
primary failover, which results in a "not master" command error (or similar).
218+
The ``stepdownHangBeforePerformingPostMemberStateUpdateActions`` fail point
219+
implemented in `d4eb562`_ for `SERVER-31355`_ may be used for this test, as it
220+
allows a primary to keep its client connections open after a step down. This
221+
fail point operates by hanging the step down procedure (i.e. ``replSetStepDown``
222+
command) until the fail point is later deactivated.
223+
224+
.. _d4eb562: https://github.com/mongodb/mongo/commit/d4eb562ac63717904f24de4a22e395070687bc62
225+
.. _SERVER-31355: https://jira.mongodb.org/browse/SERVER-31355
226+
227+
The following test requires three MongoClient instances and will generally
228+
require two execution contexts (async drivers may get by with a single thread).
229+
230+
- The client under test will connect to the replica set and be used to execute
231+
write operations.
232+
- The fail point client will connect directly to the initial primary and be used
233+
to toggle the fail point.
234+
- The step down client will connect to the replica set and be used to step down
235+
the primary. This client will generally require its own execution context,
236+
since the step down will hang.
237+
238+
In order to guarantee that the client under test does not detect the stepped
239+
down primary's state change via SDAM, it must be configured with a large
240+
`heartbeatFrequencyMS`_ value (e.g. 60 seconds). Single-threaded drivers may
241+
also need to set `serverSelectionTryOnce`_ to ``false`` to ensure that server
242+
selection for the retry attempt waits until a new primary is elected.
243+
244+
.. _heartbeatFrequencyMS: https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#heartbeatfrequencyms
245+
.. _serverSelectionTryOnce: https://github.com/mongodb/specifications/blob/master/source/server-selection/server-selection.rst#serverselectiontryonce
246+
247+
The test proceeds as follows:
248+
249+
- Using the client under test, insert a document and observe a successful write
250+
result. This will ensure that initial discovery takes place.
251+
- Using the fail point client, activate the fail point by setting ``mode``
252+
to ``"alwaysOn"``.
253+
- Using the step down client, step down the primary by executing the command
254+
``{ replSetStepDown: 60, force: true}``. This operation will hang so long as
255+
the fail point is activated. When the fail point is later deactivated, the
256+
step down will complete and the primary's client connections will be dropped.
257+
At that point, any ensuing network error should be ignored.
258+
- Using the client under test, insert a document and observe a successful write
259+
result. The test MUST assert that the insert command fails once against the
260+
stepped down node and is successfully retried on the newly elected primary
261+
(after SDAM discovers the topology change). The test MAY use APM or another
262+
means to observe both attempts.
263+
- Using the fail point client, deactivate the fail point by setting ``mode``
264+
to ``"off"``.
265+
266+
Command Construction Tests
267+
==========================
268+
269+
Drivers should also assert that command documents are properly constructed with
270+
or without a transaction ID, depending on whether the write operation is
271+
supported. `Command Monitoring`_ may be used to check for the presence of a
272+
``txnNumber`` field in the command document. Note that command documents may
273+
always include an ``lsid`` field per the `Driver Session`_ specification.
274+
275+
.. _Command Monitoring: ../../command-monitoring/command-monitoring.rst
276+
.. _Driver Session: ../../sessions/driver-sessions.rst
277+
278+
These tests may be run against both a replica set and shard cluster.
279+
280+
Drivers should test that transaction IDs are never included in commands for
281+
unsupported write operations:
282+
283+
* Write commands with unacknowledged write concerns (e.g. ``{w: 0}``)
284+
285+
* Unsupported single-statement write operations
286+
287+
- ``updateMany()``
288+
- ``deleteMany()``
289+
290+
* Unsupported multi-statement write operations
291+
292+
- ``bulkWrite()`` that includes ``UpdateMany`` or ``DeleteMany``
293+
294+
* Unsupported write commands
295+
296+
- ``aggregate`` with ``$out`` pipeline operator
297+
298+
Drivers should test that transactions IDs are always included in commands for
299+
supported write operations:
300+
301+
* Supported single-statement write operations
302+
303+
- ``insertOne()``
304+
- ``updateOne()``
305+
- ``replaceOne()``
306+
- ``deleteOne()``
307+
- ``findOneAndDelete()``
308+
- ``findOneAndReplace()``
309+
- ``findOneAndUpdate()``
310+
311+
* Supported multi-statement write operations
312+
313+
- ``insertMany()`` with ``ordered=true``
314+
- ``insertMany()`` with ``ordered=false``
315+
- ``bulkWrite()`` with ``ordered=true`` (no ``UpdateMany`` or ``DeleteMany``)
316+
- ``bulkWrite()`` with ``ordered=false`` (no ``UpdateMany`` or ``DeleteMany``)

0 commit comments

Comments
 (0)