Skip to content

Commit 8fd52e6

Browse files
committed
chore: sync test
1 parent c24f576 commit 8fd52e6

File tree

1 file changed

+185
-171
lines changed

1 file changed

+185
-171
lines changed
Lines changed: 185 additions & 171 deletions
Original file line numberDiff line numberDiff line change
@@ -1,191 +1,205 @@
11
import { expect } from 'chai';
22

3-
import type { Collection, Db, MongoClient } from '../../mongodb';
4-
import { skipBrokenAuthTestBeforeEachHook } from '../../tools/runner/hooks/configuration';
5-
6-
function ignoreNsNotFound(err) {
7-
if (!err.message.match(/ns not found/)) {
8-
throw err;
9-
}
10-
}
11-
12-
function connectionCount(client) {
13-
return client
14-
.db()
15-
.admin()
16-
.serverStatus()
17-
.then(result => result.connections.totalCreated);
18-
}
19-
20-
function expectPoolWasCleared(initialCount) {
21-
return count => expect(count).to.greaterThan(initialCount);
22-
}
23-
24-
function expectPoolWasNotCleared(initialCount) {
25-
return count => expect(count).to.equal(initialCount);
26-
}
27-
28-
// TODO: NODE-3819: Unskip flaky MacOS tests.
29-
// TODO: NODE-3903: check events as specified in the corresponding prose test description
30-
const maybeDescribe = process.platform === 'darwin' ? describe.skip : describe;
31-
maybeDescribe('Connections survive primary step down - prose', function () {
3+
import {
4+
type Collection,
5+
type ConnectionPoolClearedEvent,
6+
type FindCursor,
7+
type MongoClient,
8+
MONGODB_ERROR_CODES,
9+
MongoServerError,
10+
ReadPreference
11+
} from '../../mongodb';
12+
import { type FailPoint } from '../../tools/utils';
13+
14+
describe('Connections Survive Primary Step Down - prose', function () {
3215
let client: MongoClient;
33-
let checkClient: MongoClient;
34-
let db: Db;
3516
let collection: Collection;
17+
let poolClearedEvents: ConnectionPoolClearedEvent[];
3618

37-
beforeEach(
38-
skipBrokenAuthTestBeforeEachHook({
39-
skippedTests: [
40-
'getMore iteration',
41-
'Not Primary - Keep Connection Pool',
42-
'Not Primary - Reset Connection Pool',
43-
'Shutdown in progress - Reset Connection Pool',
44-
'Interrupted at shutdown - Reset Connection Pool'
45-
]
46-
})
47-
);
48-
49-
beforeEach(function () {
50-
const clientOptions = {
51-
maxPoolSize: 1,
52-
retryWrites: false,
53-
heartbeatFrequencyMS: 100
54-
};
55-
56-
client = this.configuration.newClient(clientOptions);
57-
return client
58-
.db()
59-
.command({ ping: 1 })
60-
.then(() => {
61-
const primary = Array.from(client.topology.description.servers.values()).filter(
62-
sd => sd.type === 'RSPrimary'
63-
)[0];
64-
65-
checkClient = this.configuration.newClient(
66-
`mongodb://${primary.address}/?directConnection=true`,
67-
clientOptions
68-
);
69-
return checkClient.connect();
70-
})
71-
.then(() => {
72-
db = client.db('step-down');
73-
collection = db.collection('step-down');
74-
})
75-
.then(() => collection.drop({ writeConcern: { w: 'majority' } }))
76-
.catch(ignoreNsNotFound)
77-
.then(() => db.createCollection('step-down', { writeConcern: { w: 'majority' } }));
78-
});
19+
afterEach(() => client.close());
7920

80-
let deferred = [];
81-
afterEach(function () {
82-
return Promise.all(deferred.map(d => d())).then(() => {
83-
deferred = [];
84-
return Promise.all([client, checkClient].filter(x => !!x).map(client => client.close()));
85-
});
21+
afterEach(async function () {
22+
const utilClient = this.configuration.newClient();
23+
await utilClient.db('admin').command({ configureFailPoint: 'failCommand', mode: 'off' });
24+
await utilClient.close();
25+
poolClearedEvents = [];
8626
});
8727

88-
it('getMore iteration', {
89-
metadata: {
90-
requires: { mongodb: '>=4.2.0', topology: 'replicaset' }
91-
},
92-
93-
test: function () {
94-
return collection
95-
.insertMany([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }], {
96-
writeConcern: { w: 'majority' }
97-
})
98-
.then(result => expect(result.insertedCount).to.equal(5))
99-
.then(() => {
100-
const cursor = collection.find({}, { batchSize: 2 });
101-
deferred.push(() => cursor.close());
102-
103-
return cursor
104-
.next()
105-
.then(item => expect(item.a).to.equal(1))
106-
.then(() => cursor.next())
107-
.then(item => expect(item.a).to.equal(2))
108-
.then(() => {
109-
return connectionCount(checkClient).then(initialConnectionCount => {
110-
return client
111-
.db('admin')
112-
.command({ replSetFreeze: 0 }, { readPreference: 'secondary' })
113-
.then(result => expect(result).property('info').to.equal('unfreezing'))
114-
.then(() =>
115-
client
116-
.db('admin')
117-
.command({ replSetStepDown: 30, force: true }, { readPreference: 'primary' })
118-
)
119-
.then(() => cursor.next())
120-
.then(item => expect(item.a).to.equal(3))
121-
.then(() =>
122-
connectionCount(checkClient).then(
123-
expectPoolWasNotCleared(initialConnectionCount)
124-
)
125-
);
126-
});
127-
});
128-
});
129-
}
28+
beforeEach(async function () {
29+
// For each test, make sure the following steps have been completed before running the actual test:
30+
31+
// - Create a ``MongoClient`` with ``retryWrites=false``
32+
client = this.configuration.newClient({ retryWrites: false, heartbeatFrequencyMS: 500 });
33+
// - Create a collection object from the ``MongoClient``, using ``step-down`` for the database and collection name.
34+
collection = client.db('step-down').collection('step-down');
35+
// - Drop the test collection, using ``writeConcern`` "majority".
36+
await collection.drop({ writeConcern: { w: 'majority' } }).catch(() => null);
37+
// - Execute the "create" command to recreate the collection, using writeConcern: "majority".
38+
collection = await client
39+
.db('step-down')
40+
.createCollection('step-down', { writeConcern: { w: 'majority' } });
41+
42+
poolClearedEvents = [];
43+
client.on('connectionPoolCleared', poolClearEvent => poolClearedEvents.push(poolClearEvent));
13044
});
13145

132-
function runStepownScenario(errorCode, predicate) {
133-
return connectionCount(checkClient).then(initialConnectionCount => {
134-
return client
46+
context('getMore Iteration', { requires: { mongodb: '>4.2', topology: ['replicaset'] } }, () => {
47+
// This test requires a replica set with server version 4.2 or higher.
48+
49+
let cursor: FindCursor;
50+
afterEach(() => cursor.close());
51+
52+
// TODO: DEBUGGING!
53+
it.skip('survives after primary step down', async () => {
54+
// - Insert 5 documents into a collection with a majority write concern.
55+
await collection.insertMany([{ a: 1 }, { a: 2 }, { a: 3 }, { a: 4 }, { a: 5 }], {
56+
writeConcern: { w: 'majority' }
57+
});
58+
// - Start a find operation on the collection with a batch size of 2, and retrieve the first batch of results.
59+
cursor = collection.find({}, { batchSize: 2 });
60+
expect(await cursor.next()).to.have.property('a', 1);
61+
expect(await cursor.next()).to.have.property('a', 2);
62+
// - Send a `{replSetFreeze: 0}` command to any secondary and verify that the command succeeded.
63+
// This command will unfreeze (because it is set to zero) the secondary and ensure that it will be eligible to be elected immediately.
64+
await client
13565
.db('admin')
136-
.command({
137-
configureFailPoint: 'failCommand',
138-
mode: { times: 1 },
139-
data: { failCommands: ['insert'], errorCode }
140-
})
141-
.then(() => {
142-
deferred.push(() =>
143-
client.db('admin').command({ configureFailPoint: 'failCommand', mode: 'off' })
144-
);
145-
146-
return collection.insertOne({ test: 1 }).then(
147-
() => Promise.reject(new Error('expected an error')),
148-
err => expect(err.code).to.equal(errorCode)
149-
);
150-
})
151-
.then(() => collection.insertOne({ test: 1 }))
152-
.then(() => connectionCount(checkClient).then(predicate(initialConnectionCount)));
66+
.command({ replSetFreeze: 0 }, { readPreference: ReadPreference.secondary });
67+
// - Send a ``{replSetStepDown: 30, force: true}`` command to the current primary and verify that the command succeeded.
68+
await client.db('admin').command({ replSetStepDown: 5, force: true });
69+
// - Retrieve the next batch of results from the cursor obtained in the find operation, and verify that this operation succeeded.
70+
expect(await cursor.next()).to.have.property('a', 3);
71+
// - If the driver implements the `CMAP`_ specification, verify that no new `PoolClearedEvent`_ has been
72+
// published. Otherwise verify that `connections.totalCreated`_ in `serverStatus`_ has not changed.
73+
expect(poolClearedEvents).to.be.empty;
74+
75+
// Referenced python's implementation. Changes from spec:
76+
// replSetStepDown: 5 instead of 30
77+
// Run these inserts to clear NotWritablePrimary issue
78+
// Create client with heartbeatFrequencyMS=500 instead of default of 10_000
79+
80+
// Attempt insertion to mark server description as stale and prevent a
81+
// NotPrimaryError on the subsequent operation.
82+
const error = await collection.insertOne({ a: 6 }).catch(error => error);
83+
expect(error)
84+
.to.be.instanceOf(MongoServerError)
85+
.to.have.property('code', MONGODB_ERROR_CODES.NotWritablePrimary);
86+
87+
// Next insert should succeed on the new primary without clearing pool.
88+
await collection.insertOne({ a: 7 });
89+
90+
expect(poolClearedEvents).to.be.empty;
15391
});
154-
}
155-
156-
it('Not Primary - Keep Connection Pool', {
157-
metadata: {
158-
requires: { mongodb: '>=4.2.0', topology: 'replicaset' }
159-
},
160-
test: function () {
161-
return runStepownScenario(10107, expectPoolWasNotCleared);
162-
}
16392
});
16493

165-
it('Not Primary - Reset Connection Pool', {
166-
metadata: {
167-
requires: { mongodb: '4.0.x', topology: 'replicaset' }
168-
},
169-
test: function () {
170-
return runStepownScenario(10107, expectPoolWasCleared);
94+
context(
95+
'Not Primary - Keep Connection Pool',
96+
{ requires: { mongodb: '>4.2', topology: ['replicaset'] } },
97+
() => {
98+
// This test requires a replica set with server version 4.2 or higher.
99+
100+
// - Set the following fail point: ``{configureFailPoint: "failCommand", mode: {times: 1}, data: {failCommands: ["insert"], errorCode: 10107}}``
101+
const failPoint: FailPoint = {
102+
configureFailPoint: 'failCommand',
103+
mode: { times: 1 },
104+
data: { failCommands: ['insert'], errorCode: 10107 }
105+
};
106+
107+
it('survives after primary step down', async () => {
108+
await client.db('admin').command(failPoint);
109+
// - Execute an insert into the test collection of a ``{test: 1}`` document.
110+
const error = await collection.insertOne({ test: 1 }).catch(error => error);
111+
// - Verify that the insert failed with an operation failure with 10107 code.
112+
expect(error).to.be.instanceOf(MongoServerError).and.has.property('code', 10107);
113+
// - Execute an insert into the test collection of a ``{test: 1}`` document and verify that it succeeds.
114+
await collection.insertOne({ test: 1 });
115+
// - If the driver implements the `CMAP`_ specification, verify that no new `PoolClearedEvent`_ has been
116+
// published. Otherwise verify that `connections.totalCreated`_ in `serverStatus`_ has not changed.
117+
expect(poolClearedEvents).to.be.empty;
118+
});
171119
}
172-
});
120+
);
173121

174-
it('Shutdown in progress - Reset Connection Pool', {
175-
metadata: {
176-
requires: { mongodb: '>=4.0.0', topology: 'replicaset' }
177-
},
178-
test: function () {
179-
return runStepownScenario(91, expectPoolWasCleared);
122+
context(
123+
'Not Primary - Reset Connection Pool',
124+
{ requires: { mongodb: '>=4.0.0 <4.2.0', topology: ['replicaset'] } },
125+
() => {
126+
// This test requires a replica set with server version 4.0.
127+
128+
// - Set the following fail point: ``{configureFailPoint: "failCommand", mode: {times: 1}, data: {failCommands: ["insert"], errorCode: 10107}}``
129+
const failPoint: FailPoint = {
130+
configureFailPoint: 'failCommand',
131+
mode: { times: 1 },
132+
data: { failCommands: ['insert'], errorCode: 10107 }
133+
};
134+
135+
it('survives after primary step down', async () => {
136+
await client.db('admin').command(failPoint);
137+
// - Execute an insert into the test collection of a ``{test: 1}`` document.
138+
const error = await collection.insertOne({ test: 1 }).catch(error => error);
139+
// - Verify that the insert failed with an operation failure with 10107 code.
140+
expect(error).to.be.instanceOf(MongoServerError).and.has.property('code', 10107);
141+
// - If the driver implements the `CMAP`_ specification, verify that a `PoolClearedEvent`_ has been published
142+
expect(poolClearedEvents).to.have.lengthOf(1);
143+
// - Execute an insert into the test collection of a ``{test: 1}`` document and verify that it succeeds.
144+
await collection.insertOne({ test: 1 });
145+
// - If the driver does NOT implement the `CMAP`_ specification, use the `serverStatus`_ command to verify `connections.totalCreated`_ has increased by 1.
146+
});
180147
}
181-
});
148+
);
182149

183-
it('Interrupted at shutdown - Reset Connection Pool', {
184-
metadata: {
185-
requires: { mongodb: '>=4.0.0', topology: 'replicaset' }
186-
},
187-
test: function () {
188-
return runStepownScenario(11600, expectPoolWasCleared);
150+
context(
151+
'Shutdown in progress - Reset Connection Pool',
152+
{ requires: { mongodb: '>=4.0', topology: ['replicaset'] } },
153+
() => {
154+
// This test should be run on all server versions >= 4.0.
155+
156+
// - Set the following fail point: ``{configureFailPoint: "failCommand", mode: {times: 1}, data: {failCommands: ["insert"], errorCode: 91}}``
157+
const failPoint: FailPoint = {
158+
configureFailPoint: 'failCommand',
159+
mode: { times: 1 },
160+
data: { failCommands: ['insert'], errorCode: 91 }
161+
};
162+
163+
it('survives after primary step down', async () => {
164+
await client.db('admin').command(failPoint);
165+
// - Execute an insert into the test collection of a ``{test: 1}`` document.
166+
const error = await collection.insertOne({ test: 1 }).catch(error => error);
167+
// - Verify that the insert failed with an operation failure with 91 code.
168+
expect(error).to.be.instanceOf(MongoServerError).and.has.property('code', 91);
169+
// - If the driver implements the `CMAP`_ specification, verify that a `PoolClearedEvent`_ has been published
170+
expect(poolClearedEvents).to.have.lengthOf(1);
171+
// - Execute an insert into the test collection of a ``{test: 1}`` document and verify that it succeeds.
172+
await collection.insertOne({ test: 1 });
173+
// - If the driver does NOT implement the `CMAP`_ specification, use the `serverStatus`_ command to verify `connections.totalCreated`_ has increased by 1.
174+
});
189175
}
190-
});
176+
);
177+
178+
context(
179+
'Interrupted at shutdown - Reset Connection Pool',
180+
{ requires: { mongodb: '>=4.0', topology: ['replicaset'] } },
181+
() => {
182+
// This test should be run on all server versions >= 4.0.
183+
184+
// - Set the following fail point: ``{configureFailPoint: "failCommand", mode: {times: 1}, data: {failCommands: ["insert"], errorCode: 11600}}``
185+
const failPoint: FailPoint = {
186+
configureFailPoint: 'failCommand',
187+
mode: { times: 1 },
188+
data: { failCommands: ['insert'], errorCode: 11600 }
189+
};
190+
191+
it('survives after primary step down', async () => {
192+
await client.db('admin').command(failPoint);
193+
// - Execute an insert into the test collection of a ``{test: 1}`` document.
194+
const error = await collection.insertOne({ test: 1 }).catch(error => error);
195+
// - Verify that the insert failed with an operation failure with 11600 code.
196+
expect(error).to.be.instanceOf(MongoServerError).and.has.property('code', 11600);
197+
// - If the driver implements the `CMAP`_ specification, verify that a `PoolClearedEvent`_ has been published
198+
expect(poolClearedEvents).to.have.lengthOf(1);
199+
// - Execute an insert into the test collection of a ``{test: 1}`` document and verify that it succeeds.
200+
await collection.insertOne({ test: 1 });
201+
// - If the driver does NOT implement the `CMAP`_ specification, use the `serverStatus`_ command to verify `connections.totalCreated`_ has increased by 1.
202+
});
203+
}
204+
);
191205
});

0 commit comments

Comments
 (0)