From 64ce959824cf9e36f9addc0af2bf9a59a6794900 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 11 Nov 2019 19:14:30 +0100 Subject: [PATCH 1/6] First iteration of liveness manager for Console --- package.json | 1 + .../server/live_hosts_manager/index.ts | 20 +++++++ .../live_hosts_manager/live_hosts_manager.ts | 32 +++++++++++ .../server/live_hosts_manager/utils.ts | 57 +++++++++++++++++++ yarn.lock | 7 +++ 5 files changed, 117 insertions(+) create mode 100644 src/legacy/core_plugins/console/server/live_hosts_manager/index.ts create mode 100644 src/legacy/core_plugins/console/server/live_hosts_manager/live_hosts_manager.ts create mode 100644 src/legacy/core_plugins/console/server/live_hosts_manager/utils.ts diff --git a/package.json b/package.json index a8e60e9749f72..a1d07e45c5fa0 100644 --- a/package.json +++ b/package.json @@ -128,6 +128,7 @@ "@kbn/ui-framework": "1.0.0", "@types/json-stable-stringify": "^1.0.32", "@types/lodash.clonedeep": "^4.5.4", + "@types/node-fetch": "2.5.3", "@types/react-grid-layout": "^0.16.7", "@types/recompose": "^0.30.5", "JSONStream": "1.3.5", diff --git a/src/legacy/core_plugins/console/server/live_hosts_manager/index.ts b/src/legacy/core_plugins/console/server/live_hosts_manager/index.ts new file mode 100644 index 0000000000000..16f9c836abba1 --- /dev/null +++ b/src/legacy/core_plugins/console/server/live_hosts_manager/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { LiveHostsManager } from './live_hosts_manager'; diff --git a/src/legacy/core_plugins/console/server/live_hosts_manager/live_hosts_manager.ts b/src/legacy/core_plugins/console/server/live_hosts_manager/live_hosts_manager.ts new file mode 100644 index 0000000000000..bc9009128733a --- /dev/null +++ b/src/legacy/core_plugins/console/server/live_hosts_manager/live_hosts_manager.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { findLiveHostIdx } from './utils'; + +export class LiveHostsManager { + // Assume first host is live + private pointer = 0; + + constructor(private readonly hosts: string[]) {} + + async getLiveHost(): Promise { + this.pointer = await findLiveHostIdx(this.pointer, this.hosts); + return this.hosts[this.pointer]; + } +} diff --git a/src/legacy/core_plugins/console/server/live_hosts_manager/utils.ts b/src/legacy/core_plugins/console/server/live_hosts_manager/utils.ts new file mode 100644 index 0000000000000..2ed35a7b227b9 --- /dev/null +++ b/src/legacy/core_plugins/console/server/live_hosts_manager/utils.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import fetch from 'node-fetch'; + +const probe = async (host: string): Promise<{ ok: boolean }> => { + try { + await fetch(`${host}/`, { method: 'HEAD' }); + return { ok: true }; + } catch (e) { + return { ok: false }; + } +}; + +const roundRobinNextIdx = (number: number, max: number) => { + if (number >= max) { + return 0; + } + return number + 1; +}; + +export const findLiveHostIdx = async (startIdx: number, hosts: string[]) => { + if ((await probe(hosts[startIdx])).ok) { + return startIdx; + } + + const recurse = async (idx: number): Promise => { + if (idx === startIdx) { + throw new Error('No live host found!'); + } + const host = hosts[idx]; + const result = await probe(host); + if (result.ok) { + return idx; + } + const nextId = roundRobinNextIdx(idx, hosts.length); + return recurse(nextId); + }; + + return await recurse(roundRobinNextIdx(startIdx, hosts.length)); +}; diff --git a/yarn.lock b/yarn.lock index 6526c90663b75..0439080388c56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3652,6 +3652,13 @@ dependencies: "@types/node" "*" +"@types/node-fetch@2.5.3": + version "2.5.3" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.3.tgz#b84127facd93642b1fb6439bc630ba0612e3ec50" + integrity sha512-X3TNlzZ7SuSwZsMkb5fV7GrPbVKvHc2iwHmslb8bIxRKWg2iqkfm3F/Wd79RhDpOXR7wCtKAwc5Y2JE6n/ibyw== + dependencies: + "@types/node" "*" + "@types/node-fetch@^2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.0.tgz#1c55616a4591bdd15a389fbd0da4a55b9502add5" From 97fc31f4e30e58c2deaab94eec943c9cffcca343 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 11 Nov 2019 19:22:09 +0100 Subject: [PATCH 2/6] First iteration of PoC working --- src/legacy/core_plugins/console/index.ts | 4 +++- src/legacy/core_plugins/console/server/proxy_route.js | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/console/index.ts b/src/legacy/core_plugins/console/index.ts index 30a85f4e7d342..722566ef2cffa 100644 --- a/src/legacy/core_plugins/console/index.ts +++ b/src/legacy/core_plugins/console/index.ts @@ -22,6 +22,7 @@ import { first } from 'rxjs/operators'; import { resolve, join, sep } from 'path'; import url from 'url'; import { has, isEmpty, head, pick } from 'lodash'; +import { LiveHostsManager } from './server/live_hosts_manager'; // @ts-ignore import { addProcessorDefinition } from './server/api_server/es_6_0/ingest'; @@ -142,7 +143,8 @@ export default function(kibana: any) { server.route( createProxyRoute({ - baseUrl: head(legacyEsConfig.hosts), + // baseUrl: head(legacyEsConfig.hosts), + liveHostsManager: new LiveHostsManager(legacyEsConfig.hosts), pathFilters: proxyPathFilters, getConfigForReq(req: any, uri: any) { const filteredHeaders = filterHeaders( diff --git a/src/legacy/core_plugins/console/server/proxy_route.js b/src/legacy/core_plugins/console/server/proxy_route.js index 8ce828879a677..a2169c29607d8 100644 --- a/src/legacy/core_plugins/console/server/proxy_route.js +++ b/src/legacy/core_plugins/console/server/proxy_route.js @@ -58,7 +58,7 @@ function getProxyHeaders(req) { } export const createProxyRoute = ({ - baseUrl = '/', + liveHostsManager, pathFilters = [/.*/], getConfigForReq = () => ({}), }) => ({ @@ -100,6 +100,7 @@ export const createProxyRoute = ({ handler: async (req, h) => { const { payload, query } = req; const { path, method } = query; + const baseUrl = await liveHostsManager.getLiveHost(); const uri = toURL(baseUrl, path); // Because this can technically be provided by a settings-defined proxy config, we need to From 5198f2047339c68b34dcfeb04c98c508ec9a17dc Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 18 Nov 2019 22:15:56 +0100 Subject: [PATCH 3/6] Updated console proxy fallback behaviour after feedback --- src/legacy/core_plugins/console/index.ts | 4 +- .../server/__tests__/proxy_route/body.js | 2 +- .../server/__tests__/proxy_route/headers.js | 2 +- .../server/__tests__/proxy_route/params.js | 22 +---- .../__tests__/proxy_route/query_string.js | 2 +- .../server/live_hosts_manager/index.ts | 20 ---- .../live_hosts_manager/live_hosts_manager.ts | 32 ------- .../server/live_hosts_manager/utils.ts | 57 ------------ .../server/{proxy_route.js => proxy_route.ts} | 91 +++++++++++-------- .../console/server/request.test.ts | 4 +- .../core_plugins/console/server/request.ts | 4 +- 11 files changed, 67 insertions(+), 173 deletions(-) delete mode 100644 src/legacy/core_plugins/console/server/live_hosts_manager/index.ts delete mode 100644 src/legacy/core_plugins/console/server/live_hosts_manager/live_hosts_manager.ts delete mode 100644 src/legacy/core_plugins/console/server/live_hosts_manager/utils.ts rename src/legacy/core_plugins/console/server/{proxy_route.js => proxy_route.ts} (67%) diff --git a/src/legacy/core_plugins/console/index.ts b/src/legacy/core_plugins/console/index.ts index 722566ef2cffa..5f6c1b86a8bba 100644 --- a/src/legacy/core_plugins/console/index.ts +++ b/src/legacy/core_plugins/console/index.ts @@ -22,7 +22,6 @@ import { first } from 'rxjs/operators'; import { resolve, join, sep } from 'path'; import url from 'url'; import { has, isEmpty, head, pick } from 'lodash'; -import { LiveHostsManager } from './server/live_hosts_manager'; // @ts-ignore import { addProcessorDefinition } from './server/api_server/es_6_0/ingest'; @@ -143,8 +142,7 @@ export default function(kibana: any) { server.route( createProxyRoute({ - // baseUrl: head(legacyEsConfig.hosts), - liveHostsManager: new LiveHostsManager(legacyEsConfig.hosts), + hosts: legacyEsConfig.hosts, pathFilters: proxyPathFilters, getConfigForReq(req: any, uri: any) { const filteredHeaders = filterHeaders( diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js b/src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js index c9ad09cb017c4..a7fd8df1b10f4 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js @@ -36,7 +36,7 @@ describe('Console Proxy Route', () => { const server = new Server(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], }) ); diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js b/src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js index 2e78201f9990e..347b8dae80e29 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js @@ -40,7 +40,7 @@ describe('Console Proxy Route', () => { const server = new Server(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], }) ); diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js b/src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js index aa7b764f84fc7..2cf09f96e7b72 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js @@ -72,7 +72,7 @@ describe('Console Proxy Route', () => { const { server } = setup(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], pathFilters: [/^\/foo\//, /^\/bar\//], }) ); @@ -91,7 +91,7 @@ describe('Console Proxy Route', () => { const { server } = setup(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], pathFilters: [/^\/foo\//, /^\/bar\//], }) ); @@ -113,7 +113,7 @@ describe('Console Proxy Route', () => { const getConfigForReq = sinon.stub().returns({}); - server.route(createProxyRoute({ baseUrl: 'http://localhost:9200', getConfigForReq })); + server.route(createProxyRoute({ hosts: ['http://localhost:9200'], getConfigForReq })); await server.inject({ method: 'POST', url: '/api/console/proxy?method=HEAD&path=/index/id', @@ -142,7 +142,7 @@ describe('Console Proxy Route', () => { server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], getConfigForReq: () => ({ timeout, agent, @@ -166,19 +166,5 @@ describe('Console Proxy Route', () => { expect(opts.headers).to.have.property('baz', 'bop'); }); }); - - describe('baseUrl', () => { - describe('default', () => { - it('ensures that the path starts with a /'); - }); - describe('url ends with a slash', () => { - it('combines clean with paths that start with a slash'); - it(`combines clean with paths that don't start with a slash`); - }); - describe(`url doesn't end with a slash`, () => { - it('combines clean with paths that start with a slash'); - it(`combines clean with paths that don't start with a slash`); - }); - }); }); }); diff --git a/src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js b/src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js index f20adb897be65..6b98702131d91 100644 --- a/src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js +++ b/src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js @@ -38,7 +38,7 @@ describe('Console Proxy Route', () => { const server = new Server(); server.route( createProxyRoute({ - baseUrl: 'http://localhost:9200', + hosts: ['http://localhost:9200'], }) ); diff --git a/src/legacy/core_plugins/console/server/live_hosts_manager/index.ts b/src/legacy/core_plugins/console/server/live_hosts_manager/index.ts deleted file mode 100644 index 16f9c836abba1..0000000000000 --- a/src/legacy/core_plugins/console/server/live_hosts_manager/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { LiveHostsManager } from './live_hosts_manager'; diff --git a/src/legacy/core_plugins/console/server/live_hosts_manager/live_hosts_manager.ts b/src/legacy/core_plugins/console/server/live_hosts_manager/live_hosts_manager.ts deleted file mode 100644 index bc9009128733a..0000000000000 --- a/src/legacy/core_plugins/console/server/live_hosts_manager/live_hosts_manager.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { findLiveHostIdx } from './utils'; - -export class LiveHostsManager { - // Assume first host is live - private pointer = 0; - - constructor(private readonly hosts: string[]) {} - - async getLiveHost(): Promise { - this.pointer = await findLiveHostIdx(this.pointer, this.hosts); - return this.hosts[this.pointer]; - } -} diff --git a/src/legacy/core_plugins/console/server/live_hosts_manager/utils.ts b/src/legacy/core_plugins/console/server/live_hosts_manager/utils.ts deleted file mode 100644 index 2ed35a7b227b9..0000000000000 --- a/src/legacy/core_plugins/console/server/live_hosts_manager/utils.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import fetch from 'node-fetch'; - -const probe = async (host: string): Promise<{ ok: boolean }> => { - try { - await fetch(`${host}/`, { method: 'HEAD' }); - return { ok: true }; - } catch (e) { - return { ok: false }; - } -}; - -const roundRobinNextIdx = (number: number, max: number) => { - if (number >= max) { - return 0; - } - return number + 1; -}; - -export const findLiveHostIdx = async (startIdx: number, hosts: string[]) => { - if ((await probe(hosts[startIdx])).ok) { - return startIdx; - } - - const recurse = async (idx: number): Promise => { - if (idx === startIdx) { - throw new Error('No live host found!'); - } - const host = hosts[idx]; - const result = await probe(host); - if (result.ok) { - return idx; - } - const nextId = roundRobinNextIdx(idx, hosts.length); - return recurse(nextId); - }; - - return await recurse(roundRobinNextIdx(startIdx, hosts.length)); -}; diff --git a/src/legacy/core_plugins/console/server/proxy_route.js b/src/legacy/core_plugins/console/server/proxy_route.ts similarity index 67% rename from src/legacy/core_plugins/console/server/proxy_route.js rename to src/legacy/core_plugins/console/server/proxy_route.ts index a2169c29607d8..cd12f8ae1531e 100644 --- a/src/legacy/core_plugins/console/server/proxy_route.js +++ b/src/legacy/core_plugins/console/server/proxy_route.ts @@ -18,12 +18,13 @@ */ import Joi from 'joi'; +import * as url from 'url'; +import { IncomingMessage } from 'http'; import Boom from 'boom'; import { trimLeft, trimRight } from 'lodash'; import { sendRequest } from './request'; -import * as url from 'url'; -function toURL(base, path) { +function toURL(base: string, path: string) { const urlResult = new url.URL(`${trimRight(base, '/')}/${trimLeft(path, '/')}`); // Appending pretty here to have Elasticsearch do the JSON formatting, as doing // in JS can lead to data loss (7.0 will get munged into 7, thus losing indication of @@ -34,11 +35,11 @@ function toURL(base, path) { return urlResult; } -function getProxyHeaders(req) { +function getProxyHeaders(req: any) { const headers = Object.create(null); // Scope this proto-unsafe functionality to where it is being used. - function extendCommaList(obj, property, value) { + function extendCommaList(obj: Record, property: string, value: any) { obj[property] = (obj[property] ? obj[property] + ',' : '') + value; } @@ -58,9 +59,13 @@ function getProxyHeaders(req) { } export const createProxyRoute = ({ - liveHostsManager, + hosts, pathFilters = [/.*/], getConfigForReq = () => ({}), +}: { + hosts: string[]; + pathFilters: RegExp[]; + getConfigForReq: (...args: any[]) => any; }) => ({ path: '/api/console/proxy', method: 'POST', @@ -83,7 +88,7 @@ export const createProxyRoute = ({ }, pre: [ - function filterPath(req) { + function filterPath(req: any) { const { path } = req.query; if (pathFilters.some(re => re.test(path))) { @@ -91,56 +96,70 @@ export const createProxyRoute = ({ } const err = Boom.forbidden(); - err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.`; + err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.` as any; err.output.headers['content-type'] = 'text/plain'; throw err; }, ], - handler: async (req, h) => { + handler: async (req: any, h: any) => { const { payload, query } = req; const { path, method } = query; - const baseUrl = await liveHostsManager.getLiveHost(); - const uri = toURL(baseUrl, path); - - // Because this can technically be provided by a settings-defined proxy config, we need to - // preserve these property names to maintain BWC. - const { timeout, agent, headers, rejectUnauthorized } = getConfigForReq(req, uri.toString()); - - const requestHeaders = { - ...headers, - ...getProxyHeaders(req), - }; - - const esIncomingMessage = await sendRequest({ - method, - headers: requestHeaders, - uri, - timeout, - payload, - rejectUnauthorized, - agent, - }); + + let esIncomingMessage: IncomingMessage; + + for (const host of hosts) { + try { + const uri = toURL(host, path); + + // Because this can technically be provided by a settings-defined proxy config, we need to + // preserve these property names to maintain BWC. + const { timeout, agent, headers, rejectUnauthorized } = getConfigForReq( + req, + uri.toString() + ); + + const requestHeaders = { + ...headers, + ...getProxyHeaders(req), + }; + + esIncomingMessage = await sendRequest({ + method, + headers: requestHeaders, + uri, + timeout, + payload, + rejectUnauthorized, + agent, + }); + + break; + } catch (e) { + if (e.code !== 'ECONNREFUSED') { + throw Boom.boomify(e); + } + // Otherwise, try the next host... + } + } const { statusCode, statusMessage, - headers: responseHeaders, - } = esIncomingMessage; - - const { warning } = responseHeaders; + headers: { warning }, + } = esIncomingMessage!; if (method.toUpperCase() !== 'HEAD') { return h - .response(esIncomingMessage) + .response(esIncomingMessage!) .code(statusCode) - .header('warning', warning); + .header('warning', warning!); } else { return h .response(`${statusCode} - ${statusMessage}`) .code(statusCode) .type('text/plain') - .header('warning', warning); + .header('warning', warning!); } }, }, diff --git a/src/legacy/core_plugins/console/server/request.test.ts b/src/legacy/core_plugins/console/server/request.test.ts index 463649a090295..4b0e9cd4dd4ec 100644 --- a/src/legacy/core_plugins/console/server/request.test.ts +++ b/src/legacy/core_plugins/console/server/request.test.ts @@ -24,7 +24,7 @@ import { fail } from 'assert'; describe(`Console's send request`, () => { let sandbox: sinon.SinonSandbox; - let stub: sinon.SinonStub; + let stub: sinon.SinonStub>; let fakeRequest: http.ClientRequest; beforeEach(() => { @@ -52,7 +52,7 @@ describe(`Console's send request`, () => { method: 'get', payload: null as any, timeout: 0, // immediately timeout - uri: new URL('http://noone.nowhere.com'), + uri: new URL('http://noone.nowhere.none'), }); fail('Should not reach here!'); } catch (e) { diff --git a/src/legacy/core_plugins/console/server/request.ts b/src/legacy/core_plugins/console/server/request.ts index 0082f3591a132..0f6b78b484adf 100644 --- a/src/legacy/core_plugins/console/server/request.ts +++ b/src/legacy/core_plugins/console/server/request.ts @@ -89,7 +89,7 @@ export const sendRequest = ({ } }); - const onError = () => reject(); + const onError = (e: Error) => reject(e); req.once('error', onError); const timeoutPromise = new Promise((timeoutResolve, timeoutReject) => { @@ -103,5 +103,5 @@ export const sendRequest = ({ }, timeout); }); - return Promise.race([reqPromise, timeoutPromise]); + return Promise.race([reqPromise, timeoutPromise]); }; From 0794b317c48549856c83dce21d019cc8b0167ca5 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 18 Nov 2019 22:21:20 +0100 Subject: [PATCH 4/6] remove @types/node-fetch --- package.json | 1 - yarn.lock | 7 ------- 2 files changed, 8 deletions(-) diff --git a/package.json b/package.json index 69ae0e578adfe..cf9158c3a59b8 100644 --- a/package.json +++ b/package.json @@ -128,7 +128,6 @@ "@kbn/ui-framework": "1.0.0", "@types/json-stable-stringify": "^1.0.32", "@types/lodash.clonedeep": "^4.5.4", - "@types/node-fetch": "2.5.3", "@types/react-grid-layout": "^0.16.7", "@types/recompose": "^0.30.5", "JSONStream": "1.3.5", diff --git a/yarn.lock b/yarn.lock index 6daf64bfeb02a..d0ebd30d70136 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3652,13 +3652,6 @@ dependencies: "@types/node" "*" -"@types/node-fetch@2.5.3": - version "2.5.3" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.3.tgz#b84127facd93642b1fb6439bc630ba0612e3ec50" - integrity sha512-X3TNlzZ7SuSwZsMkb5fV7GrPbVKvHc2iwHmslb8bIxRKWg2iqkfm3F/Wd79RhDpOXR7wCtKAwc5Y2JE6n/ibyw== - dependencies: - "@types/node" "*" - "@types/node-fetch@^2.5.0": version "2.5.0" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.0.tgz#1c55616a4591bdd15a389fbd0da4a55b9502add5" From 53d4223f9c69d853df1adb76c600f826b8be8211 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Mon, 18 Nov 2019 22:30:21 +0100 Subject: [PATCH 5/6] If all hosts failed due to connection refused errors 502 --- src/legacy/core_plugins/console/server/proxy_route.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/console/server/proxy_route.ts b/src/legacy/core_plugins/console/server/proxy_route.ts index cd12f8ae1531e..3fcfffd18840a 100644 --- a/src/legacy/core_plugins/console/server/proxy_route.ts +++ b/src/legacy/core_plugins/console/server/proxy_route.ts @@ -108,7 +108,8 @@ export const createProxyRoute = ({ let esIncomingMessage: IncomingMessage; - for (const host of hosts) { + for (let idx = 0; idx < hosts.length; ++idx) { + const host = hosts[idx]; try { const uri = toURL(host, path); @@ -136,9 +137,12 @@ export const createProxyRoute = ({ break; } catch (e) { - if (e.code !== 'ECONNREFUSED') { + if (e && e.code !== 'ECONNREFUSED') { throw Boom.boomify(e); } + if (idx === hosts.length - 1) { + throw Boom.badGateway('Could not reach any configured nodes.'); + } // Otherwise, try the next host... } } From 110b366cc51a570cb90dfb9316a359b8037eaba6 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leysens Date: Tue, 19 Nov 2019 10:40:59 +0100 Subject: [PATCH 6/6] Remove unnecessary existence check --- src/legacy/core_plugins/console/server/proxy_route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/console/server/proxy_route.ts b/src/legacy/core_plugins/console/server/proxy_route.ts index 04a39658b7944..f67c97443ba07 100644 --- a/src/legacy/core_plugins/console/server/proxy_route.ts +++ b/src/legacy/core_plugins/console/server/proxy_route.ts @@ -138,7 +138,7 @@ export const createProxyRoute = ({ break; } catch (e) { - if (e && e.code !== 'ECONNREFUSED') { + if (e.code !== 'ECONNREFUSED') { throw Boom.boomify(e); } if (idx === hosts.length - 1) {