Skip to content

Commit

Permalink
fix: session support detection spec compliance (#2732)
Browse files Browse the repository at this point in the history
  • Loading branch information
nbbeeken authored Feb 9, 2021
1 parent e8ac558 commit 9baec71
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 26 deletions.
30 changes: 24 additions & 6 deletions lib/core/sdam/topology_description.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,30 @@ class TopologyDescription {
// value among ServerDescriptions of all data-bearing server types. If any have a null
// logicalSessionTimeoutMinutes, then TopologyDescription.logicalSessionTimeoutMinutes MUST be
// set to null.
const readableServers = Array.from(this.servers.values()).filter(s => s.isReadable);
this.logicalSessionTimeoutMinutes = readableServers.reduce((result, server) => {
if (server.logicalSessionTimeoutMinutes == null) return null;
if (result == null) return server.logicalSessionTimeoutMinutes;
return Math.min(result, server.logicalSessionTimeoutMinutes);
}, null);
this.logicalSessionTimeoutMinutes = null;
for (const addressServerTuple of this.servers) {
const server = addressServerTuple[1];
if (server.isReadable) {
if (server.logicalSessionTimeoutMinutes == null) {
// If any of the servers have a null logicalSessionsTimeout, then the whole topology does
this.logicalSessionTimeoutMinutes = null;
break;
}

if (this.logicalSessionTimeoutMinutes == null) {
// First server with a non null logicalSessionsTimeout
this.logicalSessionTimeoutMinutes = server.logicalSessionTimeoutMinutes;
continue;
}

// Always select the smaller of the:
// current server logicalSessionsTimeout and the topologies logicalSessionsTimeout
this.logicalSessionTimeoutMinutes = Math.min(
this.logicalSessionTimeoutMinutes,
server.logicalSessionTimeoutMinutes
);
}
}
}

/**
Expand Down
59 changes: 39 additions & 20 deletions test/unit/core/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,39 @@ class ReplSetFixture {
this.electionIds = [new ObjectId(), new ObjectId()];
}

uri(dbName) {
return `mongodb://${this.primaryServer.uri()},${this.firstSecondaryServer.uri()},${this.secondSecondaryServer.uri()}/${dbName ||
'test'}?replicaSet=rs`;
}

setup(options) {
options = options || {};
const ismaster = options.ismaster ? options.ismaster : mock.DEFAULT_ISMASTER_36;

return Promise.all([mock.createServer(), mock.createServer(), mock.createServer()]).then(
servers => {
this.servers = servers;
this.primaryServer = servers[0];
this.firstSecondaryServer = servers[1];
this.arbiterServer = servers[2];

this.defaultFields = Object.assign({}, ismaster, {
__nodejs_mock_server__: true,
setName: 'rs',
setVersion: 1,
electionId: this.electionIds[0],
hosts: this.servers.map(server => server.uri()),
arbiters: [this.arbiterServer.uri()]
});

this.defineReplSetStates();
this.configureMessageHandlers();
}
);
return Promise.all([
mock.createServer(),
mock.createServer(),
mock.createServer(),
mock.createServer()
]).then(servers => {
this.servers = servers;
this.primaryServer = servers[0];
this.firstSecondaryServer = servers[1];
this.secondSecondaryServer = servers[2];
this.arbiterServer = servers[3];

this.defaultFields = Object.assign({}, ismaster, {
__nodejs_mock_server__: true,
setName: 'rs',
setVersion: 1,
electionId: this.electionIds[0],
hosts: this.servers.map(server => server.uri()),
arbiters: [this.arbiterServer.uri()]
});

if (!options.doNotInitStates) this.defineReplSetStates();
if (!options.doNotInitHandlers) this.configureMessageHandlers();
});
}

defineReplSetStates() {
Expand All @@ -58,6 +67,16 @@ class ReplSetFixture {
})
];

this.secondSecondaryStates = [
Object.assign({}, this.defaultFields, {
ismaster: false,
secondary: true,
me: this.secondSecondaryServer.uri(),
primary: this.primaryServer.uri(),
tags: { loc: 'la' }
})
];

this.arbiterStates = [
Object.assign({}, this.defaultFields, {
ismaster: false,
Expand Down
67 changes: 67 additions & 0 deletions test/unit/sessions/client.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const expect = require('chai').expect;
const mock = require('mongodb-mock-server');
const ReplSetFixture = require('../core/common').ReplSetFixture;

const test = {};
describe('Sessions', function() {
Expand Down Expand Up @@ -37,6 +38,72 @@ describe('Sessions', function() {
}
});

it('should throw an exception if sessions are not supported on some servers', {
metadata: { requires: { topology: 'single' } },
test() {
const replicaSetMock = new ReplSetFixture();
return replicaSetMock
.setup({ doNotInitHandlers: true })
.then(() => {
replicaSetMock.firstSecondaryServer.setMessageHandler(request => {
var doc = request.document;
if (doc.ismaster) {
const ismaster = replicaSetMock.firstSecondaryStates[0];
ismaster.logicalSessionTimeoutMinutes = 20;
request.reply(ismaster);
} else if (doc.endSessions) {
request.reply({ ok: 1 });
}
});

replicaSetMock.secondSecondaryServer.setMessageHandler(request => {
var doc = request.document;
if (doc.ismaster) {
const ismaster = replicaSetMock.secondSecondaryStates[0];
ismaster.logicalSessionTimeoutMinutes = 10;
request.reply(ismaster);
} else if (doc.endSessions) {
request.reply({ ok: 1 });
}
});

replicaSetMock.arbiterServer.setMessageHandler(request => {
var doc = request.document;
if (doc.ismaster) {
const ismaster = replicaSetMock.arbiterStates[0];
ismaster.logicalSessionTimeoutMinutes = 30;
request.reply(ismaster);
} else if (doc.endSessions) {
request.reply({ ok: 1 });
}
});

replicaSetMock.primaryServer.setMessageHandler(request => {
var doc = request.document;
if (doc.ismaster) {
const ismaster = replicaSetMock.primaryStates[0];
ismaster.logicalSessionTimeoutMinutes = null;
request.reply(ismaster);
} else if (doc.endSessions) {
request.reply({ ok: 1 });
}
});

return replicaSetMock.uri();
})
.then(uri => {
const client = this.configuration.newClient(uri);
return client.connect();
})
.then(client => {
expect(client.topology.s.description.logicalSessionTimeoutMinutes).to.not.exist;
expect(() => {
client.startSession();
}).to.throw(/Current topology does not support sessions/);
return client.close();
});
}
});
it('should return a client session when requested if the topology supports it', {
metadata: { requires: { topology: 'single' } },

Expand Down

0 comments on commit 9baec71

Please sign in to comment.