@brimble/consul 1.0.0 → 1.0.2
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/README.md +277 -0
- package/lib/config.d.ts +55 -0
- package/lib/config.js +117 -0
- package/lib/consul.d.ts +23 -0
- package/lib/consul.js +9 -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/package.json +6 -1
package/README.md
CHANGED
|
@@ -24,11 +24,13 @@ See the official [HTTP API][consul-docs-api] docs for more information.
|
|
|
24
24
|
- [Connect](#catalog-connect)
|
|
25
25
|
- [Node](#catalog-node)
|
|
26
26
|
- [Service](#catalog-service)
|
|
27
|
+
* [Config](#config)
|
|
27
28
|
* [Event](#event)
|
|
28
29
|
* [Health](#health)
|
|
29
30
|
* [Intention](#intention)
|
|
30
31
|
* [KV](#kv)
|
|
31
32
|
* [Query](#query)
|
|
33
|
+
* [Resolver](#resolver)
|
|
32
34
|
* [Session](#session)
|
|
33
35
|
* [Status](#status)
|
|
34
36
|
* [Transaction](#transaction)
|
|
@@ -1066,6 +1068,101 @@ Result
|
|
|
1066
1068
|
]
|
|
1067
1069
|
```
|
|
1068
1070
|
|
|
1071
|
+
<a id="config"></a>
|
|
1072
|
+
|
|
1073
|
+
### consul.config
|
|
1074
|
+
|
|
1075
|
+
- [list](#config-list)
|
|
1076
|
+
- [get](#config-get)
|
|
1077
|
+
- [set](#config-set)
|
|
1078
|
+
- [destroy](#config-destroy)
|
|
1079
|
+
|
|
1080
|
+
<a id="config-list"></a>
|
|
1081
|
+
|
|
1082
|
+
### consul.config.list(options)
|
|
1083
|
+
|
|
1084
|
+
Lists config entries for a given kind.
|
|
1085
|
+
|
|
1086
|
+
Options
|
|
1087
|
+
|
|
1088
|
+
- kind (String): config entry kind
|
|
1089
|
+
- dc (String, optional): datacenter
|
|
1090
|
+
|
|
1091
|
+
Usage
|
|
1092
|
+
|
|
1093
|
+
```javascript
|
|
1094
|
+
await consul.config.list("service-resolver");
|
|
1095
|
+
```
|
|
1096
|
+
|
|
1097
|
+
<a id="config-get"></a>
|
|
1098
|
+
|
|
1099
|
+
### consul.config.get(options)
|
|
1100
|
+
|
|
1101
|
+
Gets a config entry.
|
|
1102
|
+
|
|
1103
|
+
Options
|
|
1104
|
+
|
|
1105
|
+
- kind (String): config entry kind
|
|
1106
|
+
- name (String): config entry name
|
|
1107
|
+
- dc (String, optional): datacenter
|
|
1108
|
+
|
|
1109
|
+
Usage
|
|
1110
|
+
|
|
1111
|
+
```javascript
|
|
1112
|
+
await consul.config.get({
|
|
1113
|
+
kind: "service-resolver",
|
|
1114
|
+
name: "salary-index-ng",
|
|
1115
|
+
});
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
<a id="config-set"></a>
|
|
1119
|
+
|
|
1120
|
+
### consul.config.set(entry[, options])
|
|
1121
|
+
|
|
1122
|
+
Creates or updates a config entry.
|
|
1123
|
+
|
|
1124
|
+
Options
|
|
1125
|
+
|
|
1126
|
+
- entry (Object): Consul config entry payload
|
|
1127
|
+
- token (String, optional): ACL token
|
|
1128
|
+
- dc (String, optional): datacenter
|
|
1129
|
+
|
|
1130
|
+
Usage
|
|
1131
|
+
|
|
1132
|
+
```javascript
|
|
1133
|
+
await consul.config.set(
|
|
1134
|
+
{
|
|
1135
|
+
Kind: "service-resolver",
|
|
1136
|
+
Name: "salary-index-ng",
|
|
1137
|
+
Redirect: {
|
|
1138
|
+
Service: "6a3ea78d3d66fd3ab1dbbb12",
|
|
1139
|
+
},
|
|
1140
|
+
},
|
|
1141
|
+
{ token: process.env.CONSUL_HTTP_TOKEN },
|
|
1142
|
+
);
|
|
1143
|
+
```
|
|
1144
|
+
|
|
1145
|
+
<a id="config-destroy"></a>
|
|
1146
|
+
|
|
1147
|
+
### consul.config.destroy(options)
|
|
1148
|
+
|
|
1149
|
+
Deletes a config entry. Alias: `consul.config.delete(options)`.
|
|
1150
|
+
|
|
1151
|
+
Options
|
|
1152
|
+
|
|
1153
|
+
- kind (String): config entry kind
|
|
1154
|
+
- name (String): config entry name
|
|
1155
|
+
- dc (String, optional): datacenter
|
|
1156
|
+
|
|
1157
|
+
Usage
|
|
1158
|
+
|
|
1159
|
+
```javascript
|
|
1160
|
+
await consul.config.destroy({
|
|
1161
|
+
kind: "service-resolver",
|
|
1162
|
+
name: "salary-index-ng",
|
|
1163
|
+
});
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1069
1166
|
<a id="event"></a>
|
|
1070
1167
|
|
|
1071
1168
|
### consul.event
|
|
@@ -1486,6 +1583,186 @@ Usage
|
|
|
1486
1583
|
await consul.intention.destroy("a0f5dc05-84c3-5f5a-1d88-05b875e524e1");
|
|
1487
1584
|
```
|
|
1488
1585
|
|
|
1586
|
+
<a id="resolver"></a>
|
|
1587
|
+
|
|
1588
|
+
### consul.resolver(config)
|
|
1589
|
+
|
|
1590
|
+
Create a DNS resolver instance for load balancing and service discovery with Consul.
|
|
1591
|
+
|
|
1592
|
+
The resolver provides intelligent service selection using multiple algorithms, DNS resolution, health checks, and Redis-based metrics tracking.
|
|
1593
|
+
|
|
1594
|
+
Options
|
|
1595
|
+
|
|
1596
|
+
- redis (Redis, optional): ioredis instance for caching (required if cacheEnabled is true)
|
|
1597
|
+
- cacheEnabled (Boolean, default: false): enable Redis caching
|
|
1598
|
+
- cachePrefix (String, required): prefix for Redis cache keys
|
|
1599
|
+
- debug (Boolean, default: false): enable debug logging
|
|
1600
|
+
- weights (Object, optional): custom weights for weighted round robin algorithm
|
|
1601
|
+
- health (Number, default: 0.25): weight for health score
|
|
1602
|
+
- responseTime (Number, default: 0.2): weight for response time
|
|
1603
|
+
- errorRate (Number, default: 0.2): weight for error rate
|
|
1604
|
+
- resources (Number, default: 0.15): weight for CPU/memory usage
|
|
1605
|
+
- connections (Number, default: 0.1): weight for active connections
|
|
1606
|
+
- distribution (Number, default: 0.1): weight for distribution fairness
|
|
1607
|
+
- metrics (Object, optional): default metrics for new services
|
|
1608
|
+
- responseTime (Number, default: 100): default response time in ms
|
|
1609
|
+
- errorRate (Number, default: 0): default error rate percentage
|
|
1610
|
+
- cpuUsage (Number, default: 50): default CPU usage percentage
|
|
1611
|
+
- memoryUsage (Number, default: 50): default memory usage percentage
|
|
1612
|
+
- activeConnections (Number, default: 0): default active connections
|
|
1613
|
+
- cacheTTL (Number, default: 60000): cache TTL in milliseconds
|
|
1614
|
+
- dnsEndpoints (String[], optional): custom DNS endpoints
|
|
1615
|
+
- dnsTimeout (Number, default: 1500): DNS query timeout in milliseconds
|
|
1616
|
+
- dnsRetries (Number, default: 2): number of DNS retry attempts
|
|
1617
|
+
|
|
1618
|
+
Usage
|
|
1619
|
+
|
|
1620
|
+
```javascript
|
|
1621
|
+
import Consul from "@brimble/consul";
|
|
1622
|
+
import Redis from "ioredis";
|
|
1623
|
+
|
|
1624
|
+
const consul = new Consul({
|
|
1625
|
+
host: "127.0.0.1",
|
|
1626
|
+
port: 8500,
|
|
1627
|
+
});
|
|
1628
|
+
|
|
1629
|
+
const redis = new Redis({
|
|
1630
|
+
host: "localhost",
|
|
1631
|
+
port: 6379,
|
|
1632
|
+
});
|
|
1633
|
+
|
|
1634
|
+
const resolver = consul.resolver({
|
|
1635
|
+
redis,
|
|
1636
|
+
cacheEnabled: true,
|
|
1637
|
+
cachePrefix: "myapp",
|
|
1638
|
+
debug: false,
|
|
1639
|
+
});
|
|
1640
|
+
```
|
|
1641
|
+
|
|
1642
|
+
<a id="resolver-select"></a>
|
|
1643
|
+
|
|
1644
|
+
### resolver.selectOptimalService(service, algorithm)
|
|
1645
|
+
|
|
1646
|
+
Select the optimal service instance based on the specified algorithm.
|
|
1647
|
+
|
|
1648
|
+
Options
|
|
1649
|
+
|
|
1650
|
+
- service (String, required): service name to resolve
|
|
1651
|
+
- algorithm (String, optional): selection algorithm
|
|
1652
|
+
- `SelectionAlgorithm.RoundRobin` (default): round-robin selection
|
|
1653
|
+
- `SelectionAlgorithm.LeastConnection`: select service with fewest connections
|
|
1654
|
+
- `SelectionAlgorithm.WeightedRoundRobin`: weighted selection based on metrics
|
|
1655
|
+
|
|
1656
|
+
Usage
|
|
1657
|
+
|
|
1658
|
+
```javascript
|
|
1659
|
+
const Consul = require("@brimble/consul");
|
|
1660
|
+
const { SelectionAlgorithm } = Consul.Resolver;
|
|
1661
|
+
|
|
1662
|
+
const result = await resolver.selectOptimalService(
|
|
1663
|
+
"my-service",
|
|
1664
|
+
SelectionAlgorithm.LeastConnection,
|
|
1665
|
+
);
|
|
1666
|
+
|
|
1667
|
+
if (result.selected) {
|
|
1668
|
+
console.log(`Selected: ${result.selected.ip}:${result.selected.port}`);
|
|
1669
|
+
}
|
|
1670
|
+
```
|
|
1671
|
+
|
|
1672
|
+
Result
|
|
1673
|
+
|
|
1674
|
+
```json
|
|
1675
|
+
{
|
|
1676
|
+
"selected": {
|
|
1677
|
+
"ip": "192.168.1.10",
|
|
1678
|
+
"port": 8080
|
|
1679
|
+
},
|
|
1680
|
+
"services": [
|
|
1681
|
+
{
|
|
1682
|
+
"ip": "192.168.1.10",
|
|
1683
|
+
"port": 8080
|
|
1684
|
+
},
|
|
1685
|
+
{
|
|
1686
|
+
"ip": "192.168.1.11",
|
|
1687
|
+
"port": 8080
|
|
1688
|
+
}
|
|
1689
|
+
]
|
|
1690
|
+
}
|
|
1691
|
+
```
|
|
1692
|
+
|
|
1693
|
+
<a id="resolver-increment"></a>
|
|
1694
|
+
|
|
1695
|
+
### resolver.incrementConnections(serviceId)
|
|
1696
|
+
|
|
1697
|
+
Increment the active connection count for a service.
|
|
1698
|
+
|
|
1699
|
+
Options
|
|
1700
|
+
|
|
1701
|
+
- serviceId (String, required): service ID
|
|
1702
|
+
|
|
1703
|
+
Usage
|
|
1704
|
+
|
|
1705
|
+
```javascript
|
|
1706
|
+
await resolver.incrementConnections("service-id-123");
|
|
1707
|
+
```
|
|
1708
|
+
|
|
1709
|
+
<a id="resolver-decrement"></a>
|
|
1710
|
+
|
|
1711
|
+
### resolver.decrementConnections(serviceId)
|
|
1712
|
+
|
|
1713
|
+
Decrement the active connection count for a service.
|
|
1714
|
+
|
|
1715
|
+
Options
|
|
1716
|
+
|
|
1717
|
+
- serviceId (String, required): service ID
|
|
1718
|
+
|
|
1719
|
+
Usage
|
|
1720
|
+
|
|
1721
|
+
```javascript
|
|
1722
|
+
await resolver.decrementConnections("service-id-123");
|
|
1723
|
+
```
|
|
1724
|
+
|
|
1725
|
+
<a id="resolver-metrics"></a>
|
|
1726
|
+
|
|
1727
|
+
### resolver.getSelectionMetrics(serviceId)
|
|
1728
|
+
|
|
1729
|
+
Get current metrics for a specific service.
|
|
1730
|
+
|
|
1731
|
+
Options
|
|
1732
|
+
|
|
1733
|
+
- serviceId (String, required): service ID
|
|
1734
|
+
|
|
1735
|
+
Usage
|
|
1736
|
+
|
|
1737
|
+
```javascript
|
|
1738
|
+
const metrics = await resolver.getSelectionMetrics("service-id-123");
|
|
1739
|
+
```
|
|
1740
|
+
|
|
1741
|
+
Result
|
|
1742
|
+
|
|
1743
|
+
```json
|
|
1744
|
+
{
|
|
1745
|
+
"responseTime": 150,
|
|
1746
|
+
"errorRate": 0.5,
|
|
1747
|
+
"cpuUsage": 45,
|
|
1748
|
+
"memoryUsage": 60,
|
|
1749
|
+
"activeConnections": 10,
|
|
1750
|
+
"lastSelectedTime": 1234567890
|
|
1751
|
+
}
|
|
1752
|
+
```
|
|
1753
|
+
|
|
1754
|
+
<a id="resolver-refresh"></a>
|
|
1755
|
+
|
|
1756
|
+
### resolver.refresh()
|
|
1757
|
+
|
|
1758
|
+
Clear all stored metrics from Redis cache.
|
|
1759
|
+
|
|
1760
|
+
Usage
|
|
1761
|
+
|
|
1762
|
+
```javascript
|
|
1763
|
+
await resolver.refresh();
|
|
1764
|
+
```
|
|
1765
|
+
|
|
1489
1766
|
<a id="kv"></a>
|
|
1490
1767
|
|
|
1491
1768
|
### consul.kv
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { CommonOptions, Consul } from "./consul";
|
|
2
|
+
|
|
3
|
+
interface ConfigEntry {
|
|
4
|
+
Kind: string;
|
|
5
|
+
Name: string;
|
|
6
|
+
Namespace?: string;
|
|
7
|
+
Partition?: string;
|
|
8
|
+
Meta?: Record<string, string>;
|
|
9
|
+
CreateIndex?: number;
|
|
10
|
+
ModifyIndex?: number;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface ListOptions extends CommonOptions {
|
|
15
|
+
kind: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type ListResult = ConfigEntry[];
|
|
19
|
+
|
|
20
|
+
interface GetOptions extends CommonOptions {
|
|
21
|
+
kind: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type GetResult = ConfigEntry;
|
|
26
|
+
|
|
27
|
+
type SetOptions = CommonOptions;
|
|
28
|
+
|
|
29
|
+
type SetResult = boolean;
|
|
30
|
+
|
|
31
|
+
interface DestroyOptions extends CommonOptions {
|
|
32
|
+
kind: string;
|
|
33
|
+
name: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type DestroyResult = boolean;
|
|
37
|
+
|
|
38
|
+
declare class Config {
|
|
39
|
+
constructor(consul: Consul);
|
|
40
|
+
|
|
41
|
+
consul: Consul;
|
|
42
|
+
|
|
43
|
+
list(options: ListOptions): Promise<ListResult>;
|
|
44
|
+
list(kind: string): Promise<ListResult>;
|
|
45
|
+
|
|
46
|
+
get(options: GetOptions): Promise<GetResult>;
|
|
47
|
+
|
|
48
|
+
set(entry: ConfigEntry, options?: SetOptions): Promise<SetResult>;
|
|
49
|
+
|
|
50
|
+
destroy(options: DestroyOptions): Promise<DestroyResult>;
|
|
51
|
+
|
|
52
|
+
delete(options: DestroyOptions): Promise<DestroyResult>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { Config, ConfigEntry };
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const errors = require("./errors");
|
|
2
|
+
const utils = require("./utils");
|
|
3
|
+
|
|
4
|
+
class Config {
|
|
5
|
+
constructor(consul) {
|
|
6
|
+
this.consul = consul;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Lists config entries for a kind
|
|
11
|
+
*/
|
|
12
|
+
async list(opts) {
|
|
13
|
+
if (typeof opts === "string") {
|
|
14
|
+
opts = { kind: opts };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
opts = utils.normalizeKeys(opts);
|
|
18
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
19
|
+
|
|
20
|
+
const req = {
|
|
21
|
+
name: "config.list",
|
|
22
|
+
path: "/config/{kind}",
|
|
23
|
+
params: { kind: opts.kind },
|
|
24
|
+
query: {},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (!opts.kind) {
|
|
28
|
+
throw this.consul._err(errors.Validation("kind required"), req);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
utils.options(req, opts);
|
|
32
|
+
|
|
33
|
+
return await this.consul._get(req, utils.body);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Gets a config entry
|
|
38
|
+
*/
|
|
39
|
+
async get(opts) {
|
|
40
|
+
opts = utils.normalizeKeys(opts);
|
|
41
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
42
|
+
|
|
43
|
+
const req = {
|
|
44
|
+
name: "config.get",
|
|
45
|
+
path: "/config/{kind}/{name}",
|
|
46
|
+
params: { kind: opts.kind, name: opts.name },
|
|
47
|
+
query: {},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (!opts.kind) {
|
|
51
|
+
throw this.consul._err(errors.Validation("kind required"), req);
|
|
52
|
+
}
|
|
53
|
+
if (!opts.name) {
|
|
54
|
+
throw this.consul._err(errors.Validation("name required"), req);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
utils.options(req, opts);
|
|
58
|
+
|
|
59
|
+
return await this.consul._get(req, utils.body);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates or updates a config entry
|
|
64
|
+
*/
|
|
65
|
+
async set(entry, opts) {
|
|
66
|
+
opts = utils.normalizeKeys(opts);
|
|
67
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
68
|
+
|
|
69
|
+
const req = {
|
|
70
|
+
name: "config.set",
|
|
71
|
+
path: "/config",
|
|
72
|
+
query: {},
|
|
73
|
+
type: "json",
|
|
74
|
+
body: entry,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (!entry) {
|
|
78
|
+
throw this.consul._err(errors.Validation("entry required"), req);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
utils.options(req, opts);
|
|
82
|
+
|
|
83
|
+
return await this.consul._put(req, utils.body);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Deletes a config entry
|
|
88
|
+
*/
|
|
89
|
+
async destroy(opts) {
|
|
90
|
+
opts = utils.normalizeKeys(opts);
|
|
91
|
+
opts = utils.defaults(opts, this.consul._defaults);
|
|
92
|
+
|
|
93
|
+
const req = {
|
|
94
|
+
name: "config.destroy",
|
|
95
|
+
path: "/config/{kind}/{name}",
|
|
96
|
+
params: { kind: opts.kind, name: opts.name },
|
|
97
|
+
query: {},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
if (!opts.kind) {
|
|
101
|
+
throw this.consul._err(errors.Validation("kind required"), req);
|
|
102
|
+
}
|
|
103
|
+
if (!opts.name) {
|
|
104
|
+
throw this.consul._err(errors.Validation("name required"), req);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
utils.options(req, opts);
|
|
108
|
+
|
|
109
|
+
return await this.consul._delete(req, utils.body);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
delete() {
|
|
113
|
+
return this.destroy.apply(this, arguments);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
exports.Config = Config;
|
package/lib/consul.d.ts
CHANGED
|
@@ -3,11 +3,20 @@ import { Agent as httpsAgent } from "https";
|
|
|
3
3
|
import { Acl } from "./acl";
|
|
4
4
|
import { Agent } from "./agent";
|
|
5
5
|
import { Catalog } from "./catalog";
|
|
6
|
+
import { Config } from "./config";
|
|
6
7
|
import { Event } from "./event";
|
|
7
8
|
import { Health } from "./health";
|
|
8
9
|
import { Intention } from "./intention";
|
|
9
10
|
import { Kv } from "./kv";
|
|
10
11
|
import { Query } from "./query";
|
|
12
|
+
import {
|
|
13
|
+
Resolver,
|
|
14
|
+
ConsulResolverConfig,
|
|
15
|
+
SelectionAlgorithm,
|
|
16
|
+
ServiceInfo,
|
|
17
|
+
OptimalServiceResult,
|
|
18
|
+
ServiceMetrics,
|
|
19
|
+
} from "./resolver";
|
|
11
20
|
import { Session } from "./session";
|
|
12
21
|
import { Status } from "./status";
|
|
13
22
|
import { Transaction } from "./transaction";
|
|
@@ -43,6 +52,7 @@ declare class Consul {
|
|
|
43
52
|
acl: Acl;
|
|
44
53
|
agent: Agent;
|
|
45
54
|
catalog: Catalog;
|
|
55
|
+
config: Config;
|
|
46
56
|
event: Event;
|
|
47
57
|
health: Health;
|
|
48
58
|
intention: Intention;
|
|
@@ -55,11 +65,13 @@ declare class Consul {
|
|
|
55
65
|
static Acl: typeof Acl;
|
|
56
66
|
static Agent: typeof Agent;
|
|
57
67
|
static Catalog: typeof Catalog;
|
|
68
|
+
static Config: typeof Config;
|
|
58
69
|
static Event: typeof Event;
|
|
59
70
|
static Health: typeof Health;
|
|
60
71
|
static Intention: typeof Intention;
|
|
61
72
|
static Kv: typeof Kv;
|
|
62
73
|
static Query: typeof Query;
|
|
74
|
+
static Resolver: typeof Resolver;
|
|
63
75
|
static Session: typeof Session;
|
|
64
76
|
static Status: typeof Status;
|
|
65
77
|
static Transaction: typeof Transaction;
|
|
@@ -68,6 +80,17 @@ declare class Consul {
|
|
|
68
80
|
destroy(): void;
|
|
69
81
|
|
|
70
82
|
watch(options: WatchOptions): Watch;
|
|
83
|
+
|
|
84
|
+
resolver(config: ConsulResolverConfig): Resolver;
|
|
71
85
|
}
|
|
72
86
|
|
|
87
|
+
export {
|
|
88
|
+
SelectionAlgorithm,
|
|
89
|
+
ConsulResolverConfig,
|
|
90
|
+
ServiceInfo,
|
|
91
|
+
OptimalServiceResult,
|
|
92
|
+
ServiceMetrics,
|
|
93
|
+
Resolver,
|
|
94
|
+
};
|
|
95
|
+
|
|
73
96
|
export { Consul };
|
package/lib/consul.js
CHANGED
|
@@ -3,11 +3,13 @@ const papi = require("papi");
|
|
|
3
3
|
const Acl = require("./acl").Acl;
|
|
4
4
|
const Agent = require("./agent").Agent;
|
|
5
5
|
const Catalog = require("./catalog").Catalog;
|
|
6
|
+
const Config = require("./config").Config;
|
|
6
7
|
const Event = require("./event").Event;
|
|
7
8
|
const Health = require("./health").Health;
|
|
8
9
|
const Intention = require("./intention").Intention;
|
|
9
10
|
const Kv = require("./kv").Kv;
|
|
10
11
|
const Query = require("./query").Query;
|
|
12
|
+
const Resolver = require("./resolver").Resolver;
|
|
11
13
|
const Session = require("./session").Session;
|
|
12
14
|
const Status = require("./status").Status;
|
|
13
15
|
const Watch = require("./watch").Watch;
|
|
@@ -51,6 +53,7 @@ class Consul extends papi.Client {
|
|
|
51
53
|
this.acl = new Consul.Acl(this);
|
|
52
54
|
this.agent = new Consul.Agent(this);
|
|
53
55
|
this.catalog = new Consul.Catalog(this);
|
|
56
|
+
this.config = new Consul.Config(this);
|
|
54
57
|
this.event = new Consul.Event(this);
|
|
55
58
|
this.health = new Consul.Health(this);
|
|
56
59
|
this.intention = new Consul.Intention(this);
|
|
@@ -71,6 +74,10 @@ class Consul extends papi.Client {
|
|
|
71
74
|
return new Consul.Watch(this, opts);
|
|
72
75
|
}
|
|
73
76
|
|
|
77
|
+
resolver(config) {
|
|
78
|
+
return new Consul.Resolver(this, config);
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
static parseQueryMeta(res) {
|
|
75
82
|
return utils.parseQueryMeta(res);
|
|
76
83
|
}
|
|
@@ -79,11 +86,13 @@ class Consul extends papi.Client {
|
|
|
79
86
|
Consul.Acl = Acl;
|
|
80
87
|
Consul.Agent = Agent;
|
|
81
88
|
Consul.Catalog = Catalog;
|
|
89
|
+
Consul.Config = Config;
|
|
82
90
|
Consul.Event = Event;
|
|
83
91
|
Consul.Health = Health;
|
|
84
92
|
Consul.Intention = Intention;
|
|
85
93
|
Consul.Kv = Kv;
|
|
86
94
|
Consul.Query = Query;
|
|
95
|
+
Consul.Resolver = Resolver;
|
|
87
96
|
Consul.Session = Session;
|
|
88
97
|
Consul.Status = Status;
|
|
89
98
|
Consul.Transaction = Transaction;
|
|
@@ -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;
|