Skip to content

Commit f62d59a

Browse files
authored
[Console] Proxy fallback (#50185) (#51814)
* First iteration of liveness manager for Console * First iteration of PoC working * Updated console proxy fallback behaviour after feedback * remove @types/node-fetch * If all hosts failed due to connection refused errors 502 * Remove unnecessary existence check
1 parent e244cf8 commit f62d59a

File tree

8 files changed

+71
-61
lines changed

8 files changed

+71
-61
lines changed

src/legacy/core_plugins/console/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export default function(kibana: any) {
141141

142142
server.route(
143143
createProxyRoute({
144-
baseUrl: head(legacyEsConfig.hosts),
144+
hosts: legacyEsConfig.hosts,
145145
pathFilters: proxyPathFilters,
146146
getConfigForReq(req: any, uri: any) {
147147
const filteredHeaders = filterHeaders(

src/legacy/core_plugins/console/server/__tests__/proxy_route/body.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('Console Proxy Route', () => {
3636
const server = new Server();
3737
server.route(
3838
createProxyRoute({
39-
baseUrl: 'http://localhost:9200',
39+
hosts: ['http://localhost:9200'],
4040
})
4141
);
4242

src/legacy/core_plugins/console/server/__tests__/proxy_route/headers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('Console Proxy Route', () => {
4040
const server = new Server();
4141
server.route(
4242
createProxyRoute({
43-
baseUrl: 'http://localhost:9200',
43+
hosts: ['http://localhost:9200'],
4444
})
4545
);
4646

src/legacy/core_plugins/console/server/__tests__/proxy_route/params.js

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ describe('Console Proxy Route', () => {
7272
const { server } = setup();
7373
server.route(
7474
createProxyRoute({
75-
baseUrl: 'http://localhost:9200',
75+
hosts: ['http://localhost:9200'],
7676
pathFilters: [/^\/foo\//, /^\/bar\//],
7777
})
7878
);
@@ -91,7 +91,7 @@ describe('Console Proxy Route', () => {
9191
const { server } = setup();
9292
server.route(
9393
createProxyRoute({
94-
baseUrl: 'http://localhost:9200',
94+
hosts: ['http://localhost:9200'],
9595
pathFilters: [/^\/foo\//, /^\/bar\//],
9696
})
9797
);
@@ -113,7 +113,7 @@ describe('Console Proxy Route', () => {
113113

114114
const getConfigForReq = sinon.stub().returns({});
115115

116-
server.route(createProxyRoute({ baseUrl: 'http://localhost:9200', getConfigForReq }));
116+
server.route(createProxyRoute({ hosts: ['http://localhost:9200'], getConfigForReq }));
117117
await server.inject({
118118
method: 'POST',
119119
url: '/api/console/proxy?method=HEAD&path=/index/type/id',
@@ -142,7 +142,7 @@ describe('Console Proxy Route', () => {
142142

143143
server.route(
144144
createProxyRoute({
145-
baseUrl: 'http://localhost:9200',
145+
hosts: ['http://localhost:9200'],
146146
getConfigForReq: () => ({
147147
timeout,
148148
agent,
@@ -166,19 +166,5 @@ describe('Console Proxy Route', () => {
166166
expect(opts.headers).to.have.property('baz', 'bop');
167167
});
168168
});
169-
170-
describe('baseUrl', () => {
171-
describe('default', () => {
172-
it('ensures that the path starts with a /');
173-
});
174-
describe('url ends with a slash', () => {
175-
it('combines clean with paths that start with a slash');
176-
it(`combines clean with paths that don't start with a slash`);
177-
});
178-
describe(`url doesn't end with a slash`, () => {
179-
it('combines clean with paths that start with a slash');
180-
it(`combines clean with paths that don't start with a slash`);
181-
});
182-
});
183169
});
184170
});

src/legacy/core_plugins/console/server/__tests__/proxy_route/query_string.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe('Console Proxy Route', () => {
3838
const server = new Server();
3939
server.route(
4040
createProxyRoute({
41-
baseUrl: 'http://localhost:9200',
41+
hosts: ['http://localhost:9200'],
4242
})
4343
);
4444

src/legacy/core_plugins/console/server/proxy_route.js renamed to src/legacy/core_plugins/console/server/proxy_route.ts

Lines changed: 59 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@
1818
*/
1919

2020
import Joi from 'joi';
21+
import * as url from 'url';
22+
import { IncomingMessage } from 'http';
2123
import Boom from 'boom';
2224
import { trimLeft, trimRight } from 'lodash';
2325
import { sendRequest } from './request';
24-
import * as url from 'url';
2526

26-
function toURL(base, path) {
27+
function toURL(base: string, path: string) {
2728
const urlResult = new url.URL(`${trimRight(base, '/')}/${trimLeft(path, '/')}`);
2829
// Appending pretty here to have Elasticsearch do the JSON formatting, as doing
2930
// 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) {
3435
return urlResult;
3536
}
3637

37-
function getProxyHeaders(req) {
38+
function getProxyHeaders(req: any) {
3839
const headers = Object.create(null);
3940

4041
// Scope this proto-unsafe functionality to where it is being used.
41-
function extendCommaList(obj, property, value) {
42+
function extendCommaList(obj: Record<string, any>, property: string, value: any) {
4243
obj[property] = (obj[property] ? obj[property] + ',' : '') + value;
4344
}
4445

@@ -58,9 +59,13 @@ function getProxyHeaders(req) {
5859
}
5960

6061
export const createProxyRoute = ({
61-
baseUrl = '/',
62+
hosts,
6263
pathFilters = [/.*/],
6364
getConfigForReq = () => ({}),
65+
}: {
66+
hosts: string[];
67+
pathFilters: RegExp[];
68+
getConfigForReq: (...args: any[]) => any;
6469
}) => ({
6570
path: '/api/console/proxy',
6671
method: 'POST',
@@ -84,63 +89,82 @@ export const createProxyRoute = ({
8489
},
8590

8691
pre: [
87-
function filterPath(req) {
92+
function filterPath(req: any) {
8893
const { path } = req.query;
8994

9095
if (pathFilters.some(re => re.test(path))) {
9196
return null;
9297
}
9398

9499
const err = Boom.forbidden();
95-
err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.`;
100+
err.output.payload = `Error connecting to '${path}':\n\nUnable to send requests to that path.` as any;
96101
err.output.headers['content-type'] = 'text/plain';
97102
throw err;
98103
},
99104
],
100105

101-
handler: async (req, h) => {
106+
handler: async (req: any, h: any) => {
102107
const { payload, query } = req;
103108
const { path, method } = query;
104-
const uri = toURL(baseUrl, path);
105-
106-
// Because this can technically be provided by a settings-defined proxy config, we need to
107-
// preserve these property names to maintain BWC.
108-
const { timeout, agent, headers, rejectUnauthorized } = getConfigForReq(req, uri.toString());
109-
110-
const requestHeaders = {
111-
...headers,
112-
...getProxyHeaders(req),
113-
};
114-
115-
const esIncomingMessage = await sendRequest({
116-
method,
117-
headers: requestHeaders,
118-
uri,
119-
timeout,
120-
payload,
121-
rejectUnauthorized,
122-
agent,
123-
});
109+
110+
let esIncomingMessage: IncomingMessage;
111+
112+
for (let idx = 0; idx < hosts.length; ++idx) {
113+
const host = hosts[idx];
114+
try {
115+
const uri = toURL(host, path);
116+
117+
// Because this can technically be provided by a settings-defined proxy config, we need to
118+
// preserve these property names to maintain BWC.
119+
const { timeout, agent, headers, rejectUnauthorized } = getConfigForReq(
120+
req,
121+
uri.toString()
122+
);
123+
124+
const requestHeaders = {
125+
...headers,
126+
...getProxyHeaders(req),
127+
};
128+
129+
esIncomingMessage = await sendRequest({
130+
method,
131+
headers: requestHeaders,
132+
uri,
133+
timeout,
134+
payload,
135+
rejectUnauthorized,
136+
agent,
137+
});
138+
139+
break;
140+
} catch (e) {
141+
if (e.code !== 'ECONNREFUSED') {
142+
throw Boom.boomify(e);
143+
}
144+
if (idx === hosts.length - 1) {
145+
throw Boom.badGateway('Could not reach any configured nodes.');
146+
}
147+
// Otherwise, try the next host...
148+
}
149+
}
124150

125151
const {
126152
statusCode,
127153
statusMessage,
128-
headers: responseHeaders,
129-
} = esIncomingMessage;
130-
131-
const { warning } = responseHeaders;
154+
headers: { warning },
155+
} = esIncomingMessage!;
132156

133157
if (method.toUpperCase() !== 'HEAD') {
134158
return h
135-
.response(esIncomingMessage)
159+
.response(esIncomingMessage!)
136160
.code(statusCode)
137-
.header('warning', warning);
161+
.header('warning', warning!);
138162
} else {
139163
return h
140164
.response(`${statusCode} - ${statusMessage}`)
141165
.code(statusCode)
142166
.type('text/plain')
143-
.header('warning', warning);
167+
.header('warning', warning!);
144168
}
145169
},
146170
},

src/legacy/core_plugins/console/server/request.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { fail } from 'assert';
2424

2525
describe(`Console's send request`, () => {
2626
let sandbox: sinon.SinonSandbox;
27-
let stub: sinon.SinonStub<Parameters<typeof http['request']>, ClientRequest>;
27+
let stub: sinon.SinonStub<Parameters<typeof http.request>, ClientRequest>;
2828
let fakeRequest: http.ClientRequest;
2929

3030
beforeEach(() => {
@@ -52,7 +52,7 @@ describe(`Console's send request`, () => {
5252
method: 'get',
5353
payload: null as any,
5454
timeout: 0, // immediately timeout
55-
uri: new URL('http://noone.nowhere.com'),
55+
uri: new URL('http://noone.nowhere.none'),
5656
});
5757
fail('Should not reach here!');
5858
} catch (e) {

src/legacy/core_plugins/console/server/request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export const sendRequest = ({
8989
}
9090
});
9191

92-
const onError = () => reject();
92+
const onError = (e: Error) => reject(e);
9393
req.once('error', onError);
9494

9595
const timeoutPromise = new Promise<any>((timeoutResolve, timeoutReject) => {
@@ -103,5 +103,5 @@ export const sendRequest = ({
103103
}, timeout);
104104
});
105105

106-
return Promise.race<http.ServerResponse>([reqPromise, timeoutPromise]);
106+
return Promise.race<http.IncomingMessage>([reqPromise, timeoutPromise]);
107107
};

0 commit comments

Comments
 (0)