load-balancing-call.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. "use strict";
  2. /*
  3. * Copyright 2022 gRPC authors.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  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. /* The standard path format is "/{serviceName}/{methodName}", so if we split
  52. * by '/', the first item should be empty and the second should be the
  53. * service name */
  54. if (splitPath.length >= 2) {
  55. serviceName = splitPath[1];
  56. }
  57. const hostname = (_b = (_a = uri_parser_1.splitHostPort(this.host)) === null || _a === void 0 ? void 0 : _a.host) !== null && _b !== void 0 ? _b : 'localhost';
  58. /* Currently, call credentials are only allowed on HTTPS connections, so we
  59. * can assume that the scheme is "https" */
  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. const pickResult = this.channel.doPick(this.metadata, this.callConfig.pickInformation);
  84. const subchannelString = pickResult.subchannel ?
  85. '(' + pickResult.subchannel.getChannelzRef().id + ') ' + pickResult.subchannel.getAddress() :
  86. '' + pickResult.subchannel;
  87. this.trace('Pick result: ' +
  88. picker_1.PickResultType[pickResult.pickResultType] +
  89. ' subchannel: ' +
  90. subchannelString +
  91. ' status: ' + ((_a = pickResult.status) === null || _a === void 0 ? void 0 : _a.code) +
  92. ' ' + ((_b = pickResult.status) === null || _b === void 0 ? void 0 : _b.details));
  93. switch (pickResult.pickResultType) {
  94. case picker_1.PickResultType.COMPLETE:
  95. this.credentials.generateMetadata({ service_url: this.serviceUrl }).then((credsMetadata) => {
  96. var _a, _b, _c;
  97. const finalMetadata = this.metadata.clone();
  98. finalMetadata.merge(credsMetadata);
  99. if (finalMetadata.get('authorization').length > 1) {
  100. this.outputStatus({
  101. code: constants_1.Status.INTERNAL,
  102. details: '"authorization" metadata cannot have multiple values',
  103. metadata: new metadata_1.Metadata()
  104. }, 'PROCESSED');
  105. }
  106. if (pickResult.subchannel.getConnectivityState() !== connectivity_state_1.ConnectivityState.READY) {
  107. this.trace('Picked subchannel ' +
  108. subchannelString +
  109. ' has state ' +
  110. connectivity_state_1.ConnectivityState[pickResult.subchannel.getConnectivityState()] +
  111. ' after getting credentials metadata. Retrying pick');
  112. this.doPick();
  113. return;
  114. }
  115. if (this.deadline !== Infinity) {
  116. finalMetadata.set('grpc-timeout', deadline_1.getDeadlineTimeoutString(this.deadline));
  117. }
  118. try {
  119. this.child = pickResult.subchannel.getRealSubchannel().createCall(finalMetadata, this.host, this.methodName, {
  120. onReceiveMetadata: metadata => {
  121. this.listener.onReceiveMetadata(metadata);
  122. },
  123. onReceiveMessage: message => {
  124. this.listener.onReceiveMessage(message);
  125. },
  126. onReceiveStatus: status => {
  127. if (status.code === http2.constants.NGHTTP2_REFUSED_STREAM) {
  128. this.outputStatus(status, 'REFUSED');
  129. }
  130. else {
  131. this.outputStatus(status, 'PROCESSED');
  132. }
  133. }
  134. });
  135. }
  136. catch (error) {
  137. this.trace('Failed to start call on picked subchannel ' +
  138. subchannelString +
  139. ' with error ' +
  140. error.message);
  141. this.outputStatus({
  142. code: constants_1.Status.INTERNAL,
  143. details: 'Failed to start HTTP/2 stream with error ' + error.message,
  144. metadata: new metadata_1.Metadata()
  145. }, 'NOT_STARTED');
  146. return;
  147. }
  148. (_b = (_a = this.callConfig).onCommitted) === null || _b === void 0 ? void 0 : _b.call(_a);
  149. (_c = pickResult.onCallStarted) === null || _c === void 0 ? void 0 : _c.call(pickResult);
  150. this.onCallEnded = pickResult.onCallEnded;
  151. this.trace('Created child call [' + this.child.getCallNumber() + ']');
  152. if (this.readPending) {
  153. this.child.startRead();
  154. }
  155. if (this.pendingMessage) {
  156. this.child.sendMessageWithContext(this.pendingMessage.context, this.pendingMessage.message);
  157. }
  158. if (this.pendingHalfClose) {
  159. this.child.halfClose();
  160. }
  161. }, (error) => {
  162. // We assume the error code isn't 0 (Status.OK)
  163. const { code, details } = control_plane_status_1.restrictControlPlaneStatusCode(typeof error.code === 'number' ? error.code : constants_1.Status.UNKNOWN, `Getting metadata from plugin failed with error: ${error.message}`);
  164. this.outputStatus({
  165. code: code,
  166. details: details,
  167. metadata: new metadata_1.Metadata()
  168. }, 'PROCESSED');
  169. });
  170. break;
  171. case picker_1.PickResultType.DROP:
  172. const { code, details } = control_plane_status_1.restrictControlPlaneStatusCode(pickResult.status.code, pickResult.status.details);
  173. this.outputStatus({ code, details, metadata: pickResult.status.metadata }, 'DROP');
  174. break;
  175. case picker_1.PickResultType.TRANSIENT_FAILURE:
  176. if (this.metadata.getOptions().waitForReady) {
  177. this.channel.queueCallForPick(this);
  178. }
  179. else {
  180. const { code, details } = control_plane_status_1.restrictControlPlaneStatusCode(pickResult.status.code, pickResult.status.details);
  181. this.outputStatus({ code, details, metadata: pickResult.status.metadata }, 'PROCESSED');
  182. }
  183. break;
  184. case picker_1.PickResultType.QUEUE:
  185. this.channel.queueCallForPick(this);
  186. }
  187. }
  188. cancelWithStatus(status, details) {
  189. var _a;
  190. this.trace('cancelWithStatus code: ' + status + ' details: "' + details + '"');
  191. (_a = this.child) === null || _a === void 0 ? void 0 : _a.cancelWithStatus(status, details);
  192. this.outputStatus({ code: status, details: details, metadata: new metadata_1.Metadata() }, 'PROCESSED');
  193. }
  194. getPeer() {
  195. var _a, _b;
  196. return (_b = (_a = this.child) === null || _a === void 0 ? void 0 : _a.getPeer()) !== null && _b !== void 0 ? _b : this.channel.getTarget();
  197. }
  198. start(metadata, listener) {
  199. this.trace('start called');
  200. this.listener = listener;
  201. this.metadata = metadata;
  202. this.doPick();
  203. }
  204. sendMessageWithContext(context, message) {
  205. this.trace('write() called with message of length ' + message.length);
  206. if (this.child) {
  207. this.child.sendMessageWithContext(context, message);
  208. }
  209. else {
  210. this.pendingMessage = { context, message };
  211. }
  212. }
  213. startRead() {
  214. this.trace('startRead called');
  215. if (this.child) {
  216. this.child.startRead();
  217. }
  218. else {
  219. this.readPending = true;
  220. }
  221. }
  222. halfClose() {
  223. this.trace('halfClose called');
  224. if (this.child) {
  225. this.child.halfClose();
  226. }
  227. else {
  228. this.pendingHalfClose = true;
  229. }
  230. }
  231. setCredentials(credentials) {
  232. throw new Error("Method not implemented.");
  233. }
  234. getCallNumber() {
  235. return this.callNumber;
  236. }
  237. }
  238. exports.LoadBalancingCall = LoadBalancingCall;
  239. //# sourceMappingURL=load-balancing-call.js.map