Skip to content

Commit 0952f6e

Browse files
lukasolsonjughosta
andauthored
Fix resolve index API to not throw 500 when encountering no_such_remote_cluster_exception (#204802)
## Summary Fixes #197747. Updates the `/internal/index-pattern-management/resolve_index/{query}` route to properly handle `no_such_remote_cluster_exception` and return `404` rather than `500` server error. Adds unit tests for the route handler. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios --------- Co-authored-by: Julia Rechkunova <[email protected]>
1 parent 9ad31d0 commit 0952f6e

File tree

3 files changed

+255
-2
lines changed

3 files changed

+255
-2
lines changed
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import type { MockedKeys } from '@kbn/utility-types-jest';
11+
import { CoreSetup, RequestHandlerContext } from '@kbn/core/server';
12+
import { coreMock, httpServerMock } from '@kbn/core/server/mocks';
13+
import { registerResolveIndexRoute } from './resolve_index';
14+
15+
const mockResponseIndices = {
16+
indices: [
17+
{
18+
name: 'kibana_sample_data_logs',
19+
attributes: ['open'],
20+
},
21+
],
22+
aliases: [],
23+
data_streams: [],
24+
};
25+
26+
const mockResponseEmpty = {
27+
indices: [],
28+
aliases: [],
29+
data_streams: [],
30+
};
31+
32+
const mockError403 = {
33+
meta: {
34+
body: {
35+
error: {
36+
root_cause: [
37+
{
38+
type: 'no_such_remote_cluster_exception',
39+
reason: 'no such remote cluster: [cluster1]',
40+
},
41+
],
42+
type: 'security_exception',
43+
reason:
44+
'action [indices:admin/resolve/index] is unauthorized for user [elastic] with effective roles [superuser], this action is granted by the index privileges [view_index_metadata,manage,read,all]',
45+
caused_by: {
46+
type: 'no_such_remote_cluster_exception',
47+
reason: 'no such remote cluster: [cluster1]',
48+
},
49+
},
50+
status: 403,
51+
},
52+
statusCode: 403,
53+
},
54+
};
55+
56+
const mockError404 = {
57+
meta: {
58+
body: {
59+
error: {
60+
root_cause: [
61+
{
62+
type: 'index_not_found_exception',
63+
reason: 'no such index [asdf]',
64+
'resource.type': 'index_or_alias',
65+
'resource.id': 'asdf',
66+
index_uuid: '_na_',
67+
index: 'asdf',
68+
},
69+
],
70+
type: 'index_not_found_exception',
71+
reason: 'no such index [asdf]',
72+
'resource.type': 'index_or_alias',
73+
'resource.id': 'asdf',
74+
index_uuid: '_na_',
75+
index: 'asdf',
76+
},
77+
status: 404,
78+
},
79+
statusCode: 404,
80+
},
81+
};
82+
83+
describe('resolve_index route', () => {
84+
let mockCoreSetup: MockedKeys<CoreSetup>;
85+
86+
beforeEach(() => {
87+
mockCoreSetup = coreMock.createSetup();
88+
});
89+
90+
it('handler calls /_resolve/index with the given request', async () => {
91+
const mockClient = {
92+
indices: {
93+
resolveIndex: jest.fn().mockResolvedValue(mockResponseIndices),
94+
},
95+
};
96+
const mockContext = {
97+
core: {
98+
elasticsearch: { client: { asCurrentUser: mockClient } },
99+
},
100+
};
101+
const mockRequest = httpServerMock.createKibanaRequest({
102+
params: {
103+
query: 'kibana_sample_data_logs',
104+
},
105+
});
106+
const mockResponse = httpServerMock.createResponseFactory();
107+
108+
registerResolveIndexRoute(mockCoreSetup.http.createRouter());
109+
110+
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
111+
const handler = mockRouter.get.mock.calls[0][1];
112+
await handler(mockContext as unknown as RequestHandlerContext, mockRequest, mockResponse);
113+
114+
expect(mockClient.indices.resolveIndex.mock.calls[0][0]).toMatchInlineSnapshot(`
115+
Object {
116+
"expand_wildcards": "open",
117+
"name": "kibana_sample_data_logs",
118+
}
119+
`);
120+
121+
expect(mockResponse.ok).toBeCalled();
122+
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: mockResponseIndices });
123+
});
124+
125+
it('should return 200 for a search for indices with wildcard', async () => {
126+
const mockClient = {
127+
indices: {
128+
resolveIndex: jest.fn().mockResolvedValue(mockResponseEmpty),
129+
},
130+
};
131+
const mockContext = {
132+
core: {
133+
elasticsearch: { client: { asCurrentUser: mockClient } },
134+
},
135+
};
136+
const mockRequest = httpServerMock.createKibanaRequest({
137+
params: {
138+
query: 'asdf*',
139+
},
140+
});
141+
const mockResponse = httpServerMock.createResponseFactory();
142+
143+
registerResolveIndexRoute(mockCoreSetup.http.createRouter());
144+
145+
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
146+
const handler = mockRouter.get.mock.calls[0][1];
147+
await handler(mockContext as unknown as RequestHandlerContext, mockRequest, mockResponse);
148+
149+
expect(mockClient.indices.resolveIndex.mock.calls[0][0]).toMatchInlineSnapshot(`
150+
Object {
151+
"expand_wildcards": "open",
152+
"name": "asdf*",
153+
}
154+
`);
155+
156+
expect(mockResponse.ok).toBeCalled();
157+
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: mockResponseEmpty });
158+
});
159+
160+
it('returns 404 when hitting a 403 from Elasticsearch', async () => {
161+
const mockClient = {
162+
indices: {
163+
resolveIndex: jest.fn().mockRejectedValue(mockError403),
164+
},
165+
};
166+
const mockContext = {
167+
core: {
168+
elasticsearch: { client: { asCurrentUser: mockClient } },
169+
},
170+
};
171+
const mockRequest = httpServerMock.createKibanaRequest({
172+
params: {
173+
query: 'cluster1:filebeat-*,cluster2:filebeat-*',
174+
},
175+
});
176+
const mockResponse = httpServerMock.createResponseFactory();
177+
178+
registerResolveIndexRoute(mockCoreSetup.http.createRouter());
179+
180+
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
181+
const handler = mockRouter.get.mock.calls[0][1];
182+
183+
await handler(mockContext as unknown as RequestHandlerContext, mockRequest, mockResponse);
184+
185+
expect(mockClient.indices.resolveIndex.mock.calls[0][0]).toMatchInlineSnapshot(`
186+
Object {
187+
"expand_wildcards": "open",
188+
"name": "cluster1:filebeat-*,cluster2:filebeat-*",
189+
}
190+
`);
191+
192+
expect(mockResponse.notFound).toBeCalled();
193+
expect(mockResponse.notFound.mock.calls[0][0]).toMatchInlineSnapshot(`
194+
Object {
195+
"body": Object {
196+
"message": "action [indices:admin/resolve/index] is unauthorized for user [elastic] with effective roles [superuser], this action is granted by the index privileges [view_index_metadata,manage,read,all]",
197+
},
198+
}
199+
`);
200+
});
201+
202+
it('returns 404 when hitting a 404 from Elasticsearch', async () => {
203+
const mockClient = {
204+
indices: {
205+
resolveIndex: jest.fn().mockRejectedValue(mockError404),
206+
},
207+
};
208+
const mockContext = {
209+
core: {
210+
elasticsearch: { client: { asCurrentUser: mockClient } },
211+
},
212+
};
213+
const mockRequest = httpServerMock.createKibanaRequest({
214+
params: {
215+
query: 'asdf',
216+
},
217+
});
218+
const mockResponse = httpServerMock.createResponseFactory();
219+
220+
registerResolveIndexRoute(mockCoreSetup.http.createRouter());
221+
222+
const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
223+
const handler = mockRouter.get.mock.calls[0][1];
224+
225+
await handler(mockContext as unknown as RequestHandlerContext, mockRequest, mockResponse);
226+
227+
expect(mockClient.indices.resolveIndex.mock.calls[0][0]).toMatchInlineSnapshot(`
228+
Object {
229+
"expand_wildcards": "open",
230+
"name": "asdf",
231+
}
232+
`);
233+
234+
expect(mockResponse.notFound).toBeCalled();
235+
expect(mockResponse.notFound.mock.calls[0][0]).toMatchInlineSnapshot(`
236+
Object {
237+
"body": Object {
238+
"message": "no such index [asdf]",
239+
},
240+
}
241+
`);
242+
});
243+
});

src/plugins/data_view_management/server/routes/resolve_index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ export function registerResolveIndexRoute(router: IRouter): void {
4747
});
4848
return res.ok({ body });
4949
} catch (e) {
50-
if (e?.meta.statusCode === 404) {
50+
// 403: no_such_remote_cluster_exception
51+
// 404: index_not_found_exception
52+
if ([403, 404].includes(e?.meta.statusCode)) {
5153
return res.notFound({ body: { message: e.meta?.body?.error?.reason } });
5254
} else {
5355
throw getKbnServerError(e);

test/api_integration/apis/data_views/resolve_index/resolve_index.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,18 @@ export default function ({ getService }: FtrProviderContext) {
2222
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
2323
.expect(200));
2424

25-
it('should return 404 for an exact match index', () =>
25+
it('should return 404 when no indices match', () =>
2626
supertest
2727
.get(`/internal/index-pattern-management/resolve_index/test`)
2828
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
2929
.expect(404));
30+
31+
it('should return 404 when cluster is not found', () =>
32+
supertest
33+
.get(
34+
`/internal/index-pattern-management/resolve_index/cluster1:filebeat-*,cluster2:filebeat-*`
35+
)
36+
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
37+
.expect(404));
3038
});
3139
}

0 commit comments

Comments
 (0)