Skip to content

Commit

Permalink
[8.x] Fix resolve index API to not throw 500 when encountering `…
Browse files Browse the repository at this point in the history
…no_such_remote_cluster_exception` (#204802) (#205016)

# Backport

This will backport the following commits from `main` to `8.x`:
- [Fix resolve index API to not throw 500 when encountering
`no_such_remote_cluster_exception`
(#204802)](#204802)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Lukas
Olson","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-12-19T21:55:29Z","message":"Fix
resolve index API to not throw 500 when encountering
`no_such_remote_cluster_exception` (#204802)\n\n## Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/197747.\r\n\r\nUpdates the
`/internal/index-pattern-management/resolve_index/{query}`\r\nroute to
properly handle `no_such_remote_cluster_exception` and return\r\n`404`
rather than `500` server error.\r\n\r\nAdds unit tests for the route
handler.\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following
conditions. \r\n\r\nReviewers should verify this PR satisfies this list
as well.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Julia Rechkunova
<[email protected]>","sha":"0952f6e0f52771b3503c9ddd2afd793d8ed86709","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["Feature:Data
Views","release_note:skip","v9.0.0","Team:DataDiscovery","backport:prev-minor"],"title":"Fix
resolve index API to not throw 500 when encountering
`no_such_remote_cluster_exception`","number":204802,"url":"https://github.com/elastic/kibana/pull/204802","mergeCommit":{"message":"Fix
resolve index API to not throw 500 when encountering
`no_such_remote_cluster_exception` (#204802)\n\n## Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/197747.\r\n\r\nUpdates the
`/internal/index-pattern-management/resolve_index/{query}`\r\nroute to
properly handle `no_such_remote_cluster_exception` and return\r\n`404`
rather than `500` server error.\r\n\r\nAdds unit tests for the route
handler.\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following
conditions. \r\n\r\nReviewers should verify this PR satisfies this list
as well.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Julia Rechkunova
<[email protected]>","sha":"0952f6e0f52771b3503c9ddd2afd793d8ed86709"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/204802","number":204802,"mergeCommit":{"message":"Fix
resolve index API to not throw 500 when encountering
`no_such_remote_cluster_exception` (#204802)\n\n## Summary\r\n\r\nFixes
https://github.com/elastic/kibana/issues/197747.\r\n\r\nUpdates the
`/internal/index-pattern-management/resolve_index/{query}`\r\nroute to
properly handle `no_such_remote_cluster_exception` and return\r\n`404`
rather than `500` server error.\r\n\r\nAdds unit tests for the route
handler.\r\n\r\n### Checklist\r\n\r\nCheck the PR satisfies following
conditions. \r\n\r\nReviewers should verify this PR satisfies this list
as well.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: Julia Rechkunova
<[email protected]>","sha":"0952f6e0f52771b3503c9ddd2afd793d8ed86709"}}]}]
BACKPORT-->

Co-authored-by: Lukas Olson <[email protected]>
  • Loading branch information
kibanamachine and lukasolson authored Dec 19, 2024
1 parent 9b2b922 commit 4f85910
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 2 deletions.
243 changes: 243 additions & 0 deletions src/plugins/data_view_management/server/routes/resolve_index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { MockedKeys } from '@kbn/utility-types-jest';
import { CoreSetup, RequestHandlerContext } from '@kbn/core/server';
import { coreMock, httpServerMock } from '@kbn/core/server/mocks';
import { registerResolveIndexRoute } from './resolve_index';

const mockResponseIndices = {
indices: [
{
name: 'kibana_sample_data_logs',
attributes: ['open'],
},
],
aliases: [],
data_streams: [],
};

const mockResponseEmpty = {
indices: [],
aliases: [],
data_streams: [],
};

const mockError403 = {
meta: {
body: {
error: {
root_cause: [
{
type: 'no_such_remote_cluster_exception',
reason: 'no such remote cluster: [cluster1]',
},
],
type: 'security_exception',
reason:
'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]',
caused_by: {
type: 'no_such_remote_cluster_exception',
reason: 'no such remote cluster: [cluster1]',
},
},
status: 403,
},
statusCode: 403,
},
};

const mockError404 = {
meta: {
body: {
error: {
root_cause: [
{
type: 'index_not_found_exception',
reason: 'no such index [asdf]',
'resource.type': 'index_or_alias',
'resource.id': 'asdf',
index_uuid: '_na_',
index: 'asdf',
},
],
type: 'index_not_found_exception',
reason: 'no such index [asdf]',
'resource.type': 'index_or_alias',
'resource.id': 'asdf',
index_uuid: '_na_',
index: 'asdf',
},
status: 404,
},
statusCode: 404,
},
};

describe('resolve_index route', () => {
let mockCoreSetup: MockedKeys<CoreSetup>;

beforeEach(() => {
mockCoreSetup = coreMock.createSetup();
});

it('handler calls /_resolve/index with the given request', async () => {
const mockClient = {
indices: {
resolveIndex: jest.fn().mockResolvedValue(mockResponseIndices),
},
};
const mockContext = {
core: {
elasticsearch: { client: { asCurrentUser: mockClient } },
},
};
const mockRequest = httpServerMock.createKibanaRequest({
params: {
query: 'kibana_sample_data_logs',
},
});
const mockResponse = httpServerMock.createResponseFactory();

registerResolveIndexRoute(mockCoreSetup.http.createRouter());

const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const handler = mockRouter.get.mock.calls[0][1];
await handler(mockContext as unknown as RequestHandlerContext, mockRequest, mockResponse);

expect(mockClient.indices.resolveIndex.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"expand_wildcards": "open",
"name": "kibana_sample_data_logs",
}
`);

expect(mockResponse.ok).toBeCalled();
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: mockResponseIndices });
});

it('should return 200 for a search for indices with wildcard', async () => {
const mockClient = {
indices: {
resolveIndex: jest.fn().mockResolvedValue(mockResponseEmpty),
},
};
const mockContext = {
core: {
elasticsearch: { client: { asCurrentUser: mockClient } },
},
};
const mockRequest = httpServerMock.createKibanaRequest({
params: {
query: 'asdf*',
},
});
const mockResponse = httpServerMock.createResponseFactory();

registerResolveIndexRoute(mockCoreSetup.http.createRouter());

const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const handler = mockRouter.get.mock.calls[0][1];
await handler(mockContext as unknown as RequestHandlerContext, mockRequest, mockResponse);

expect(mockClient.indices.resolveIndex.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"expand_wildcards": "open",
"name": "asdf*",
}
`);

expect(mockResponse.ok).toBeCalled();
expect(mockResponse.ok.mock.calls[0][0]).toEqual({ body: mockResponseEmpty });
});

it('returns 404 when hitting a 403 from Elasticsearch', async () => {
const mockClient = {
indices: {
resolveIndex: jest.fn().mockRejectedValue(mockError403),
},
};
const mockContext = {
core: {
elasticsearch: { client: { asCurrentUser: mockClient } },
},
};
const mockRequest = httpServerMock.createKibanaRequest({
params: {
query: 'cluster1:filebeat-*,cluster2:filebeat-*',
},
});
const mockResponse = httpServerMock.createResponseFactory();

registerResolveIndexRoute(mockCoreSetup.http.createRouter());

const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const handler = mockRouter.get.mock.calls[0][1];

await handler(mockContext as unknown as RequestHandlerContext, mockRequest, mockResponse);

expect(mockClient.indices.resolveIndex.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"expand_wildcards": "open",
"name": "cluster1:filebeat-*,cluster2:filebeat-*",
}
`);

expect(mockResponse.notFound).toBeCalled();
expect(mockResponse.notFound.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"body": Object {
"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]",
},
}
`);
});

it('returns 404 when hitting a 404 from Elasticsearch', async () => {
const mockClient = {
indices: {
resolveIndex: jest.fn().mockRejectedValue(mockError404),
},
};
const mockContext = {
core: {
elasticsearch: { client: { asCurrentUser: mockClient } },
},
};
const mockRequest = httpServerMock.createKibanaRequest({
params: {
query: 'asdf',
},
});
const mockResponse = httpServerMock.createResponseFactory();

registerResolveIndexRoute(mockCoreSetup.http.createRouter());

const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const handler = mockRouter.get.mock.calls[0][1];

await handler(mockContext as unknown as RequestHandlerContext, mockRequest, mockResponse);

expect(mockClient.indices.resolveIndex.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"expand_wildcards": "open",
"name": "asdf",
}
`);

expect(mockResponse.notFound).toBeCalled();
expect(mockResponse.notFound.mock.calls[0][0]).toMatchInlineSnapshot(`
Object {
"body": Object {
"message": "no such index [asdf]",
},
}
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ export function registerResolveIndexRoute(router: IRouter): void {
});
return res.ok({ body });
} catch (e) {
if (e?.meta.statusCode === 404) {
// 403: no_such_remote_cluster_exception
// 404: index_not_found_exception
if ([403, 404].includes(e?.meta.statusCode)) {
return res.notFound({ body: { message: e.meta?.body?.error?.reason } });
} else {
throw getKbnServerError(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,18 @@ export default function ({ getService }: FtrProviderContext) {
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(200));

it('should return 404 for an exact match index', () =>
it('should return 404 when no indices match', () =>
supertest
.get(`/internal/index-pattern-management/resolve_index/test`)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(404));

it('should return 404 when cluster is not found', () =>
supertest
.get(
`/internal/index-pattern-management/resolve_index/cluster1:filebeat-*,cluster2:filebeat-*`
)
.set(X_ELASTIC_INTERNAL_ORIGIN_REQUEST, 'kibana')
.expect(404));
});
}

0 comments on commit 4f85910

Please sign in to comment.