spice-couchbase 2.0.61 → 2.0.63
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/connectionHealth.js +573 -0
- package/build/connectionHealth.test.js +659 -0
- package/build/index.auto-optimize.test.js +204 -2
- package/build/index.js +341 -43
- package/build/utility.js +65 -0
- package/build/utility.test.js +49 -1
- package/package.json +3 -2
- package/src/connectionHealth.js +529 -0
- package/src/connectionHealth.test.js +412 -0
- package/src/index.auto-optimize.test.js +158 -2
- package/src/index.js +320 -55
- package/src/utility.js +63 -0
- package/src/utility.test.js +69 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
|
|
4
|
+
|
|
5
|
+
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
|
|
6
|
+
|
|
7
|
+
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
|
8
|
+
|
|
9
|
+
function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
|
|
10
|
+
|
|
11
|
+
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
|
|
12
|
+
|
|
13
|
+
var assert = require("node:assert/strict");
|
|
14
|
+
|
|
15
|
+
var {
|
|
16
|
+
test
|
|
17
|
+
} = require("node:test");
|
|
18
|
+
|
|
19
|
+
var couchbase = require("couchbase");
|
|
20
|
+
|
|
21
|
+
var {
|
|
22
|
+
createHealthMonitor,
|
|
23
|
+
buildConnectOptions,
|
|
24
|
+
isConnectivityError
|
|
25
|
+
} = require("./connectionHealth");
|
|
26
|
+
|
|
27
|
+
var config = {
|
|
28
|
+
nodes: "couchbase://db",
|
|
29
|
+
bucket: "saturn",
|
|
30
|
+
scope: "app",
|
|
31
|
+
username: "u",
|
|
32
|
+
password: "p"
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
var timeoutError = () => new couchbase.UnambiguousTimeoutError(); // A fake cluster whose probe behaviour can be flipped between healthy and
|
|
36
|
+
// wedged. `hang` makes operations never settle, like a stuck SDK.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
function fakeCluster(id) {
|
|
40
|
+
var {
|
|
41
|
+
healthy = true,
|
|
42
|
+
hang = false
|
|
43
|
+
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
44
|
+
var cluster = {
|
|
45
|
+
id,
|
|
46
|
+
healthy,
|
|
47
|
+
hang,
|
|
48
|
+
closed: 0,
|
|
49
|
+
queries: 0,
|
|
50
|
+
|
|
51
|
+
close() {
|
|
52
|
+
return _asyncToGenerator(function* () {
|
|
53
|
+
cluster.closed += 1;
|
|
54
|
+
})();
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
query() {
|
|
58
|
+
cluster.queries += 1;
|
|
59
|
+
if (cluster.hang) return new Promise(() => {});
|
|
60
|
+
return cluster.healthy ? Promise.resolve({
|
|
61
|
+
rows: [{
|
|
62
|
+
$1: 1
|
|
63
|
+
}]
|
|
64
|
+
}) : Promise.reject(timeoutError());
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
bucket(name) {
|
|
68
|
+
var bucket = {
|
|
69
|
+
name,
|
|
70
|
+
cluster,
|
|
71
|
+
|
|
72
|
+
waitUntilReady() {
|
|
73
|
+
return _asyncToGenerator(function* () {
|
|
74
|
+
if (!cluster.healthy) throw timeoutError();
|
|
75
|
+
})();
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
scope(scopeName) {
|
|
79
|
+
return {
|
|
80
|
+
name: scopeName,
|
|
81
|
+
bucket,
|
|
82
|
+
|
|
83
|
+
collection(collectionName) {
|
|
84
|
+
return {
|
|
85
|
+
name: collectionName,
|
|
86
|
+
cluster,
|
|
87
|
+
|
|
88
|
+
exists() {
|
|
89
|
+
if (cluster.hang) return new Promise(() => {});
|
|
90
|
+
return cluster.healthy ? Promise.resolve({
|
|
91
|
+
exists: false
|
|
92
|
+
}) : Promise.reject(timeoutError());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
};
|
|
102
|
+
return bucket;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
};
|
|
106
|
+
return cluster;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function handlesFor(cluster) {
|
|
110
|
+
var bucket = cluster.bucket(config.bucket);
|
|
111
|
+
var scope = bucket.scope(config.scope);
|
|
112
|
+
return {
|
|
113
|
+
cluster,
|
|
114
|
+
bucket,
|
|
115
|
+
scope,
|
|
116
|
+
collection: scope.collection("_default")
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function setup() {
|
|
121
|
+
var {
|
|
122
|
+
settings = {},
|
|
123
|
+
connect,
|
|
124
|
+
clock
|
|
125
|
+
} = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
126
|
+
var initial = fakeCluster("initial");
|
|
127
|
+
var handles = handlesFor(initial);
|
|
128
|
+
var globals = {
|
|
129
|
+
database: {
|
|
130
|
+
clusters: {
|
|
131
|
+
default: handles.cluster
|
|
132
|
+
},
|
|
133
|
+
buckets: {
|
|
134
|
+
default: handles.bucket
|
|
135
|
+
},
|
|
136
|
+
scopes: {
|
|
137
|
+
default: handles.scope
|
|
138
|
+
},
|
|
139
|
+
connections: {
|
|
140
|
+
default: handles.collection
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
checked_collections: {
|
|
144
|
+
users: true
|
|
145
|
+
},
|
|
146
|
+
db: {
|
|
147
|
+
collections: new Set(["users"])
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
var opened = [];
|
|
151
|
+
var time = clock || {
|
|
152
|
+
now: 1000
|
|
153
|
+
};
|
|
154
|
+
var terminated = [];
|
|
155
|
+
var logs = [];
|
|
156
|
+
var log = {
|
|
157
|
+
log: m => logs.push(m),
|
|
158
|
+
warn: m => logs.push(m),
|
|
159
|
+
error: m => logs.push(m)
|
|
160
|
+
};
|
|
161
|
+
var monitor = createHealthMonitor({
|
|
162
|
+
settings: _objectSpread({
|
|
163
|
+
healthIntervalMs: 0,
|
|
164
|
+
probeTimeoutMs: 20,
|
|
165
|
+
probeFailureThreshold: 2,
|
|
166
|
+
failureThreshold: 3,
|
|
167
|
+
failureWindowMs: 1000,
|
|
168
|
+
reconnectTimeoutMs: 50,
|
|
169
|
+
reconnectBackoffMinMs: 100,
|
|
170
|
+
reconnectBackoffMaxMs: 1000,
|
|
171
|
+
retireGraceMs: 5,
|
|
172
|
+
exitAfterMs: 10000
|
|
173
|
+
}, settings),
|
|
174
|
+
connect: connect ||
|
|
175
|
+
/*#__PURE__*/
|
|
176
|
+
_asyncToGenerator(function* () {
|
|
177
|
+
var cluster = fakeCluster("fresh-".concat(opened.length + 1));
|
|
178
|
+
opened.push(cluster);
|
|
179
|
+
return cluster;
|
|
180
|
+
}),
|
|
181
|
+
now: () => time.now,
|
|
182
|
+
random: () => 0.5,
|
|
183
|
+
log,
|
|
184
|
+
getGlobals: () => globals,
|
|
185
|
+
terminate: () => terminated.push(time.now)
|
|
186
|
+
});
|
|
187
|
+
monitor.register("default", config);
|
|
188
|
+
return {
|
|
189
|
+
monitor,
|
|
190
|
+
initial,
|
|
191
|
+
globals,
|
|
192
|
+
opened,
|
|
193
|
+
time,
|
|
194
|
+
terminated,
|
|
195
|
+
logs
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
var sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
|
200
|
+
|
|
201
|
+
test("only connectivity errors count", () => {
|
|
202
|
+
assert.equal(isConnectivityError(timeoutError()), true);
|
|
203
|
+
assert.equal(isConnectivityError(new couchbase.AmbiguousTimeoutError()), true);
|
|
204
|
+
assert.equal(isConnectivityError(new couchbase.RequestCanceledError()), true);
|
|
205
|
+
assert.equal(isConnectivityError(new couchbase.ServiceNotAvailableError()), true);
|
|
206
|
+
assert.equal(isConnectivityError(new couchbase.DocumentNotFoundError()), false);
|
|
207
|
+
assert.equal(isConnectivityError(new Error("syntax error")), false);
|
|
208
|
+
assert.equal(isConnectivityError(undefined), false);
|
|
209
|
+
});
|
|
210
|
+
test("connect options carry only the timeouts that are set", () => {
|
|
211
|
+
assert.deepEqual(buildConnectOptions(config), {
|
|
212
|
+
username: "u",
|
|
213
|
+
password: "p"
|
|
214
|
+
});
|
|
215
|
+
assert.deepEqual(buildConnectOptions(_objectSpread({}, config, {
|
|
216
|
+
configProfile: "wanDevelopment",
|
|
217
|
+
timeouts: {
|
|
218
|
+
queryTimeout: "30000",
|
|
219
|
+
kvTimeout: undefined,
|
|
220
|
+
connectTimeout: ""
|
|
221
|
+
}
|
|
222
|
+
})), {
|
|
223
|
+
username: "u",
|
|
224
|
+
password: "p",
|
|
225
|
+
configProfile: "wanDevelopment",
|
|
226
|
+
timeouts: {
|
|
227
|
+
queryTimeout: 30000
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
test("a healthy probe leaves the connection alone",
|
|
232
|
+
/*#__PURE__*/
|
|
233
|
+
_asyncToGenerator(function* () {
|
|
234
|
+
var {
|
|
235
|
+
monitor,
|
|
236
|
+
opened,
|
|
237
|
+
globals,
|
|
238
|
+
initial
|
|
239
|
+
} = setup();
|
|
240
|
+
assert.equal((yield monitor.check("default")), true);
|
|
241
|
+
assert.equal(opened.length, 0);
|
|
242
|
+
assert.equal(globals.database.clusters.default, initial);
|
|
243
|
+
assert.equal(monitor.status().default.healthy, true);
|
|
244
|
+
}));
|
|
245
|
+
test("non-connectivity errors never trigger a probe",
|
|
246
|
+
/*#__PURE__*/
|
|
247
|
+
_asyncToGenerator(function* () {
|
|
248
|
+
var {
|
|
249
|
+
monitor,
|
|
250
|
+
initial
|
|
251
|
+
} = setup();
|
|
252
|
+
|
|
253
|
+
for (var i = 0; i < 10; i++) {
|
|
254
|
+
monitor.recordOutcome("default", new couchbase.DocumentNotFoundError());
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
yield sleep(5);
|
|
258
|
+
assert.equal(initial.queries, 0);
|
|
259
|
+
}));
|
|
260
|
+
test("one failed probe is not enough, two trigger a reconnect",
|
|
261
|
+
/*#__PURE__*/
|
|
262
|
+
_asyncToGenerator(function* () {
|
|
263
|
+
var {
|
|
264
|
+
monitor,
|
|
265
|
+
initial,
|
|
266
|
+
opened
|
|
267
|
+
} = setup();
|
|
268
|
+
initial.healthy = false;
|
|
269
|
+
yield monitor.check("default");
|
|
270
|
+
assert.equal(opened.length, 0);
|
|
271
|
+
assert.equal(monitor.status().default.healthy, true);
|
|
272
|
+
yield monitor.check("default");
|
|
273
|
+
assert.equal(opened.length, 1);
|
|
274
|
+
assert.equal(monitor.status().default.healthy, true);
|
|
275
|
+
assert.equal(monitor.status().default.generation, 2);
|
|
276
|
+
}));
|
|
277
|
+
test("a burst of operation timeouts probes and reconnects at once",
|
|
278
|
+
/*#__PURE__*/
|
|
279
|
+
_asyncToGenerator(function* () {
|
|
280
|
+
var {
|
|
281
|
+
monitor,
|
|
282
|
+
initial,
|
|
283
|
+
opened,
|
|
284
|
+
globals
|
|
285
|
+
} = setup();
|
|
286
|
+
initial.healthy = false;
|
|
287
|
+
|
|
288
|
+
for (var i = 0; i < 3; i++) {
|
|
289
|
+
monitor.recordOutcome("default", timeoutError());
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
yield sleep(10);
|
|
293
|
+
assert.equal(opened.length, 1);
|
|
294
|
+
assert.equal(globals.database.clusters.default, opened[0]);
|
|
295
|
+
}));
|
|
296
|
+
test("timeouts outside the window do not add up",
|
|
297
|
+
/*#__PURE__*/
|
|
298
|
+
_asyncToGenerator(function* () {
|
|
299
|
+
var {
|
|
300
|
+
monitor,
|
|
301
|
+
initial,
|
|
302
|
+
opened,
|
|
303
|
+
time
|
|
304
|
+
} = setup();
|
|
305
|
+
initial.healthy = false;
|
|
306
|
+
monitor.recordOutcome("default", timeoutError());
|
|
307
|
+
monitor.recordOutcome("default", timeoutError());
|
|
308
|
+
time.now += 2000;
|
|
309
|
+
monitor.recordOutcome("default", timeoutError());
|
|
310
|
+
yield sleep(10);
|
|
311
|
+
assert.equal(initial.queries, 0);
|
|
312
|
+
assert.equal(opened.length, 0);
|
|
313
|
+
}));
|
|
314
|
+
test("reconnect swaps every global handle and resets collection caches",
|
|
315
|
+
/*#__PURE__*/
|
|
316
|
+
_asyncToGenerator(function* () {
|
|
317
|
+
var {
|
|
318
|
+
monitor,
|
|
319
|
+
initial,
|
|
320
|
+
opened,
|
|
321
|
+
globals
|
|
322
|
+
} = setup();
|
|
323
|
+
initial.healthy = false;
|
|
324
|
+
yield monitor.check("default");
|
|
325
|
+
yield monitor.check("default");
|
|
326
|
+
var fresh = opened[0];
|
|
327
|
+
assert.equal(globals.database.clusters.default, fresh);
|
|
328
|
+
assert.equal(globals.database.buckets.default.cluster, fresh);
|
|
329
|
+
assert.equal(globals.database.scopes.default.bucket.cluster, fresh);
|
|
330
|
+
assert.equal(globals.database.connections.default.cluster, fresh);
|
|
331
|
+
assert.deepEqual(globals.checked_collections, {});
|
|
332
|
+
assert.equal(globals.db.collections.size, 0);
|
|
333
|
+
}));
|
|
334
|
+
test("the replaced cluster is closed after the grace period",
|
|
335
|
+
/*#__PURE__*/
|
|
336
|
+
_asyncToGenerator(function* () {
|
|
337
|
+
var {
|
|
338
|
+
monitor,
|
|
339
|
+
initial
|
|
340
|
+
} = setup();
|
|
341
|
+
initial.healthy = false;
|
|
342
|
+
yield monitor.check("default");
|
|
343
|
+
yield monitor.check("default");
|
|
344
|
+
assert.equal(initial.closed, 0);
|
|
345
|
+
yield sleep(20);
|
|
346
|
+
assert.equal(initial.closed, 1);
|
|
347
|
+
}));
|
|
348
|
+
test("concurrent triggers share a single reconnect",
|
|
349
|
+
/*#__PURE__*/
|
|
350
|
+
_asyncToGenerator(function* () {
|
|
351
|
+
var release;
|
|
352
|
+
var opened = [];
|
|
353
|
+
var {
|
|
354
|
+
monitor,
|
|
355
|
+
initial
|
|
356
|
+
} = setup({
|
|
357
|
+
settings: {
|
|
358
|
+
probeFailureThreshold: 1,
|
|
359
|
+
reconnectTimeoutMs: 1000
|
|
360
|
+
},
|
|
361
|
+
connect: () => new Promise(resolve => {
|
|
362
|
+
release = () => {
|
|
363
|
+
var cluster = fakeCluster("fresh");
|
|
364
|
+
opened.push(cluster);
|
|
365
|
+
resolve(cluster);
|
|
366
|
+
};
|
|
367
|
+
})
|
|
368
|
+
});
|
|
369
|
+
initial.healthy = false;
|
|
370
|
+
var first = monitor.check("default");
|
|
371
|
+
yield sleep(5);
|
|
372
|
+
var others = [monitor.check("default"), monitor.reconnect("default")];
|
|
373
|
+
|
|
374
|
+
for (var i = 0; i < 5; i++) {
|
|
375
|
+
monitor.recordOutcome("default", timeoutError());
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
release();
|
|
379
|
+
yield Promise.all([first, ...others]);
|
|
380
|
+
assert.equal(opened.length, 1);
|
|
381
|
+
assert.equal(monitor.status().default.generation, 2);
|
|
382
|
+
}));
|
|
383
|
+
test("a probe that hangs is cut off by the hard deadline",
|
|
384
|
+
/*#__PURE__*/
|
|
385
|
+
_asyncToGenerator(function* () {
|
|
386
|
+
var {
|
|
387
|
+
monitor,
|
|
388
|
+
initial,
|
|
389
|
+
opened
|
|
390
|
+
} = setup({
|
|
391
|
+
settings: {
|
|
392
|
+
probeFailureThreshold: 1
|
|
393
|
+
}
|
|
394
|
+
});
|
|
395
|
+
initial.hang = true; // The deadline timer is unref'd; keep the loop alive while the probe hangs.
|
|
396
|
+
|
|
397
|
+
var keepAlive = setInterval(() => {}, 1000);
|
|
398
|
+
|
|
399
|
+
try {
|
|
400
|
+
var started = Date.now();
|
|
401
|
+
yield monitor.check("default");
|
|
402
|
+
assert.ok(Date.now() - started < 1500);
|
|
403
|
+
assert.equal(opened.length, 1);
|
|
404
|
+
} finally {
|
|
405
|
+
clearInterval(keepAlive);
|
|
406
|
+
}
|
|
407
|
+
}));
|
|
408
|
+
test("a failed reconnect backs off and keeps the old cluster",
|
|
409
|
+
/*#__PURE__*/
|
|
410
|
+
_asyncToGenerator(function* () {
|
|
411
|
+
var opened = [];
|
|
412
|
+
var {
|
|
413
|
+
monitor,
|
|
414
|
+
initial,
|
|
415
|
+
globals,
|
|
416
|
+
time
|
|
417
|
+
} = setup({
|
|
418
|
+
settings: {
|
|
419
|
+
probeFailureThreshold: 1
|
|
420
|
+
},
|
|
421
|
+
connect: function () {
|
|
422
|
+
var _connect = _asyncToGenerator(function* () {
|
|
423
|
+
var cluster = fakeCluster("bad-".concat(opened.length + 1), {
|
|
424
|
+
healthy: false
|
|
425
|
+
});
|
|
426
|
+
opened.push(cluster);
|
|
427
|
+
return cluster;
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
function connect() {
|
|
431
|
+
return _connect.apply(this, arguments);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return connect;
|
|
435
|
+
}()
|
|
436
|
+
});
|
|
437
|
+
initial.healthy = false;
|
|
438
|
+
yield monitor.check("default");
|
|
439
|
+
assert.equal(opened.length, 1);
|
|
440
|
+
assert.equal(opened[0].closed, 1, "unverified cluster is closed");
|
|
441
|
+
assert.equal(globals.database.clusters.default, initial);
|
|
442
|
+
assert.equal(monitor.status().default.reconnectAttempts, 1); // Inside the backoff window: probe only, no new connect.
|
|
443
|
+
|
|
444
|
+
yield monitor.check("default");
|
|
445
|
+
assert.equal(opened.length, 1);
|
|
446
|
+
time.now += 150;
|
|
447
|
+
yield monitor.check("default");
|
|
448
|
+
assert.equal(opened.length, 2);
|
|
449
|
+
assert.equal(monitor.status().default.reconnectAttempts, 2);
|
|
450
|
+
}));
|
|
451
|
+
test("a connection that recovers on its own is marked healthy again",
|
|
452
|
+
/*#__PURE__*/
|
|
453
|
+
_asyncToGenerator(function* () {
|
|
454
|
+
var {
|
|
455
|
+
monitor,
|
|
456
|
+
initial,
|
|
457
|
+
time
|
|
458
|
+
} = setup({
|
|
459
|
+
settings: {
|
|
460
|
+
probeFailureThreshold: 1
|
|
461
|
+
},
|
|
462
|
+
connect: function () {
|
|
463
|
+
var _connect2 = _asyncToGenerator(function* () {
|
|
464
|
+
return fakeCluster("bad", {
|
|
465
|
+
healthy: false
|
|
466
|
+
});
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
function connect() {
|
|
470
|
+
return _connect2.apply(this, arguments);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return connect;
|
|
474
|
+
}()
|
|
475
|
+
});
|
|
476
|
+
initial.healthy = false;
|
|
477
|
+
yield monitor.check("default");
|
|
478
|
+
assert.equal(monitor.status().default.healthy, false);
|
|
479
|
+
initial.healthy = true;
|
|
480
|
+
time.now += 5000;
|
|
481
|
+
yield monitor.check("default");
|
|
482
|
+
var status = monitor.status().default;
|
|
483
|
+
assert.equal(status.healthy, true);
|
|
484
|
+
assert.equal(status.reconnectAttempts, 0);
|
|
485
|
+
assert.equal(status.unhealthySince, null);
|
|
486
|
+
}));
|
|
487
|
+
test("staying unhealthy past the limit terminates the process once",
|
|
488
|
+
/*#__PURE__*/
|
|
489
|
+
_asyncToGenerator(function* () {
|
|
490
|
+
var {
|
|
491
|
+
monitor,
|
|
492
|
+
initial,
|
|
493
|
+
time,
|
|
494
|
+
terminated
|
|
495
|
+
} = setup({
|
|
496
|
+
settings: {
|
|
497
|
+
probeFailureThreshold: 1,
|
|
498
|
+
exitAfterMs: 1000
|
|
499
|
+
},
|
|
500
|
+
connect: function () {
|
|
501
|
+
var _connect3 = _asyncToGenerator(function* () {
|
|
502
|
+
return fakeCluster("bad", {
|
|
503
|
+
healthy: false
|
|
504
|
+
});
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
function connect() {
|
|
508
|
+
return _connect3.apply(this, arguments);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
return connect;
|
|
512
|
+
}()
|
|
513
|
+
});
|
|
514
|
+
initial.healthy = false;
|
|
515
|
+
yield monitor.check("default");
|
|
516
|
+
assert.equal(terminated.length, 0);
|
|
517
|
+
time.now += 1500;
|
|
518
|
+
yield monitor.check("default");
|
|
519
|
+
time.now += 1500;
|
|
520
|
+
yield monitor.check("default");
|
|
521
|
+
assert.equal(terminated.length, 1);
|
|
522
|
+
}));
|
|
523
|
+
test("exitAfterMs of 0 never terminates",
|
|
524
|
+
/*#__PURE__*/
|
|
525
|
+
_asyncToGenerator(function* () {
|
|
526
|
+
var {
|
|
527
|
+
monitor,
|
|
528
|
+
initial,
|
|
529
|
+
time,
|
|
530
|
+
terminated
|
|
531
|
+
} = setup({
|
|
532
|
+
settings: {
|
|
533
|
+
probeFailureThreshold: 1,
|
|
534
|
+
exitAfterMs: 0
|
|
535
|
+
},
|
|
536
|
+
connect: function () {
|
|
537
|
+
var _connect4 = _asyncToGenerator(function* () {
|
|
538
|
+
return fakeCluster("bad", {
|
|
539
|
+
healthy: false
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
function connect() {
|
|
544
|
+
return _connect4.apply(this, arguments);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return connect;
|
|
548
|
+
}()
|
|
549
|
+
});
|
|
550
|
+
initial.healthy = false;
|
|
551
|
+
|
|
552
|
+
for (var i = 0; i < 5; i++) {
|
|
553
|
+
time.now += 100000;
|
|
554
|
+
yield monitor.check("default");
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
assert.equal(terminated.length, 0);
|
|
558
|
+
}));
|
|
559
|
+
test("stop() halts reconnects and closes retired clusters",
|
|
560
|
+
/*#__PURE__*/
|
|
561
|
+
_asyncToGenerator(function* () {
|
|
562
|
+
var {
|
|
563
|
+
monitor,
|
|
564
|
+
initial,
|
|
565
|
+
opened
|
|
566
|
+
} = setup({
|
|
567
|
+
settings: {
|
|
568
|
+
probeFailureThreshold: 1,
|
|
569
|
+
retireGraceMs: 60000
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
initial.healthy = false;
|
|
573
|
+
yield monitor.check("default");
|
|
574
|
+
assert.equal(opened.length, 1);
|
|
575
|
+
yield monitor.stop();
|
|
576
|
+
assert.equal(initial.closed, 1);
|
|
577
|
+
opened[0].healthy = false;
|
|
578
|
+
yield monitor.check("default");
|
|
579
|
+
yield monitor.reconnect("default");
|
|
580
|
+
assert.equal(opened.length, 1);
|
|
581
|
+
}));
|
|
582
|
+
test("the interval probe runs when enabled",
|
|
583
|
+
/*#__PURE__*/
|
|
584
|
+
_asyncToGenerator(function* () {
|
|
585
|
+
var {
|
|
586
|
+
monitor,
|
|
587
|
+
initial,
|
|
588
|
+
opened
|
|
589
|
+
} = setup({
|
|
590
|
+
settings: {
|
|
591
|
+
healthIntervalMs: 5,
|
|
592
|
+
probeFailureThreshold: 1
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
initial.healthy = false;
|
|
596
|
+
yield sleep(40);
|
|
597
|
+
assert.ok(opened.length >= 1);
|
|
598
|
+
yield monitor.stop();
|
|
599
|
+
}));
|
|
600
|
+
test("a Database instance follows the swapped globals",
|
|
601
|
+
/*#__PURE__*/
|
|
602
|
+
_asyncToGenerator(function* () {
|
|
603
|
+
var path = require.resolve("./index");
|
|
604
|
+
|
|
605
|
+
delete require.cache[path];
|
|
606
|
+
var previousSpice = global.spice;
|
|
607
|
+
var {
|
|
608
|
+
monitor,
|
|
609
|
+
initial,
|
|
610
|
+
opened,
|
|
611
|
+
globals
|
|
612
|
+
} = setup({
|
|
613
|
+
settings: {
|
|
614
|
+
probeFailureThreshold: 1
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
global.spice = _objectSpread({}, globals, {
|
|
618
|
+
config: {
|
|
619
|
+
database: {
|
|
620
|
+
connections: {
|
|
621
|
+
default: _objectSpread({}, config)
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}); // Point the shared module-level monitor at this test's monitor.
|
|
626
|
+
|
|
627
|
+
var key = Symbol.for("spice-couchbase.health-monitor");
|
|
628
|
+
var previousMonitor = globalThis[key];
|
|
629
|
+
globalThis[key] = monitor;
|
|
630
|
+
|
|
631
|
+
try {
|
|
632
|
+
// Keep a reference to the same globals object the monitor mutates.
|
|
633
|
+
global.spice.database = globals.database; // The constructor reads the bucket name off the boot-time collection.
|
|
634
|
+
|
|
635
|
+
globals.database.connections.default._scope = {
|
|
636
|
+
_bucket: {
|
|
637
|
+
_name: "saturn"
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
var Database = require("./index");
|
|
642
|
+
|
|
643
|
+
var db = new Database("default");
|
|
644
|
+
assert.equal(db.cluster, initial);
|
|
645
|
+
var oldCollection = db.collection;
|
|
646
|
+
assert.equal(oldCollection.cluster, initial);
|
|
647
|
+
initial.healthy = false;
|
|
648
|
+
yield monitor.check("default");
|
|
649
|
+
assert.equal(opened.length, 1);
|
|
650
|
+
assert.equal(db.cluster, opened[0]);
|
|
651
|
+
assert.equal(db.collection.cluster, opened[0]);
|
|
652
|
+
assert.notEqual(db.collection, oldCollection);
|
|
653
|
+
assert.deepEqual(Database.healthStatus().default.generation, 2);
|
|
654
|
+
} finally {
|
|
655
|
+
globalThis[key] = previousMonitor;
|
|
656
|
+
global.spice = previousSpice;
|
|
657
|
+
delete require.cache[path];
|
|
658
|
+
}
|
|
659
|
+
}));
|