Ohm-Management - Projektarbeit B-ME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

shared.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. 'use strict';
  2. const os = require('os');
  3. const f = require('util').format;
  4. const ReadPreference = require('./read_preference');
  5. const Buffer = require('safe-buffer').Buffer;
  6. const TopologyType = require('../sdam/topology_description').TopologyType;
  7. /**
  8. * Emit event if it exists
  9. * @method
  10. */
  11. function emitSDAMEvent(self, event, description) {
  12. if (self.listeners(event).length > 0) {
  13. self.emit(event, description);
  14. }
  15. }
  16. // Get package.json variable
  17. var driverVersion = require('../../package.json').version;
  18. var nodejsversion = f('Node.js %s, %s', process.version, os.endianness());
  19. var type = os.type();
  20. var name = process.platform;
  21. var architecture = process.arch;
  22. var release = os.release();
  23. function createClientInfo(options) {
  24. // Build default client information
  25. var clientInfo = options.clientInfo
  26. ? clone(options.clientInfo)
  27. : {
  28. driver: {
  29. name: 'nodejs-core',
  30. version: driverVersion
  31. },
  32. os: {
  33. type: type,
  34. name: name,
  35. architecture: architecture,
  36. version: release
  37. }
  38. };
  39. // Is platform specified
  40. if (clientInfo.platform && clientInfo.platform.indexOf('mongodb-core') === -1) {
  41. clientInfo.platform = f('%s, mongodb-core: %s', clientInfo.platform, driverVersion);
  42. } else if (!clientInfo.platform) {
  43. clientInfo.platform = nodejsversion;
  44. }
  45. // Do we have an application specific string
  46. if (options.appname) {
  47. // Cut at 128 bytes
  48. var buffer = Buffer.from(options.appname);
  49. // Return the truncated appname
  50. var appname = buffer.length > 128 ? buffer.slice(0, 128).toString('utf8') : options.appname;
  51. // Add to the clientInfo
  52. clientInfo.application = { name: appname };
  53. }
  54. return clientInfo;
  55. }
  56. function createCompressionInfo(options) {
  57. if (!options.compression || !options.compression.compressors) {
  58. return [];
  59. }
  60. // Check that all supplied compressors are valid
  61. options.compression.compressors.forEach(function(compressor) {
  62. if (compressor !== 'snappy' && compressor !== 'zlib') {
  63. throw new Error('compressors must be at least one of snappy or zlib');
  64. }
  65. });
  66. return options.compression.compressors;
  67. }
  68. function clone(object) {
  69. return JSON.parse(JSON.stringify(object));
  70. }
  71. var getPreviousDescription = function(self) {
  72. if (!self.s.serverDescription) {
  73. self.s.serverDescription = {
  74. address: self.name,
  75. arbiters: [],
  76. hosts: [],
  77. passives: [],
  78. type: 'Unknown'
  79. };
  80. }
  81. return self.s.serverDescription;
  82. };
  83. var emitServerDescriptionChanged = function(self, description) {
  84. if (self.listeners('serverDescriptionChanged').length > 0) {
  85. // Emit the server description changed events
  86. self.emit('serverDescriptionChanged', {
  87. topologyId: self.s.topologyId !== -1 ? self.s.topologyId : self.id,
  88. address: self.name,
  89. previousDescription: getPreviousDescription(self),
  90. newDescription: description
  91. });
  92. self.s.serverDescription = description;
  93. }
  94. };
  95. var getPreviousTopologyDescription = function(self) {
  96. if (!self.s.topologyDescription) {
  97. self.s.topologyDescription = {
  98. topologyType: 'Unknown',
  99. servers: [
  100. {
  101. address: self.name,
  102. arbiters: [],
  103. hosts: [],
  104. passives: [],
  105. type: 'Unknown'
  106. }
  107. ]
  108. };
  109. }
  110. return self.s.topologyDescription;
  111. };
  112. var emitTopologyDescriptionChanged = function(self, description) {
  113. if (self.listeners('topologyDescriptionChanged').length > 0) {
  114. // Emit the server description changed events
  115. self.emit('topologyDescriptionChanged', {
  116. topologyId: self.s.topologyId !== -1 ? self.s.topologyId : self.id,
  117. address: self.name,
  118. previousDescription: getPreviousTopologyDescription(self),
  119. newDescription: description
  120. });
  121. self.s.serverDescription = description;
  122. }
  123. };
  124. var changedIsMaster = function(self, currentIsmaster, ismaster) {
  125. var currentType = getTopologyType(self, currentIsmaster);
  126. var newType = getTopologyType(self, ismaster);
  127. if (newType !== currentType) return true;
  128. return false;
  129. };
  130. var getTopologyType = function(self, ismaster) {
  131. if (!ismaster) {
  132. ismaster = self.ismaster;
  133. }
  134. if (!ismaster) return 'Unknown';
  135. if (ismaster.ismaster && ismaster.msg === 'isdbgrid') return 'Mongos';
  136. if (ismaster.ismaster && !ismaster.hosts) return 'Standalone';
  137. if (ismaster.ismaster) return 'RSPrimary';
  138. if (ismaster.secondary) return 'RSSecondary';
  139. if (ismaster.arbiterOnly) return 'RSArbiter';
  140. return 'Unknown';
  141. };
  142. var inquireServerState = function(self) {
  143. return function(callback) {
  144. if (self.s.state === 'destroyed') return;
  145. // Record response time
  146. var start = new Date().getTime();
  147. // emitSDAMEvent
  148. emitSDAMEvent(self, 'serverHeartbeatStarted', { connectionId: self.name });
  149. // Attempt to execute ismaster command
  150. self.command('admin.$cmd', { ismaster: true }, { monitoring: true }, function(err, r) {
  151. if (!err) {
  152. // Legacy event sender
  153. self.emit('ismaster', r, self);
  154. // Calculate latencyMS
  155. var latencyMS = new Date().getTime() - start;
  156. // Server heart beat event
  157. emitSDAMEvent(self, 'serverHeartbeatSucceeded', {
  158. durationMS: latencyMS,
  159. reply: r.result,
  160. connectionId: self.name
  161. });
  162. // Did the server change
  163. if (changedIsMaster(self, self.s.ismaster, r.result)) {
  164. // Emit server description changed if something listening
  165. emitServerDescriptionChanged(self, {
  166. address: self.name,
  167. arbiters: [],
  168. hosts: [],
  169. passives: [],
  170. type: !self.s.inTopology ? 'Standalone' : getTopologyType(self)
  171. });
  172. }
  173. // Updat ismaster view
  174. self.s.ismaster = r.result;
  175. // Set server response time
  176. self.s.isMasterLatencyMS = latencyMS;
  177. } else {
  178. emitSDAMEvent(self, 'serverHeartbeatFailed', {
  179. durationMS: latencyMS,
  180. failure: err,
  181. connectionId: self.name
  182. });
  183. }
  184. // Peforming an ismaster monitoring callback operation
  185. if (typeof callback === 'function') {
  186. return callback(err, r);
  187. }
  188. // Perform another sweep
  189. self.s.inquireServerStateTimeout = setTimeout(inquireServerState(self), self.s.haInterval);
  190. });
  191. };
  192. };
  193. //
  194. // Clone the options
  195. var cloneOptions = function(options) {
  196. var opts = {};
  197. for (var name in options) {
  198. opts[name] = options[name];
  199. }
  200. return opts;
  201. };
  202. function Interval(fn, time) {
  203. var timer = false;
  204. this.start = function() {
  205. if (!this.isRunning()) {
  206. timer = setInterval(fn, time);
  207. }
  208. return this;
  209. };
  210. this.stop = function() {
  211. clearInterval(timer);
  212. timer = false;
  213. return this;
  214. };
  215. this.isRunning = function() {
  216. return timer !== false;
  217. };
  218. }
  219. function Timeout(fn, time) {
  220. var timer = false;
  221. this.start = function() {
  222. if (!this.isRunning()) {
  223. timer = setTimeout(fn, time);
  224. }
  225. return this;
  226. };
  227. this.stop = function() {
  228. clearTimeout(timer);
  229. timer = false;
  230. return this;
  231. };
  232. this.isRunning = function() {
  233. if (timer && timer._called) return false;
  234. return timer !== false;
  235. };
  236. }
  237. function diff(previous, current) {
  238. // Difference document
  239. var diff = {
  240. servers: []
  241. };
  242. // Previous entry
  243. if (!previous) {
  244. previous = { servers: [] };
  245. }
  246. // Check if we have any previous servers missing in the current ones
  247. for (var i = 0; i < previous.servers.length; i++) {
  248. var found = false;
  249. for (var j = 0; j < current.servers.length; j++) {
  250. if (current.servers[j].address.toLowerCase() === previous.servers[i].address.toLowerCase()) {
  251. found = true;
  252. break;
  253. }
  254. }
  255. if (!found) {
  256. // Add to the diff
  257. diff.servers.push({
  258. address: previous.servers[i].address,
  259. from: previous.servers[i].type,
  260. to: 'Unknown'
  261. });
  262. }
  263. }
  264. // Check if there are any severs that don't exist
  265. for (j = 0; j < current.servers.length; j++) {
  266. found = false;
  267. // Go over all the previous servers
  268. for (i = 0; i < previous.servers.length; i++) {
  269. if (previous.servers[i].address.toLowerCase() === current.servers[j].address.toLowerCase()) {
  270. found = true;
  271. break;
  272. }
  273. }
  274. // Add the server to the diff
  275. if (!found) {
  276. diff.servers.push({
  277. address: current.servers[j].address,
  278. from: 'Unknown',
  279. to: current.servers[j].type
  280. });
  281. }
  282. }
  283. // Got through all the servers
  284. for (i = 0; i < previous.servers.length; i++) {
  285. var prevServer = previous.servers[i];
  286. // Go through all current servers
  287. for (j = 0; j < current.servers.length; j++) {
  288. var currServer = current.servers[j];
  289. // Matching server
  290. if (prevServer.address.toLowerCase() === currServer.address.toLowerCase()) {
  291. // We had a change in state
  292. if (prevServer.type !== currServer.type) {
  293. diff.servers.push({
  294. address: prevServer.address,
  295. from: prevServer.type,
  296. to: currServer.type
  297. });
  298. }
  299. }
  300. }
  301. }
  302. // Return difference
  303. return diff;
  304. }
  305. /**
  306. * Shared function to determine clusterTime for a given topology
  307. *
  308. * @param {*} topology
  309. * @param {*} clusterTime
  310. */
  311. function resolveClusterTime(topology, $clusterTime) {
  312. if (topology.clusterTime == null) {
  313. topology.clusterTime = $clusterTime;
  314. } else {
  315. if ($clusterTime.clusterTime.greaterThan(topology.clusterTime.clusterTime)) {
  316. topology.clusterTime = $clusterTime;
  317. }
  318. }
  319. }
  320. // NOTE: this is a temporary move until the topologies can be more formally refactored
  321. // to share code.
  322. const SessionMixins = {
  323. endSessions: function(sessions, callback) {
  324. if (!Array.isArray(sessions)) {
  325. sessions = [sessions];
  326. }
  327. // TODO:
  328. // When connected to a sharded cluster the endSessions command
  329. // can be sent to any mongos. When connected to a replica set the
  330. // endSessions command MUST be sent to the primary if the primary
  331. // is available, otherwise it MUST be sent to any available secondary.
  332. // Is it enough to use: ReadPreference.primaryPreferred ?
  333. this.command(
  334. 'admin.$cmd',
  335. { endSessions: sessions },
  336. { readPreference: ReadPreference.primaryPreferred },
  337. () => {
  338. // intentionally ignored, per spec
  339. if (typeof callback === 'function') callback();
  340. }
  341. );
  342. }
  343. };
  344. function topologyType(topology) {
  345. if (topology.description) {
  346. return topology.description.type;
  347. }
  348. if (topology.type === 'mongos') {
  349. return TopologyType.Sharded;
  350. } else if (topology.type === 'replset') {
  351. return TopologyType.ReplicaSetWithPrimary;
  352. }
  353. return TopologyType.Single;
  354. }
  355. const RETRYABLE_WIRE_VERSION = 6;
  356. /**
  357. * Determines whether the provided topology supports retryable writes
  358. *
  359. * @param {Mongos|Replset} topology
  360. */
  361. const isRetryableWritesSupported = function(topology) {
  362. const maxWireVersion = topology.lastIsMaster().maxWireVersion;
  363. if (maxWireVersion < RETRYABLE_WIRE_VERSION) {
  364. return false;
  365. }
  366. if (!topology.logicalSessionTimeoutMinutes) {
  367. return false;
  368. }
  369. if (topologyType(topology) === TopologyType.Single) {
  370. return false;
  371. }
  372. return true;
  373. };
  374. module.exports.SessionMixins = SessionMixins;
  375. module.exports.resolveClusterTime = resolveClusterTime;
  376. module.exports.inquireServerState = inquireServerState;
  377. module.exports.getTopologyType = getTopologyType;
  378. module.exports.emitServerDescriptionChanged = emitServerDescriptionChanged;
  379. module.exports.emitTopologyDescriptionChanged = emitTopologyDescriptionChanged;
  380. module.exports.cloneOptions = cloneOptions;
  381. module.exports.createClientInfo = createClientInfo;
  382. module.exports.createCompressionInfo = createCompressionInfo;
  383. module.exports.clone = clone;
  384. module.exports.diff = diff;
  385. module.exports.Interval = Interval;
  386. module.exports.Timeout = Timeout;
  387. module.exports.isRetryableWritesSupported = isRetryableWritesSupported;