@brimble/consul 0.0.1
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/LICENSE +19 -0
- package/README.md +2423 -0
- package/lib/acl/legacy.d.ts +75 -0
- package/lib/acl/legacy.js +167 -0
- package/lib/acl.d.ts +37 -0
- package/lib/acl.js +49 -0
- package/lib/agent/check.d.ts +113 -0
- package/lib/agent/check.js +164 -0
- package/lib/agent/service.d.ts +63 -0
- package/lib/agent/service.js +113 -0
- package/lib/agent.d.ts +77 -0
- package/lib/agent.js +162 -0
- package/lib/catalog/connect.d.ts +11 -0
- package/lib/catalog/connect.js +37 -0
- package/lib/catalog/node.d.ts +30 -0
- package/lib/catalog/node.js +57 -0
- package/lib/catalog/service.d.ts +32 -0
- package/lib/catalog/service.js +61 -0
- package/lib/catalog.d.ts +118 -0
- package/lib/catalog.js +113 -0
- package/lib/constants.js +21 -0
- package/lib/consul.d.ts +93 -0
- package/lib/consul.js +98 -0
- package/lib/errors.js +29 -0
- package/lib/event.d.ts +43 -0
- package/lib/event.js +91 -0
- package/lib/health.d.ts +77 -0
- package/lib/health.js +126 -0
- package/lib/index.d.ts +18 -0
- package/lib/index.js +3 -0
- package/lib/intention.d.ts +73 -0
- package/lib/intention.js +180 -0
- package/lib/kv.d.ts +80 -0
- package/lib/kv.js +180 -0
- package/lib/query.d.ts +84 -0
- package/lib/query.js +244 -0
- package/lib/resolver/algorithms.js +127 -0
- package/lib/resolver/dns.js +182 -0
- package/lib/resolver/health.js +51 -0
- package/lib/resolver/metrics.js +199 -0
- package/lib/resolver/scoring.js +95 -0
- package/lib/resolver/types.js +28 -0
- package/lib/resolver.d.ts +76 -0
- package/lib/resolver.js +290 -0
- package/lib/session.d.ts +92 -0
- package/lib/session.js +164 -0
- package/lib/status.d.ts +19 -0
- package/lib/status.js +43 -0
- package/lib/transaction.d.ts +50 -0
- package/lib/transaction.js +58 -0
- package/lib/utils.js +655 -0
- package/lib/watch.d.ts +22 -0
- package/lib/watch.js +183 -0
- package/package.json +55 -0
package/lib/query.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const errors = require("./errors");
|
|
2
|
+
const utils = require("./utils");
|
|
3
|
+
|
|
4
|
+
class Query {
|
|
5
|
+
constructor(consul) {
|
|
6
|
+
this.consul = consul;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Lists all queries
|
|
11
|
+
*/
|
|
12
|
+
async list(opts) {
|
|
13
|
+
opts = utils.normalizeKeys(opts);
|
|
14
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
15
|
+
|
|
16
|
+
const req = {
|
|
17
|
+
name: "query.list",
|
|
18
|
+
path: "/query",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
utils.options(req, opts);
|
|
22
|
+
|
|
23
|
+
return await this.consul._get(req, utils.body);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create a new query
|
|
28
|
+
*/
|
|
29
|
+
async create(opts) {
|
|
30
|
+
if (typeof opts === "string") {
|
|
31
|
+
opts = { service: { service: opts } };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
opts = utils.normalizeKeys(opts);
|
|
35
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
36
|
+
|
|
37
|
+
const req = {
|
|
38
|
+
name: "query.create",
|
|
39
|
+
path: "/query",
|
|
40
|
+
query: {},
|
|
41
|
+
type: "json",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
this._params(req, opts);
|
|
45
|
+
|
|
46
|
+
if (!req.body.Service || !req.body.Service.Service) {
|
|
47
|
+
throw this.consul._err(errors.Validation("service required"), req);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
utils.options(req, opts, { near: true });
|
|
51
|
+
|
|
52
|
+
return await this.consul._post(req, utils.body);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Gets a given query
|
|
57
|
+
*/
|
|
58
|
+
async get(opts) {
|
|
59
|
+
if (typeof opts === "string") {
|
|
60
|
+
opts = { query: opts };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
opts = utils.normalizeKeys(opts);
|
|
64
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
65
|
+
|
|
66
|
+
const req = {
|
|
67
|
+
name: "query.get",
|
|
68
|
+
path: "/query/{query}",
|
|
69
|
+
params: { query: opts.query },
|
|
70
|
+
query: {},
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
if (!opts.query) {
|
|
74
|
+
throw this.consul._err(errors.Validation("query required"), req);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
utils.options(req, opts);
|
|
78
|
+
|
|
79
|
+
return await this.consul._get(req, utils.bodyItem);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Update existing query
|
|
84
|
+
*/
|
|
85
|
+
async update(opts) {
|
|
86
|
+
opts = utils.normalizeKeys(opts);
|
|
87
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
88
|
+
|
|
89
|
+
const req = {
|
|
90
|
+
name: "query.update",
|
|
91
|
+
path: "/query/{query}",
|
|
92
|
+
params: { query: opts.query },
|
|
93
|
+
query: {},
|
|
94
|
+
type: "json",
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (!opts.query) {
|
|
98
|
+
throw this.consul._err(errors.Validation("query required"), req);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
this._params(req, opts);
|
|
102
|
+
|
|
103
|
+
if (!req.body.Service || !req.body.Service.Service) {
|
|
104
|
+
throw this.consul._err(errors.Validation("service required"), req);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
utils.options(req, opts, { near: true });
|
|
108
|
+
|
|
109
|
+
return await this.consul._put(req, utils.empty);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Destroys a given query
|
|
114
|
+
*/
|
|
115
|
+
async destroy(opts) {
|
|
116
|
+
if (typeof opts === "string") {
|
|
117
|
+
opts = { query: opts };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
opts = utils.normalizeKeys(opts);
|
|
121
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
122
|
+
|
|
123
|
+
const req = {
|
|
124
|
+
name: "query.destroy",
|
|
125
|
+
path: "/query/{query}",
|
|
126
|
+
params: { query: opts.query },
|
|
127
|
+
query: {},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (!opts.query) {
|
|
131
|
+
throw this.consul._err(errors.Validation("query required"), req);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
utils.options(req, opts);
|
|
135
|
+
|
|
136
|
+
return await this.consul._delete(req, utils.empty);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Executes a given query
|
|
141
|
+
*/
|
|
142
|
+
async execute(opts) {
|
|
143
|
+
if (typeof opts === "string") {
|
|
144
|
+
opts = { query: opts };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
opts = utils.normalizeKeys(opts);
|
|
148
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
149
|
+
|
|
150
|
+
const req = {
|
|
151
|
+
name: "query.execute",
|
|
152
|
+
path: "/query/{query}/execute",
|
|
153
|
+
params: { query: opts.query },
|
|
154
|
+
query: {},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (!opts.query) {
|
|
158
|
+
throw this.consul._err(errors.Validation("query required"), req);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
utils.options(req, opts);
|
|
162
|
+
|
|
163
|
+
return await this.consul._get(req, utils.body);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Explain a given query
|
|
168
|
+
*/
|
|
169
|
+
async explain(opts) {
|
|
170
|
+
if (typeof opts === "string") {
|
|
171
|
+
opts = { query: opts };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
opts = utils.normalizeKeys(opts);
|
|
175
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
176
|
+
|
|
177
|
+
const req = {
|
|
178
|
+
name: "query.explain",
|
|
179
|
+
path: "/query/{query}/explain",
|
|
180
|
+
params: { query: opts.query },
|
|
181
|
+
query: {},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
if (!opts.query) {
|
|
185
|
+
throw this.consul._err(errors.Validation("query required"), req);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
utils.options(req, opts);
|
|
189
|
+
|
|
190
|
+
return await this.consul._get(req, utils.bodyItem);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Generate body for query create and update
|
|
195
|
+
*/
|
|
196
|
+
_params(req, opts) {
|
|
197
|
+
const body = req.body || {};
|
|
198
|
+
|
|
199
|
+
if (opts.name) body.Name = opts.name;
|
|
200
|
+
if (opts.session) body.Session = opts.session;
|
|
201
|
+
if (opts.token) {
|
|
202
|
+
body.Token = opts.token;
|
|
203
|
+
delete opts.token;
|
|
204
|
+
}
|
|
205
|
+
if (opts.near) body.Near = opts.near;
|
|
206
|
+
if (opts.template) {
|
|
207
|
+
const template = utils.normalizeKeys(opts.template);
|
|
208
|
+
if (template.type || template.regexp) {
|
|
209
|
+
body.Template = {};
|
|
210
|
+
if (template.type) body.Template.Type = template.type;
|
|
211
|
+
if (template.regexp) body.Template.Regexp = template.regexp;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (opts.service) {
|
|
215
|
+
const service = utils.normalizeKeys(opts.service);
|
|
216
|
+
body.Service = {};
|
|
217
|
+
if (service.service) body.Service.Service = service.service;
|
|
218
|
+
if (service.failover) {
|
|
219
|
+
const failover = utils.normalizeKeys(service.failover);
|
|
220
|
+
if (typeof failover.nearestn === "number" || failover.datacenters) {
|
|
221
|
+
body.Service.Failover = {};
|
|
222
|
+
if (typeof failover.nearestn === "number") {
|
|
223
|
+
body.Service.Failover.NearestN = failover.nearestn;
|
|
224
|
+
}
|
|
225
|
+
if (failover.datacenters) {
|
|
226
|
+
body.Service.Failover.Datacenters = failover.datacenters;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (typeof service.onlypassing === "boolean") {
|
|
231
|
+
body.Service.OnlyPassing = service.onlypassing;
|
|
232
|
+
}
|
|
233
|
+
if (service.tags) body.Service.Tags = service.tags;
|
|
234
|
+
}
|
|
235
|
+
if (opts.dns) {
|
|
236
|
+
const dns = utils.normalizeKeys(opts.dns);
|
|
237
|
+
if (dns.ttl) body.DNS = { TTL: dns.ttl };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
req.body = body;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
exports.Query = Query;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
function roundRobinSelection(services, currentIndex) {
|
|
4
|
+
const healthyServices = services.filter((service) =>
|
|
5
|
+
service.Checks.every((check) => check.Status === "passing"),
|
|
6
|
+
);
|
|
7
|
+
|
|
8
|
+
if (healthyServices.length === 0) {
|
|
9
|
+
throw new Error("No healthy services available");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const service = healthyServices[currentIndex % healthyServices.length];
|
|
13
|
+
const nextIndex = (currentIndex + 1) % healthyServices.length;
|
|
14
|
+
|
|
15
|
+
return {
|
|
16
|
+
id: service.Service.ID,
|
|
17
|
+
service,
|
|
18
|
+
nextIndex,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function leastConnectionSelection(services, metrics, defaultMetrics) {
|
|
23
|
+
const healthyServices = services
|
|
24
|
+
.filter((service) =>
|
|
25
|
+
service.Checks.every((check) => check.Status === "passing"),
|
|
26
|
+
)
|
|
27
|
+
.map((service) => {
|
|
28
|
+
const serviceMetrics = metrics.get(service.Service.ID) || defaultMetrics;
|
|
29
|
+
return {
|
|
30
|
+
service,
|
|
31
|
+
connections: serviceMetrics.activeConnections || 0,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
if (healthyServices.length === 0) {
|
|
36
|
+
throw new Error("No healthy services available");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const selectedService = healthyServices.reduce((min, current) =>
|
|
40
|
+
current.connections < min.connections ? current : min,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
id: selectedService.service.Service.ID,
|
|
45
|
+
service: selectedService.service,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function weightedRandomSelection(rankedServices) {
|
|
50
|
+
if (rankedServices.length === 0) {
|
|
51
|
+
throw new Error("No services available for selection");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const totalScore = rankedServices.reduce(
|
|
55
|
+
(sum, service) => sum + service.score,
|
|
56
|
+
0,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (totalScore <= 0) {
|
|
60
|
+
return {
|
|
61
|
+
id: rankedServices[0].id,
|
|
62
|
+
service: rankedServices[0].service,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
let random = Math.random() * totalScore;
|
|
67
|
+
|
|
68
|
+
for (const service of rankedServices) {
|
|
69
|
+
random -= service.score;
|
|
70
|
+
if (random <= 0) {
|
|
71
|
+
return {
|
|
72
|
+
id: service.id,
|
|
73
|
+
service: service.service,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
id: rankedServices[0].id,
|
|
80
|
+
service: rankedServices[0].service,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function roundRobinSrvSelection(records, currentIndex) {
|
|
85
|
+
if (!records || records.length === 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const selected = records[currentIndex % records.length];
|
|
90
|
+
const nextIndex = (currentIndex + 1) % records.length;
|
|
91
|
+
|
|
92
|
+
return { selected, nextIndex };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function weightedSrvRecordSelection(records, currentIndex) {
|
|
96
|
+
if (!records || records.length === 0) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const hasNonZeroWeights = records.some((record) => (record.weight || 0) > 0);
|
|
101
|
+
|
|
102
|
+
if (!hasNonZeroWeights) {
|
|
103
|
+
return roundRobinSrvSelection(records, currentIndex);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const totalWeight = records.reduce(
|
|
107
|
+
(sum, record) => sum + (record.weight || 1),
|
|
108
|
+
0,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
let random = Math.random() * totalWeight;
|
|
112
|
+
|
|
113
|
+
for (const record of records) {
|
|
114
|
+
random -= record.weight || 1;
|
|
115
|
+
if (random <= 0) {
|
|
116
|
+
return { selected: record, nextIndex: currentIndex };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { selected: records[0], nextIndex: currentIndex };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
exports.roundRobinSelection = roundRobinSelection;
|
|
124
|
+
exports.leastConnectionSelection = leastConnectionSelection;
|
|
125
|
+
exports.weightedRandomSelection = weightedRandomSelection;
|
|
126
|
+
exports.roundRobinSrvSelection = roundRobinSrvSelection;
|
|
127
|
+
exports.weightedSrvRecordSelection = weightedSrvRecordSelection;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { log } = require("@brimble/utils");
|
|
4
|
+
const { query } = require("dns-query");
|
|
5
|
+
const {
|
|
6
|
+
roundRobinSrvSelection,
|
|
7
|
+
weightedSrvRecordSelection,
|
|
8
|
+
} = require("./algorithms");
|
|
9
|
+
const { SelectionAlgorithm } = require("./types");
|
|
10
|
+
|
|
11
|
+
class DNSManager {
|
|
12
|
+
constructor(
|
|
13
|
+
redis,
|
|
14
|
+
cachePrefix,
|
|
15
|
+
cacheTTL,
|
|
16
|
+
cacheEnabled,
|
|
17
|
+
debug,
|
|
18
|
+
dnsEndpoints,
|
|
19
|
+
dnsTimeout,
|
|
20
|
+
dnsRetries,
|
|
21
|
+
) {
|
|
22
|
+
this.redis = redis;
|
|
23
|
+
this.cachePrefix = cachePrefix;
|
|
24
|
+
this.cacheTTL = cacheTTL;
|
|
25
|
+
this.cacheEnabled = cacheEnabled;
|
|
26
|
+
this.debug = debug;
|
|
27
|
+
this.dnsEndpoints = dnsEndpoints;
|
|
28
|
+
this.dnsTimeout = dnsTimeout;
|
|
29
|
+
this.dnsRetries = dnsRetries;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getDNSCacheKey(service) {
|
|
33
|
+
return `${this.cachePrefix}:dns:${service}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async resolveDNS(service) {
|
|
37
|
+
const cacheKey = this.getDNSCacheKey(service);
|
|
38
|
+
|
|
39
|
+
if (this.cacheEnabled) {
|
|
40
|
+
try {
|
|
41
|
+
const cachedData = await Promise.race([
|
|
42
|
+
this.redis?.get(cacheKey),
|
|
43
|
+
new Promise((_, reject) =>
|
|
44
|
+
setTimeout(() => reject(new Error("Redis get timeout")), 200),
|
|
45
|
+
),
|
|
46
|
+
]);
|
|
47
|
+
if (cachedData) {
|
|
48
|
+
if (this.debug) {
|
|
49
|
+
log.debug(`DNS cache hit for ${service}`);
|
|
50
|
+
}
|
|
51
|
+
return JSON.parse(cachedData);
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
if (this.debug) log.error("Redis get error or timeout:", e);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const result = await query(
|
|
60
|
+
{
|
|
61
|
+
question: {
|
|
62
|
+
type: "SRV",
|
|
63
|
+
name: service,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
endpoints: this.dnsEndpoints,
|
|
68
|
+
timeout: this.dnsTimeout ?? 1500,
|
|
69
|
+
retries: this.dnsRetries ?? 2,
|
|
70
|
+
},
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (this.debug) {
|
|
74
|
+
log.debug("DNS QUERY RESULT", result);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!result.answers || result.answers.length === 0) {
|
|
78
|
+
if (this.debug) {
|
|
79
|
+
log.debug(`No SRV records found for ${service}`);
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const additionalsByName = {};
|
|
85
|
+
if (result.additionals) {
|
|
86
|
+
for (const additional of result.additionals) {
|
|
87
|
+
if (additional.type === "A") {
|
|
88
|
+
additionalsByName[additional.name] = additional;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const records = result.answers
|
|
93
|
+
.map((answer) => {
|
|
94
|
+
const target = answer.data.target;
|
|
95
|
+
const aRecord = additionalsByName[target];
|
|
96
|
+
return {
|
|
97
|
+
name: target,
|
|
98
|
+
ip: aRecord?.data || "",
|
|
99
|
+
port: answer.data.port,
|
|
100
|
+
priority: answer.data.priority,
|
|
101
|
+
weight: answer.data.weight,
|
|
102
|
+
};
|
|
103
|
+
})
|
|
104
|
+
.filter((record) => record.ip);
|
|
105
|
+
|
|
106
|
+
if (this.cacheEnabled) {
|
|
107
|
+
try {
|
|
108
|
+
await Promise.race([
|
|
109
|
+
this.redis?.set(
|
|
110
|
+
cacheKey,
|
|
111
|
+
JSON.stringify(records),
|
|
112
|
+
"EX",
|
|
113
|
+
this.cacheTTL,
|
|
114
|
+
),
|
|
115
|
+
new Promise((_, reject) =>
|
|
116
|
+
setTimeout(() => reject(new Error("Redis set timeout")), 200),
|
|
117
|
+
),
|
|
118
|
+
]);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
if (this.debug) log.error("Redis set error or timeout:", e);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return records;
|
|
125
|
+
} catch (error) {
|
|
126
|
+
if (this.debug) {
|
|
127
|
+
log.error("DNS resolution error:", error);
|
|
128
|
+
}
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
sortByPriority(records) {
|
|
134
|
+
return [...records].sort((a, b) => (a.priority || 0) - (b.priority || 0));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
selectFromSrvRecords(records, algorithm, currentIndex) {
|
|
138
|
+
if (!records || records.length === 0) {
|
|
139
|
+
return { selected: null, nextIndex: currentIndex };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
switch (algorithm) {
|
|
143
|
+
case SelectionAlgorithm.RoundRobin: {
|
|
144
|
+
const rrResult = roundRobinSrvSelection(records, currentIndex);
|
|
145
|
+
if (!rrResult) {
|
|
146
|
+
return { selected: null, nextIndex: currentIndex };
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
selected: rrResult.selected,
|
|
150
|
+
nextIndex: rrResult.nextIndex,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
case SelectionAlgorithm.WeightedRoundRobin: {
|
|
155
|
+
const wrResult = weightedSrvRecordSelection(records, currentIndex);
|
|
156
|
+
if (!wrResult) {
|
|
157
|
+
return { selected: null, nextIndex: currentIndex };
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
selected: wrResult.selected,
|
|
161
|
+
nextIndex: wrResult.nextIndex,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
case SelectionAlgorithm.LeastConnection: {
|
|
166
|
+
const lcResult = roundRobinSrvSelection(records, currentIndex);
|
|
167
|
+
if (!lcResult) {
|
|
168
|
+
return { selected: null, nextIndex: currentIndex };
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
selected: lcResult.selected,
|
|
172
|
+
nextIndex: lcResult.nextIndex,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
default:
|
|
177
|
+
return { selected: records[0], nextIndex: currentIndex };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
exports.DNSManager = DNSManager;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { log } = require("@brimble/utils");
|
|
4
|
+
|
|
5
|
+
class HealthCheckManager {
|
|
6
|
+
constructor(consul, redis, cachePrefix, cacheTTL, cacheEnabled, debug) {
|
|
7
|
+
this.consul = consul;
|
|
8
|
+
this.redis = redis;
|
|
9
|
+
this.cachePrefix = cachePrefix;
|
|
10
|
+
this.cacheTTL = cacheTTL;
|
|
11
|
+
this.cacheEnabled = cacheEnabled;
|
|
12
|
+
this.debug = debug;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getHealthCacheKey(service) {
|
|
16
|
+
return `${this.cachePrefix}:health:${service}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async getHealthChecks(service) {
|
|
20
|
+
const cacheKey = this.getHealthCacheKey(service);
|
|
21
|
+
|
|
22
|
+
if (this.cacheEnabled) {
|
|
23
|
+
const cachedHealth = await this.redis?.get(cacheKey);
|
|
24
|
+
if (cachedHealth) {
|
|
25
|
+
return JSON.parse(cachedHealth);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const healthChecks = await this.consul.health.service(service);
|
|
31
|
+
|
|
32
|
+
if (this.cacheEnabled) {
|
|
33
|
+
await this.redis?.set(
|
|
34
|
+
cacheKey,
|
|
35
|
+
JSON.stringify(healthChecks),
|
|
36
|
+
"EX",
|
|
37
|
+
this.cacheTTL,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return healthChecks;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (this.debug) {
|
|
44
|
+
log.error(`Error fetching health checks for ${service}:`, error);
|
|
45
|
+
}
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
exports.HealthCheckManager = HealthCheckManager;
|