@interopio/bridge 0.0.1-alpha

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.
Files changed (84) hide show
  1. package/bin/bridge.js +9 -0
  2. package/gen/instance/GeneratedBuildInfo.ts +4 -0
  3. package/license.md +5 -0
  4. package/package.json +40 -0
  5. package/readme.md +10 -0
  6. package/src/cluster/Address.ts +57 -0
  7. package/src/cluster/Cluster.ts +13 -0
  8. package/src/cluster/Endpoint.ts +5 -0
  9. package/src/cluster/Member.ts +9 -0
  10. package/src/cluster/MembershipListener.ts +6 -0
  11. package/src/config/Config.ts +100 -0
  12. package/src/config/DiscoveryConfig.ts +21 -0
  13. package/src/config/Duration.ts +168 -0
  14. package/src/config/KubernetesConfig.ts +7 -0
  15. package/src/config/NamedDiscoveryConfig.ts +17 -0
  16. package/src/config/Properties.ts +49 -0
  17. package/src/config/index.ts +1 -0
  18. package/src/discovery/SimpleDiscoveryNode.ts +14 -0
  19. package/src/discovery/index.ts +207 -0
  20. package/src/discovery/multicast/MulticastDiscoveryStrategy.ts +141 -0
  21. package/src/discovery/multicast/MulticastDiscoveryStrategyFactory.ts +30 -0
  22. package/src/discovery/multicast/MulticastProperties.ts +4 -0
  23. package/src/discovery/settings.ts +37 -0
  24. package/src/error/RequestFailure.ts +48 -0
  25. package/src/gossip/ApplicationState.ts +48 -0
  26. package/src/gossip/EndpointState.ts +141 -0
  27. package/src/gossip/FailureDetector.ts +235 -0
  28. package/src/gossip/Gossiper.ts +1133 -0
  29. package/src/gossip/HeartbeatState.ts +66 -0
  30. package/src/gossip/Messenger.ts +130 -0
  31. package/src/gossip/VersionedValue.ts +59 -0
  32. package/src/index.ts +3 -0
  33. package/src/instance/AddressPicker.ts +245 -0
  34. package/src/instance/BridgeNode.ts +141 -0
  35. package/src/instance/ClusterTopologyIntentTracker.ts +4 -0
  36. package/src/io/VersionedSerializer.ts +230 -0
  37. package/src/io/util.ts +117 -0
  38. package/src/kubernetes/DnsEndpointResolver.ts +70 -0
  39. package/src/kubernetes/KubernetesApiEndpointResolver.ts +111 -0
  40. package/src/kubernetes/KubernetesApiProvider.ts +75 -0
  41. package/src/kubernetes/KubernetesClient.ts +264 -0
  42. package/src/kubernetes/KubernetesConfig.ts +130 -0
  43. package/src/kubernetes/KubernetesDiscoveryStrategy.ts +30 -0
  44. package/src/kubernetes/KubernetesDiscoveryStrategyFactory.ts +71 -0
  45. package/src/kubernetes/KubernetesEndpointResolver.ts +43 -0
  46. package/src/kubernetes/KubernetesProperties.ts +22 -0
  47. package/src/license/BridgeLicenseValidator.ts +19 -0
  48. package/src/license/LicenseValidator.ts +114 -0
  49. package/src/license/types.ts +40 -0
  50. package/src/logging.ts +22 -0
  51. package/src/main.mts +53 -0
  52. package/src/net/Action.ts +143 -0
  53. package/src/net/AddressSerializer.ts +44 -0
  54. package/src/net/ByteBufferAllocator.ts +27 -0
  55. package/src/net/FrameDecoder.ts +314 -0
  56. package/src/net/FrameEncoder.ts +138 -0
  57. package/src/net/HandshakeProtocol.ts +143 -0
  58. package/src/net/InboundConnection.ts +108 -0
  59. package/src/net/InboundConnectionInitiator.ts +150 -0
  60. package/src/net/InboundMessageHandler.ts +377 -0
  61. package/src/net/InboundSink.ts +38 -0
  62. package/src/net/Message.ts +428 -0
  63. package/src/net/OutboundConnection.ts +1141 -0
  64. package/src/net/OutboundConnectionInitiator.ts +76 -0
  65. package/src/net/RequestCallbacks.ts +148 -0
  66. package/src/net/ResponseHandler.ts +30 -0
  67. package/src/net/ShareableBytes.ts +125 -0
  68. package/src/net/internal/AsyncResourceExecutor.ts +464 -0
  69. package/src/net/internal/AsyncSocketPromise.ts +37 -0
  70. package/src/net/internal/channel/ChannelHandlerAdapter.ts +99 -0
  71. package/src/net/internal/channel/types.ts +188 -0
  72. package/src/utils/bigint.ts +23 -0
  73. package/src/utils/buffer.ts +434 -0
  74. package/src/utils/clock.ts +148 -0
  75. package/src/utils/collections.ts +283 -0
  76. package/src/utils/crc.ts +39 -0
  77. package/src/utils/internal/IpAddressUtil.ts +161 -0
  78. package/src/utils/memory/BufferPools.ts +40 -0
  79. package/src/utils/network.ts +130 -0
  80. package/src/utils/promise.ts +38 -0
  81. package/src/utils/uuid.ts +5 -0
  82. package/src/utils/vint.ts +238 -0
  83. package/src/version/MemberVersion.ts +42 -0
  84. package/src/version/Version.ts +12 -0
@@ -0,0 +1,207 @@
1
+ import {type Address} from '../cluster/Address.ts';
2
+ import {type Properties, type PropertyDefinition} from '../config/Properties.ts';
3
+ import {type DiscoveryConfig, type DiscoveryStrategyConfig} from '../config/DiscoveryConfig.ts';
4
+ import {type Logger} from '../logging.ts';
5
+ import {KubernetesDiscoveryStrategyFactory} from '../kubernetes/KubernetesDiscoveryStrategyFactory.ts';
6
+ import {MulticastDiscoveryStrategyFactory} from './multicast/MulticastDiscoveryStrategyFactory.ts';
7
+
8
+ export interface DiscoveryServiceFactory {
9
+ newDiscoveryService(settings: DiscoveryServiceSettings): DiscoveryService
10
+ }
11
+
12
+ export interface DiscoveryService {
13
+ start(): Promise<void>
14
+ discover(): Promise<Iterable<DiscoveryNode>>
15
+ close(): Promise<void>
16
+ }
17
+
18
+
19
+ export type DiscoveryServiceSettings = {
20
+ readonly node: Promise<DiscoveryNode>
21
+ readonly mode: DiscoveryMode
22
+ readonly conf: DiscoveryConfig
23
+ readonly auto: boolean
24
+ readonly logger: Logger
25
+ }
26
+
27
+ export type DiscoveryMode = 'member' | 'client';
28
+
29
+ export class DefaultDiscoveryServiceFactory implements DiscoveryServiceFactory {
30
+ newDiscoveryService(settings: DiscoveryServiceSettings): DiscoveryService {
31
+ return new DefaultDiscoveryService(settings);
32
+ }
33
+ }
34
+
35
+ function verifyNoUnknownProperties(mappedProperties: Properties, allProperties: Properties) {
36
+ const unknownProperties = Object.keys(allProperties).filter(key => !mappedProperties.hasOwnProperty(key));
37
+ if (unknownProperties.length > 0) {
38
+ throw new Error(`Unknown properties: ${unknownProperties.join(', ')} on discovery strategy`);
39
+ }
40
+ }
41
+
42
+ function prepareProperties(properties: Properties,
43
+ propertyDefinitions: readonly PropertyDefinition[]) {
44
+ const mappedProperties: Properties = {};
45
+ for (const propertyDefinition of propertyDefinitions) {
46
+ const propertyKey = propertyDefinition.key;
47
+ if (properties[propertyKey] == undefined) {
48
+ if (!propertyDefinition.optional) {
49
+ throw new Error(`Missing required property: ${propertyKey} on discovery strategy`);
50
+ }
51
+ continue;
52
+ }
53
+ const value = properties[propertyKey];
54
+ const converter = propertyDefinition.converter;
55
+ const mappedValue = converter.convert(value);
56
+
57
+ mappedProperties[propertyKey] = mappedValue;
58
+ }
59
+
60
+ verifyNoUnknownProperties(mappedProperties, properties);
61
+ return mappedProperties;
62
+
63
+ }
64
+
65
+ export class DefaultDiscoveryService implements DiscoveryService {
66
+ private readonly settings: DiscoveryServiceSettings;
67
+ private readonly strategies: Array<DiscoveryStrategy> = [];
68
+ constructor(settings: DiscoveryServiceSettings) {
69
+ this.settings = settings;
70
+ }
71
+
72
+ async start(): Promise<void> {
73
+ this.strategies.push(...await this.loadDiscoveryStrategies());
74
+ for (const strategy of this.strategies) {
75
+ await strategy.start();
76
+ }
77
+ }
78
+
79
+ async discover(): Promise<Iterable<DiscoveryNode>> {
80
+ const discoveryNodes = new Set<DiscoveryNode>();
81
+ for (const strategy of this.strategies) {
82
+ const candidates = await strategy.discover();
83
+ if (candidates) {
84
+ for (const candidate of candidates) {
85
+ if (this.validateCandidate(candidate)) {
86
+ discoveryNodes.add(candidate);
87
+ }
88
+ }
89
+ }
90
+
91
+ }
92
+
93
+ return discoveryNodes;
94
+ }
95
+
96
+ async close(): Promise<void> {
97
+ for (const strategy of this.strategies) {
98
+ await strategy.close();
99
+ }
100
+ }
101
+
102
+ private validateCandidate(candidate: DiscoveryNode) : boolean {
103
+ // todo
104
+ return true;
105
+ }
106
+
107
+ private async loadDiscoveryStrategies(): Promise<Iterable<DiscoveryStrategy>> {
108
+
109
+ const discoveryStrategyConfigs = this.settings.conf.discoveryStrategyConfigs;
110
+ const factories = this.collectFactories(discoveryStrategyConfigs);
111
+
112
+ const discoveryStrategies = new Array<DiscoveryStrategy>();
113
+ for (const config of discoveryStrategyConfigs) {
114
+ const discoveryStrategy = await this.buildDiscoveryStrategy(config, factories);
115
+ discoveryStrategies.push(discoveryStrategy);
116
+ }
117
+ const logger = this.settings.logger;
118
+ if (discoveryStrategies.length == 0 && this.settings.auto) {
119
+ logger.debug(`Discovery auto-detection enabled, trying to detect discovery strategy`);
120
+ const autoDetectedFactory = await this.detectDiscoveryStrategyFactory(factories);
121
+ if (autoDetectedFactory) {
122
+ logger.debug(`Auto-detected discovery strategy: ${autoDetectedFactory.constructor.name}`);
123
+ discoveryStrategies.push(autoDetectedFactory.newDiscoveryStrategy(await this.settings.node, logger, {}));
124
+ }
125
+ else {
126
+ logger.debug(`No discovery strategy auto-detected`);
127
+ }
128
+ }
129
+ return discoveryStrategies;
130
+ }
131
+
132
+ private collectFactories(strategyConfigs: ReadonlyArray<DiscoveryStrategyConfig>): DiscoveryStrategyFactory[] {
133
+
134
+ const knownFactories = [new MulticastDiscoveryStrategyFactory(), new KubernetesDiscoveryStrategyFactory()]
135
+
136
+ const factories = new Array<DiscoveryStrategyFactory>();
137
+
138
+ for (const factory of knownFactories) {
139
+ factories.push(factory);
140
+ }
141
+
142
+ for (const config of strategyConfigs) {
143
+ const factory = config.discoveryStrategyFactory;
144
+ if (factory) {
145
+ factories.push(factory);
146
+ }
147
+ }
148
+
149
+ return factories;
150
+ }
151
+
152
+ private async buildDiscoveryStrategy(config: DiscoveryStrategyConfig,
153
+ factories: DiscoveryStrategyFactory[]): Promise<DiscoveryStrategy> {
154
+ const discoveryNode = await this.settings.node;
155
+ const logger = this.settings.logger;
156
+
157
+ for (const factory of factories) {
158
+ if (factory.constructor.name == config.discoveryStrategyFactory.constructor.name) {
159
+ const properties = prepareProperties(config.properties, factory.getConfigurationProperties());
160
+ return factory.newDiscoveryStrategy(discoveryNode, logger, properties);
161
+ }
162
+ }
163
+ throw new Error('DiscoveryStrategyFactory not found');
164
+ }
165
+
166
+ private async detectDiscoveryStrategyFactory(factories: DiscoveryStrategyFactory[]) {
167
+ let highestPriorityFactory: DiscoveryStrategyFactory;
168
+ for (const factory of factories) {
169
+ try {
170
+ if (await factory.isAutoDetectionApplicable()) {
171
+ if (highestPriorityFactory === undefined || factory.priority > highestPriorityFactory.priority) {
172
+ highestPriorityFactory = factory;
173
+ }
174
+ }
175
+ } catch (error) {
176
+ // todo log error
177
+ }
178
+ }
179
+ return highestPriorityFactory;
180
+ }
181
+
182
+
183
+ }
184
+
185
+ export interface DiscoveryStrategy {
186
+ start(): Promise<void>
187
+ discover(): Promise<Iterable<DiscoveryNode> | undefined>
188
+ close(): Promise<void>
189
+ }
190
+
191
+ export interface DiscoveryStrategyFactory {
192
+ readonly priority: number
193
+ isAutoDetectionApplicable(): Promise<boolean>
194
+ newDiscoveryStrategy(node: DiscoveryNode, logger: Logger, settings: Properties): DiscoveryStrategy
195
+ getConfigurationProperties(): ReadonlyArray<PropertyDefinition>
196
+ }
197
+ export const DiscoveryStrategyPriorities = {
198
+ UNKNOWN: 0,
199
+ PLATFORM: 20,
200
+ CUSTOM: 50,
201
+ }
202
+
203
+ export interface DiscoveryNode {
204
+ readonly publicAddress: Address;
205
+ readonly privateAddress: Address;
206
+ readonly properties: ReadonlyMap<string, unknown>;
207
+ }
@@ -0,0 +1,141 @@
1
+ import {createSocket, type RemoteInfo, Socket} from 'node:dgram';
2
+ import {type DiscoveryNode, type DiscoveryStrategy} from '../index.ts';
3
+ import {SimpleDiscoveryNode} from '../SimpleDiscoveryNode.ts';
4
+ import {type Properties} from '../../config/Properties.ts';
5
+ import {GROUP, PORT} from './MulticastProperties.ts';
6
+ import {getOrDefault} from '../settings.ts';
7
+ import {fromHostname, toIpAddress} from '../../cluster/Address.ts';
8
+ import {isLoopback} from '../../utils/network.ts';
9
+ import {type Logger} from '../../logging.ts';
10
+
11
+ const DATA_OUTPUT_BUFFER_SIZE = 64 * 1024;
12
+ const DEFAULT_MULTICAST_PORT = 34567;
13
+ const DEFAULT_MULTICAST_GROUP = '239.1.2.3';
14
+ const SOCKET_TIME_TO_LIVE = 255; // unrestricted (other possible values would be 1 (local-network), 15 (site-local), 32(local-area), 64(regional), 128(global))
15
+
16
+ interface MulticastMemberInfo {
17
+ readonly host: string;
18
+ readonly port: number;
19
+ }
20
+
21
+ function multicastDiscoverySender(discoveryNode: DiscoveryNode | undefined, socket: Socket, group: string, port: number): () => void {
22
+ let multicastMemberInfo: MulticastMemberInfo;
23
+ if (discoveryNode) {
24
+ const address = discoveryNode.publicAddress;
25
+ multicastMemberInfo = {host: address.host, port: address.port};
26
+ }
27
+ const msg: Buffer = Buffer.from(JSON.stringify(multicastMemberInfo));
28
+
29
+ return () => {
30
+ socket.send(msg, port, group, (error) => {
31
+ console.error(error);
32
+ });
33
+ }
34
+ }
35
+
36
+ function multicastDiscoveryReceiver(socket: Socket): () => Promise<MulticastMemberInfo> {
37
+ return () => {
38
+ return new Promise<MulticastMemberInfo>((resolve, reject) => {
39
+ socket.on('message', (message: Buffer, remote: RemoteInfo) => {
40
+ resolve(JSON.parse(message as unknown as string));
41
+ });
42
+ socket.on('error', (err: Error) => {
43
+ reject(err);
44
+ });
45
+ });
46
+ }
47
+ }
48
+
49
+ export class MulticastDiscoveryStrategy implements DiscoveryStrategy {
50
+ private readonly logger: Logger;
51
+ private readonly properties: Properties;
52
+ private readonly discoveryNode?: DiscoveryNode;
53
+ private isClient?: boolean;
54
+ private discoverySender?: () => void;
55
+ private discoveryReceiver?: () => Promise<MulticastMemberInfo>;
56
+ private sendIntervalId?: ReturnType<typeof setInterval>;
57
+ private socket?: Socket;
58
+ constructor(discoveryNode: DiscoveryNode | undefined,
59
+ logger: Logger,
60
+ properties: Properties) {
61
+ this.logger = logger;
62
+ this.properties = properties;
63
+ this.discoveryNode = discoveryNode;
64
+ }
65
+
66
+ private async initializeMulticastSocket() {
67
+ const port = getOrDefault(this.properties, '', PORT, DEFAULT_MULTICAST_PORT);
68
+ if (port < 0 || port > 65535) {
69
+ throw new Error('port number must be between 0 and 65535');
70
+ }
71
+ const group = getOrDefault(this.properties, '', GROUP, DEFAULT_MULTICAST_GROUP);
72
+ this.socket = createSocket({
73
+ type: 'udp4',
74
+ reuseAddr: true,
75
+ recvBufferSize: DATA_OUTPUT_BUFFER_SIZE,
76
+ sendBufferSize: DATA_OUTPUT_BUFFER_SIZE});
77
+
78
+ if (this.discoveryNode) {
79
+ const address = await toIpAddress(this.discoveryNode.privateAddress);
80
+ if (!isLoopback(address)) {
81
+ this.socket.setMulticastInterface(address.address);
82
+ }
83
+ }
84
+
85
+ this.socket.bind(port, () => {
86
+ this.socket.setMulticastTTL(SOCKET_TIME_TO_LIVE);
87
+ this.socket.addMembership(group);
88
+ });
89
+
90
+ this.discoverySender = multicastDiscoverySender(this.discoveryNode, this.socket, group, port);
91
+ this.discoveryReceiver = multicastDiscoveryReceiver(this.socket);
92
+ if (this.discoveryNode == null) {
93
+ this.isClient = true;
94
+ }
95
+ }
96
+
97
+ async discover(): Promise<Iterable<DiscoveryNode> | undefined> {
98
+ const multicastMemberInfo = await this.discoveryReceiver();
99
+ if (multicastMemberInfo) {
100
+ const list = [];
101
+ try {
102
+ const discoveryNode = new SimpleDiscoveryNode(await fromHostname(multicastMemberInfo.host, multicastMemberInfo.port));
103
+ list.push(discoveryNode);
104
+ }
105
+ catch (e) {
106
+ this.logger.trace(e.message);
107
+ }
108
+ return list;
109
+ }
110
+ }
111
+
112
+ async start() {
113
+ await this.initializeMulticastSocket();
114
+ if (!this.isClient) {
115
+ this.sendIntervalId = setInterval(() => {
116
+ this.discoverySender();
117
+ }, 2000);
118
+ }
119
+ }
120
+
121
+ async close() {
122
+ if (this.sendIntervalId) {
123
+ clearInterval(this.sendIntervalId);
124
+ delete this.sendIntervalId;
125
+ }
126
+ return new Promise<void>((resolve, reject) => {
127
+ if (this.socket)
128
+ {
129
+ this.socket.close(() => {
130
+ resolve()
131
+ });
132
+ }
133
+ else
134
+ {
135
+ resolve();
136
+ }
137
+ });
138
+
139
+ }
140
+
141
+ }
@@ -0,0 +1,30 @@
1
+ import {
2
+ type DiscoveryNode,
3
+ type DiscoveryStrategy,
4
+ type DiscoveryStrategyFactory,
5
+ DiscoveryStrategyPriorities
6
+ } from '../index.ts';
7
+ import {MulticastDiscoveryStrategy} from './MulticastDiscoveryStrategy.ts';
8
+ import {GROUP, PORT} from './MulticastProperties.ts';
9
+ import {type Properties, type PropertyDefinition} from '../../config/Properties.ts';
10
+ import {type Logger} from '../../logging.ts';
11
+
12
+ const PROPERTY_DEFINITIONS: ReadonlyArray<PropertyDefinition> = [
13
+ GROUP,
14
+ PORT
15
+ ];
16
+
17
+ export class MulticastDiscoveryStrategyFactory implements DiscoveryStrategyFactory {
18
+ readonly priority = DiscoveryStrategyPriorities.UNKNOWN;
19
+ newDiscoveryStrategy(discoveryNode: DiscoveryNode, logger: Logger, prop: Properties): DiscoveryStrategy {
20
+ return new MulticastDiscoveryStrategy(discoveryNode, logger, prop);
21
+ }
22
+
23
+ async isAutoDetectionApplicable(): Promise<boolean> {
24
+ return false;
25
+ }
26
+
27
+ getConfigurationProperties(): ReadonlyArray<PropertyDefinition> {
28
+ return PROPERTY_DEFINITIONS;
29
+ }
30
+ }
@@ -0,0 +1,4 @@
1
+ import {NUMBER, property, STRING} from '../../config/Properties.ts';
2
+
3
+ export const PORT = property<number>('port', NUMBER);
4
+ export const GROUP = property<string>('group', STRING);
@@ -0,0 +1,37 @@
1
+ import {type Properties, type PropertyDefinition, type PropertyType} from '../config/Properties.ts';
2
+
3
+ function getProperty(prefix: string, property: PropertyDefinition) {
4
+ let s = prefix;
5
+ if (!s.endsWith('.')) {
6
+ s += '.';
7
+ }
8
+ return s + property.key;
9
+ }
10
+
11
+ function readProperty(prefix: string | undefined, property: PropertyDefinition): PropertyType | undefined {
12
+ if (prefix) {
13
+ const p = getProperty(prefix, property);
14
+ const v = process.env[p];
15
+ // todo convert
16
+ return v;
17
+ }
18
+ }
19
+
20
+ export function getOrUndefined<T extends PropertyType>(properties: Properties, prefix: string, property?: PropertyDefinition): T | undefined {
21
+ return getOrDefault(properties, prefix, property);
22
+ }
23
+
24
+ export function getOrDefault<T extends PropertyType>(properties: Properties, prefix: string, property?: PropertyDefinition, defaultValue?: T) {
25
+ if (property === undefined) {
26
+ return defaultValue;
27
+ }
28
+
29
+ let value = readProperty(prefix, property);
30
+ if (value === undefined) {
31
+ value = properties[property.key];
32
+ }
33
+ if (value === undefined) {
34
+ return defaultValue;
35
+ }
36
+ return value as T;
37
+ }
@@ -0,0 +1,48 @@
1
+ import type {DataInput, DataOutput, VersionedSerializer} from '../io/VersionedSerializer.ts';
2
+
3
+ export const RequestFailure = {
4
+ UNKNOWN: 0,
5
+ }
6
+
7
+ export type RequestFailureReason = keyof typeof RequestFailure
8
+
9
+ const codeToReason: RequestFailureReason[] = function initCodeToReason() {
10
+ const result: RequestFailureReason[] = [];
11
+ for (const [reason, code] of Object.entries(RequestFailure)) {
12
+ if (result[code] !== undefined) {
13
+ throw new Error(`Duplicate RequestFailure code: ${code}`);
14
+ }
15
+ result[code] = reason as RequestFailureReason;
16
+ }
17
+ if (result.length >= 1<<8) {
18
+ throw new Error('RequestFailure codes exceed 8 bits');
19
+ }
20
+ return result;
21
+ } ();
22
+
23
+ export function fromCode(code: number): RequestFailureReason {
24
+ if (code < 0) {
25
+ throw new Error(`Invalid RequestFailure code: ${code}`);
26
+ }
27
+ return codeToReason[code] ?? 'UNKNOWN';
28
+ }
29
+
30
+ export function forError(error: Error): RequestFailureReason {
31
+ return 'UNKNOWN';
32
+ }
33
+
34
+ export const RequestFailureReasonSerializer: VersionedSerializer<RequestFailureReason> = new (class implements VersionedSerializer<RequestFailureReason> {
35
+ serializedSize(value: RequestFailureReason, version?: number): number {
36
+ return 1;
37
+ }
38
+
39
+ serialize(value: RequestFailureReason, output: DataOutput, version?: number) {
40
+ const code = RequestFailure[value];
41
+ output.writeUint8(code);
42
+ }
43
+
44
+ deserialize(input: DataInput, version?: number): RequestFailureReason {
45
+ const code = input.readUint8();
46
+ return fromCode(code);
47
+ }
48
+ })
@@ -0,0 +1,48 @@
1
+ const ApplicationStates = {
2
+ _STATUS: 0,
3
+ LOAD: 1,
4
+ _SCHEMA: 2,
5
+ DC: 3,
6
+ RACK: 4,
7
+ RELEASE_VERSION: 5,
8
+ _INTERNAL_IP: 7,
9
+ _RPC_ADDRESS: 8,
10
+ NET_VERSION: 11,
11
+ HOST_ID: 12,
12
+ _TOKENS: 13,
13
+ _RPC_READY: 14,
14
+ INTERNAL_ADDRESS: 15,
15
+ NATIVE_ADDRESS: 16,
16
+ STATUS: 17,
17
+ _SSTABLE_VERSIONS: 18,
18
+ _DISK_USAGE: 19,
19
+ _INDEX_STATUS: 20,
20
+ _X1: 21,
21
+ _X2: 22,
22
+ _X3: 23,
23
+ _X4: 24,
24
+ _X5: 25,
25
+ _X6: 26,
26
+ _X7: 27,
27
+ _X8: 28,
28
+ _X9: 29
29
+ }
30
+
31
+ export function forId(id: number): ApplicationState {
32
+ const entry = Object.entries(ApplicationStates)
33
+ .find(([, code]) => id === code);
34
+ if (!entry) {
35
+ throw new Error(`No ApplicationState found for id ${id}`);
36
+ }
37
+ return entry[0] as ApplicationState;
38
+ }
39
+ export function toId(name: ApplicationState): number {
40
+ const entry = Object.entries(ApplicationStates)
41
+ .find(([state]) => name === state);
42
+ if (!entry) {
43
+ throw new Error(`No ApplicationState found for name ${name}`);
44
+ }
45
+ return entry[1] as number;
46
+ }
47
+
48
+ export type ApplicationState = keyof typeof ApplicationStates;
@@ -0,0 +1,141 @@
1
+ import type {DataInput, DataOutput, VersionedSerializer} from '../io/VersionedSerializer.ts';
2
+ import {HeartbeatState, HeartbeatStateSerializer} from './HeartbeatState.ts';
3
+ import {type ApplicationState, forId, toId} from './ApplicationState.ts';
4
+ import {type VersionedValue, VersionedValueSerializer} from './VersionedValue.ts';
5
+ import {globalClock} from '../utils/clock.ts';
6
+
7
+ export class EndpointState {
8
+ private _heartbeat: HeartbeatState
9
+ private _application: Map<ApplicationState, VersionedValue>
10
+ private _timestamp: bigint
11
+ private _alive: boolean
12
+
13
+ constructor(heartbeat: HeartbeatState, states?: Map<ApplicationState, VersionedValue>) {
14
+ this._heartbeat = heartbeat;
15
+ this._application = states ?? new Map<ApplicationState, VersionedValue>();
16
+ this._timestamp = globalClock.nanoTime();
17
+ this._alive = true;
18
+ }
19
+
20
+ copy(): EndpointState {
21
+ return new EndpointState(this.heartbeat.copy(), new Map(this._application));
22
+ }
23
+
24
+ updateTimestamp(): void {
25
+ this._timestamp = globalClock.nanoTime();
26
+ }
27
+
28
+ get timestamp(): bigint {
29
+ return this._timestamp;
30
+ }
31
+
32
+ /*testing*/ set timestamp(timestamp: bigint) {
33
+ this._timestamp = timestamp
34
+ }
35
+
36
+ public get heartbeat(): HeartbeatState {
37
+ return this._heartbeat;
38
+ }
39
+
40
+ set heartbeat(heartbeat: HeartbeatState) {
41
+ this.updateTimestamp();
42
+ this._heartbeat = heartbeat
43
+ }
44
+
45
+ getApplicationState(key: ApplicationState): VersionedValue {
46
+ return this._application.get(key);
47
+ }
48
+
49
+ hasApplicationState(key: ApplicationState): boolean {
50
+ return this._application.has(key);
51
+ }
52
+
53
+ public states() {
54
+ return this._application.entries();
55
+ }
56
+
57
+ addApplicationState(key: ApplicationState, value: VersionedValue): void {
58
+ this.addApplicationStates([[key, value]]);
59
+ }
60
+
61
+ addApplicationStates(values: [ApplicationState, VersionedValue][]): void {
62
+ while (true) {
63
+ const orig = this._application;
64
+ const copy = new Map(orig);
65
+
66
+ for (const [key, value] of values) {
67
+ copy.set(key, value);
68
+ }
69
+
70
+ if (this._application === orig) {
71
+ this._application = copy;
72
+ return;
73
+ }
74
+ }
75
+ }
76
+
77
+ get isAlive(): boolean {
78
+ return this._alive;
79
+ }
80
+
81
+ markAsDead(): void {
82
+ this._alive = false;
83
+ }
84
+
85
+ markAsAlive(): void {
86
+ this._alive = true;
87
+ }
88
+
89
+ get isStateEmpty(): boolean {
90
+ return this._application.size === 0;
91
+ }
92
+
93
+ get isEmptyWithoutStatus(): boolean {
94
+ const state = this._application;
95
+ const hasStatus = state.has('STATUS') || state.has('_STATUS');
96
+ return this._heartbeat.isEmpty() && !hasStatus;
97
+ }
98
+
99
+ toString() {
100
+ return `EndpointState{heartbeat=${this._heartbeat}, ApplicationState=${this._application}`;
101
+ }
102
+ }
103
+
104
+ export const EndpointStateSerializer = new (class implements VersionedSerializer<EndpointState> {
105
+ serialize(value: EndpointState, output: DataOutput, version: number) {
106
+ const heartbeat = value.heartbeat;
107
+ HeartbeatStateSerializer.serialize(heartbeat, output, version);
108
+
109
+ const states = Array.from(value.states());
110
+ output.writeInt32(states.length);
111
+ for (const [key, state] of states) {
112
+ output.writeInt32(toId(key));
113
+ VersionedValueSerializer.serialize(state, output, version);
114
+ }
115
+ }
116
+
117
+ deserialize(input: DataInput, version: number): EndpointState {
118
+ const heartbeat = HeartbeatStateSerializer.deserialize(input, version);
119
+ const size = input.readInt32();
120
+
121
+ const state = new EndpointState(heartbeat);
122
+ for (let i = 0; i < size; i++) {
123
+ const key = input.readInt32();
124
+ const value = VersionedValueSerializer.deserialize(input, version);
125
+ state.addApplicationState(forId(key), value);
126
+ }
127
+ return state;
128
+ }
129
+
130
+ serializedSize(value: EndpointState, version: number): number {
131
+ let size = HeartbeatStateSerializer.serializedSize(value.heartbeat, version);
132
+ const states = value.states();
133
+ size += 4; // size of states
134
+ for (const [key, state] of states) {
135
+ size += 4; // key
136
+ size += VersionedValueSerializer.serializedSize(state, version);
137
+ }
138
+ return size;
139
+ }
140
+ });
141
+