1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 | Object.defineProperty(exports, "__esModule", { value: true });
|
19 | exports.LoadBalancingCall = void 0;
|
20 | const connectivity_state_1 = require("./connectivity-state");
|
21 | const constants_1 = require("./constants");
|
22 | const deadline_1 = require("./deadline");
|
23 | const metadata_1 = require("./metadata");
|
24 | const picker_1 = require("./picker");
|
25 | const uri_parser_1 = require("./uri-parser");
|
26 | const logging = require("./logging");
|
27 | const control_plane_status_1 = require("./control-plane-status");
|
28 | const http2 = require("http2");
|
29 | const TRACER_NAME = 'load_balancing_call';
|
30 | class LoadBalancingCall {
|
31 | constructor(channel, callConfig, methodName, host, credentials, deadline, callNumber) {
|
32 | var _a, _b;
|
33 | this.channel = channel;
|
34 | this.callConfig = callConfig;
|
35 | this.methodName = methodName;
|
36 | this.host = host;
|
37 | this.credentials = credentials;
|
38 | this.deadline = deadline;
|
39 | this.callNumber = callNumber;
|
40 | this.child = null;
|
41 | this.readPending = false;
|
42 | this.pendingMessage = null;
|
43 | this.pendingHalfClose = false;
|
44 | this.pendingChildStatus = null;
|
45 | this.ended = false;
|
46 | this.metadata = null;
|
47 | this.listener = null;
|
48 | this.onCallEnded = null;
|
49 | const splitPath = this.methodName.split('/');
|
50 | let serviceName = '';
|
51 | |
52 |
|
53 |
|
54 | if (splitPath.length >= 2) {
|
55 | serviceName = splitPath[1];
|
56 | }
|
57 | const hostname = (_b = (_a = (0, uri_parser_1.splitHostPort)(this.host)) === null || _a === void 0 ? void 0 : _a.host) !== null && _b !== void 0 ? _b : 'localhost';
|
58 | |
59 |
|
60 | this.serviceUrl = `https://${hostname}/${serviceName}`;
|
61 | }
|
62 | trace(text) {
|
63 | logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '[' + this.callNumber + '] ' + text);
|
64 | }
|
65 | outputStatus(status, progress) {
|
66 | var _a, _b;
|
67 | if (!this.ended) {
|
68 | this.ended = true;
|
69 | this.trace('ended with status: code=' + status.code + ' details="' + status.details + '"');
|
70 | const finalStatus = Object.assign(Object.assign({}, status), { progress });
|
71 | (_a = this.listener) === null || _a === void 0 ? void 0 : _a.onReceiveStatus(finalStatus);
|
72 | (_b = this.onCallEnded) === null || _b === void 0 ? void 0 : _b.call(this, finalStatus.code);
|
73 | }
|
74 | }
|
75 | doPick() {
|
76 | var _a, _b;
|
77 | if (this.ended) {
|
78 | return;
|
79 | }
|
80 | if (!this.metadata) {
|
81 | throw new Error('doPick called before start');
|
82 | }
|
83 | this.trace('Pick called');
|
84 | const pickResult = this.channel.doPick(this.metadata, this.callConfig.pickInformation);
|
85 | const subchannelString = pickResult.subchannel ?
|
86 | '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() :
|
87 | '' + pickResult.subchannel;
|
88 | this.trace('Pick result: ' +
|
89 | picker_1.PickResultType[pickResult.pickResultType] +
|
90 | ' subchannel: ' +
|
91 | subchannelString +
|
92 | ' status: ' +
|
93 | ((_a = pickResult.status) === null || _a === void 0 ? void 0 : _a.code) +
|
94 | ' ' +
|
95 | ((_b = pickResult.status) === null || _b === void 0 ? void 0 : _b.details));
|
96 | switch (pickResult.pickResultType) {
|
97 | case picker_1.PickResultType.COMPLETE:
|
98 | this.credentials.generateMetadata({ service_url: this.serviceUrl }).then((credsMetadata) => {
|
99 | var _a, _b, _c;
|
100 | const finalMetadata = this.metadata.clone();
|
101 | finalMetadata.merge(credsMetadata);
|
102 | if (finalMetadata.get('authorization').length > 1) {
|
103 | this.outputStatus({
|
104 | code: constants_1.Status.INTERNAL,
|
105 | details: '"authorization" metadata cannot have multiple values',
|
106 | metadata: new metadata_1.Metadata()
|
107 | }, 'PROCESSED');
|
108 | }
|
109 | if (pickResult.subchannel.getConnectivityState() !== connectivity_state_1.ConnectivityState.READY) {
|
110 | this.trace('Picked subchannel ' +
|
111 | subchannelString +
|
112 | ' has state ' +
|
113 | connectivity_state_1.ConnectivityState[pickResult.subchannel.getConnectivityState()] +
|
114 | ' after getting credentials metadata. Retrying pick');
|
115 | this.doPick();
|
116 | return;
|
117 | }
|
118 | if (this.deadline !== Infinity) {
|
119 | finalMetadata.set('grpc-timeout', (0, deadline_1.getDeadlineTimeoutString)(this.deadline));
|
120 | }
|
121 | try {
|
122 | this.child = pickResult.subchannel.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, {
|
123 | onReceiveMetadata: metadata => {
|
124 | this.trace('Received metadata');
|
125 | this.listener.onReceiveMetadata(metadata);
|
126 | },
|
127 | onReceiveMessage: message => {
|
128 | this.trace('Received message');
|
129 | this.listener.onReceiveMessage(message);
|
130 | },
|
131 | onReceiveStatus: status => {
|
132 | this.trace('Received status');
|
133 | if (status.rstCode === http2.constants.NGHTTP2_REFUSED_STREAM) {
|
134 | this.outputStatus(status, 'REFUSED');
|
135 | }
|
136 | else {
|
137 | this.outputStatus(status, 'PROCESSED');
|
138 | }
|
139 | }
|
140 | });
|
141 | }
|
142 | catch (error) {
|
143 | this.trace('Failed to start call on picked subchannel ' +
|
144 | subchannelString +
|
145 | ' with error ' +
|
146 | error.message);
|
147 | this.outputStatus({
|
148 | code: constants_1.Status.INTERNAL,
|
149 | details: 'Failed to start HTTP/2 stream with error ' + error.message,
|
150 | metadata: new metadata_1.Metadata()
|
151 | }, 'NOT_STARTED');
|
152 | return;
|
153 | }
|
154 | (_b = (_a = this.callConfig).onCommitted) === null || _b === void 0 ? void 0 : _b.call(_a);
|
155 | (_c = pickResult.onCallStarted) === null || _c === void 0 ? void 0 : _c.call(pickResult);
|
156 | this.onCallEnded = pickResult.onCallEnded;
|
157 | this.trace('Created child call [' + this.child.getCallNumber() + ']');
|
158 | if (this.readPending) {
|
159 | this.child.startRead();
|
160 | }
|
161 | if (this.pendingMessage) {
|
162 | this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message);
|
163 | }
|
164 | if (this.pendingHalfClose) {
|
165 | this.child.halfClose();
|
166 | }
|
167 | }, (error) => {
|
168 |
|
169 | const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(typeof error.code === 'number' ? error.code : constants_1.Status.UNKNOWN, `Getting metadata from plugin failed with error: ${error.message}`);
|
170 | this.outputStatus({
|
171 | code: code,
|
172 | details: details,
|
173 | metadata: new metadata_1.Metadata()
|
174 | }, 'PROCESSED');
|
175 | });
|
176 | break;
|
177 | case picker_1.PickResultType.DROP:
|
178 | const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(pickResult.status.code, pickResult.status.details);
|
179 | this.outputStatus({ code, details, metadata: pickResult.status.metadata }, 'DROP');
|
180 | break;
|
181 | case picker_1.PickResultType.TRANSIENT_FAILURE:
|
182 | if (this.metadata.getOptions().waitForReady) {
|
183 | this.channel.queueCallForPick(this);
|
184 | }
|
185 | else {
|
186 | const { code, details } = (0, control_plane_status_1.restrictControlPlaneStatusCode)(pickResult.status.code, pickResult.status.details);
|
187 | this.outputStatus({ code, details, metadata: pickResult.status.metadata }, 'PROCESSED');
|
188 | }
|
189 | break;
|
190 | case picker_1.PickResultType.QUEUE:
|
191 | this.channel.queueCallForPick(this);
|
192 | }
|
193 | }
|
194 | cancelWithStatus(status, details) {
|
195 | var _a;
|
196 | this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"');
|
197 | (_a = this.child) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(status, details);
|
198 | this.outputStatus({ code: status, details: details, metadata: new metadata_1.Metadata() }, 'PROCESSED');
|
199 | }
|
200 | getPeer() {
|
201 | var _a, _b;
|
202 | return (_b = (_a = this.child) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : this.channel.getTarget();
|
203 | }
|
204 | start(metadata, listener) {
|
205 | this.trace('start called');
|
206 | this.listener = listener;
|
207 | this.metadata = metadata;
|
208 | this.doPick();
|
209 | }
|
210 | sendMessageWithContext(context, message) {
|
211 | this.trace('write() called with message of length ' + message.length);
|
212 | if (this.child) {
|
213 | this.child.sendMessageWithContext(context, message);
|
214 | }
|
215 | else {
|
216 | this.pendingMessage = { context, message };
|
217 | }
|
218 | }
|
219 | startRead() {
|
220 | this.trace('startRead called');
|
221 | if (this.child) {
|
222 | this.child.startRead();
|
223 | }
|
224 | else {
|
225 | this.readPending = true;
|
226 | }
|
227 | }
|
228 | halfClose() {
|
229 | this.trace('halfClose called');
|
230 | if (this.child) {
|
231 | this.child.halfClose();
|
232 | }
|
233 | else {
|
234 | this.pendingHalfClose = true;
|
235 | }
|
236 | }
|
237 | setCredentials(credentials) {
|
238 | throw new Error("Method not implemented.");
|
239 | }
|
240 | getCallNumber() {
|
241 | return this.callNumber;
|
242 | }
|
243 | }
|
244 | exports.LoadBalancingCall = LoadBalancingCall;
|
245 |
|
\ | No newline at end of file |