diff --git a/x-pack/plugins/cross_cluster_replication/README.md b/x-pack/plugins/cross_cluster_replication/README.md index 9c500950c20d..3522499c7356 100644 --- a/x-pack/plugins/cross_cluster_replication/README.md +++ b/x-pack/plugins/cross_cluster_replication/README.md @@ -4,10 +4,11 @@ You can run a local cluster and simulate a remote cluster within a single Kibana directory. -1. Start your "local" cluster by running `yarn es snapshot --license=trial` and `yarn start` to start Kibana. -2. Start your "remote" cluster by running `yarn es snapshot --license=trial -E cluster.name=europe -E transport.port=9400` in a separate terminal tab. -3. Index a document into your remote cluster by running `curl -X PUT http://elastic:changeme@localhost:9201/my-leader-index --data '{"settings":{"number_of_shards":1,"soft_deletes.enabled":true}}' --header "Content-Type: application/json"`. -Note that these settings are required for testing auto-follow pattern conflicts errors (see below). +1. Ensure Kibana isn't running so it doesn't load up any data into your cluster. Run `yarn es snapshot --license=trial` to install a fresh snapshot. Wait for ES to finish setting up. +2. Create a "remote" copy of your ES snapshot by running: `cp -R .es/8.0.0 .es/8.0.0-2`. +4. Start your "remote" cluster by running `.es/8.0.0-2/bin/elasticsearch -E cluster.name=europe -E transport.port=9400`. +4. Run `yarn start` to start Kibana. +5. Index a document into your remote cluster by running `curl -X PUT http://elastic:changeme@localhost:9201/my-leader-index --data '{"settings":{"number_of_shards":1,"soft_deletes.enabled":true}}' --header "Content-Type: application/json"`. Note that these settings are required for testing auto-follow pattern conflicts errors (see below). Now you can create follower indices and auto-follow patterns to replicate the `my-leader-index` index on the remote cluster that's available at `127.0.0.1:9400`. diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/section_error.js b/x-pack/plugins/cross_cluster_replication/public/app/components/section_error.js index 1bf54d32f934..c12c4dfb5a70 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/section_error.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/section_error.js @@ -11,21 +11,19 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; export function SectionError(props) { const { title, error, ...rest } = props; const data = error.body ? error.body : error; - const { - error: errorString, - attributes, // wrapEsError() on the server add a "cause" array - message, - } = data; + const { error: errorString, attributes, message } = data; return (
{message || errorString}
- {attributes && attributes.cause && ( + {attributes?.error?.root_cause && (
    - {attributes.cause.map((message, i) => ( -
  • {message}
  • + {attributes.error.root_cause.map(({ type, reason }, i) => ( +
  • + {type}: {reason} +
  • ))}
diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js index 67b1576e9859..d060fb83832c 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/auto_follow_pattern_edit/auto_follow_pattern_edit.js @@ -79,24 +79,24 @@ export class AutoFollowPatternEdit extends PureComponent { params: { id: name }, }, } = this.props; + const title = i18n.translate( 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorTitle', { defaultMessage: 'Error loading auto-follow pattern', } ); + const errorMessage = - error.status === 404 + error.body.statusCode === 404 ? { - data: { - error: i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorMessage', - { - defaultMessage: `The auto-follow pattern '{name}' does not exist.`, - values: { name }, - } - ), - }, + error: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternEditForm.loadingErrorMessage', + { + defaultMessage: `The auto-follow pattern '{name}' does not exist.`, + values: { name }, + } + ), } : error; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js index 8d6e47d4004b..41b09a398b1f 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/follower_index_edit/follower_index_edit.js @@ -121,24 +121,24 @@ export class FollowerIndexEdit extends PureComponent { params: { id: name }, }, } = this.props; + const title = i18n.translate( 'xpack.crossClusterReplication.followerIndexEditForm.loadingErrorTitle', { defaultMessage: 'Error loading follower index', } ); + const errorMessage = - error.status === 404 + error.body.statusCode === 404 ? { - data: { - error: i18n.translate( - 'xpack.crossClusterReplication.followerIndexEditForm.loadingErrorMessage', - { - defaultMessage: `The follower index '{name}' does not exist.`, - values: { name }, - } - ), - }, + error: i18n.translate( + 'xpack.crossClusterReplication.followerIndexEditForm.loadingErrorMessage', + { + defaultMessage: `The follower index '{name}' does not exist.`, + values: { name }, + } + ), } : error; diff --git a/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.ts b/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.ts deleted file mode 100644 index 67bdc0e99821..000000000000 --- a/x-pack/plugins/cross_cluster_replication/server/client/elasticsearch_ccr.ts +++ /dev/null @@ -1,199 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => { - const ca = components.clientAction.factory; - - Client.prototype.ccr = components.clientAction.namespaceFactory(); - const ccr = Client.prototype.ccr.prototype; - - ccr.permissions = ca({ - urls: [ - { - fmt: '/_security/user/_has_privileges', - }, - ], - needBody: true, - method: 'POST', - }); - - ccr.autoFollowPatterns = ca({ - urls: [ - { - fmt: '/_ccr/auto_follow', - }, - ], - method: 'GET', - }); - - ccr.autoFollowPattern = ca({ - urls: [ - { - fmt: '/_ccr/auto_follow/<%=id%>', - req: { - id: { - type: 'string', - }, - }, - }, - ], - method: 'GET', - }); - - ccr.saveAutoFollowPattern = ca({ - urls: [ - { - fmt: '/_ccr/auto_follow/<%=id%>', - req: { - id: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'PUT', - }); - - ccr.deleteAutoFollowPattern = ca({ - urls: [ - { - fmt: '/_ccr/auto_follow/<%=id%>', - req: { - id: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'DELETE', - }); - - ccr.pauseAutoFollowPattern = ca({ - urls: [ - { - fmt: '/_ccr/auto_follow/<%=id%>/pause', - req: { - id: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ccr.resumeAutoFollowPattern = ca({ - urls: [ - { - fmt: '/_ccr/auto_follow/<%=id%>/resume', - req: { - id: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ccr.info = ca({ - urls: [ - { - fmt: '/<%=id%>/_ccr/info', - req: { - id: { - type: 'string', - }, - }, - }, - ], - method: 'GET', - }); - - ccr.stats = ca({ - urls: [ - { - fmt: '/_ccr/stats', - }, - ], - method: 'GET', - }); - - ccr.followerIndexStats = ca({ - urls: [ - { - fmt: '/<%=id%>/_ccr/stats', - req: { - id: { - type: 'string', - }, - }, - }, - ], - method: 'GET', - }); - - ccr.saveFollowerIndex = ca({ - urls: [ - { - fmt: '/<%=name%>/_ccr/follow', - req: { - name: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'PUT', - }); - - ccr.pauseFollowerIndex = ca({ - urls: [ - { - fmt: '/<%=id%>/_ccr/pause_follow', - req: { - id: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); - - ccr.resumeFollowerIndex = ca({ - urls: [ - { - fmt: '/<%=id%>/_ccr/resume_follow', - req: { - id: { - type: 'string', - }, - }, - }, - ], - needBody: true, - method: 'POST', - }); - - ccr.unfollowLeaderIndex = ca({ - urls: [ - { - fmt: '/<%=id%>/_ccr/unfollow', - req: { - id: { - type: 'string', - }, - }, - }, - ], - method: 'POST', - }); -}; diff --git a/x-pack/plugins/cross_cluster_replication/server/lib/format_es_error.ts b/x-pack/plugins/cross_cluster_replication/server/lib/format_es_error.ts deleted file mode 100644 index a20ec5c6fd51..000000000000 --- a/x-pack/plugins/cross_cluster_replication/server/lib/format_es_error.ts +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -function extractCausedByChain( - causedBy: Record = {}, - accumulator: string[] = [] -): string[] { - const { reason, caused_by } = causedBy; // eslint-disable-line @typescript-eslint/naming-convention - - if (reason) { - accumulator.push(reason); - } - - if (caused_by) { - return extractCausedByChain(caused_by, accumulator); - } - - return accumulator; -} - -/** - * Wraps an error thrown by the ES JS client into a Boom error response and returns it - * - * @param err Object Error thrown by ES JS client - * @param statusCodeToMessageMap Object Optional map of HTTP status codes => error messages - */ -export function wrapEsError( - err: any, - statusCodeToMessageMap: Record = {} -): { message: string; body?: { cause?: string[] }; statusCode: number } { - const { statusCode, response } = err; - - const { - error: { - root_cause = [], // eslint-disable-line @typescript-eslint/naming-convention - caused_by = undefined, // eslint-disable-line @typescript-eslint/naming-convention - } = {}, - } = JSON.parse(response); - - // If no custom message if specified for the error's status code, just - // wrap the error as a Boom error response and return it - if (!statusCodeToMessageMap[statusCode]) { - // The caused_by chain has the most information so use that if it's available. If not then - // settle for the root_cause. - const causedByChain = extractCausedByChain(caused_by); - const defaultCause = root_cause.length ? extractCausedByChain(root_cause[0]) : undefined; - - return { - message: err.message, - statusCode, - body: { - cause: causedByChain.length ? causedByChain : defaultCause, - }, - }; - } - - // Otherwise, use the custom message to create a Boom error response and - // return it - const message = statusCodeToMessageMap[statusCode]; - return { message, statusCode }; -} - -export function formatEsError(err: any): any { - const { statusCode, message, body } = wrapEsError(err); - return { - statusCode, - body: { - message, - attributes: { - cause: body?.cause, - }, - }, - }; -} diff --git a/x-pack/plugins/cross_cluster_replication/server/plugin.ts b/x-pack/plugins/cross_cluster_replication/server/plugin.ts index e3a1de1dbfab..c371602df249 100644 --- a/x-pack/plugins/cross_cluster_replication/server/plugin.ts +++ b/x-pack/plugins/cross_cluster_replication/server/plugin.ts @@ -10,7 +10,6 @@ import { first } from 'rxjs/operators'; import { CoreSetup, CoreStart, - ILegacyCustomClusterClient, Plugin, Logger, PluginInitializerContext, @@ -19,20 +18,12 @@ import { import { Index } from '../../index_management/server'; import { PLUGIN } from '../common/constants'; -import { SetupDependencies, StartDependencies, CcrRequestHandlerContext } from './types'; +import { SetupDependencies, StartDependencies } from './types'; import { registerApiRoutes } from './routes'; -import { elasticsearchJsPlugin } from './client/elasticsearch_ccr'; import { CrossClusterReplicationConfig } from './config'; -import { License, isEsError } from './shared_imports'; -import { formatEsError } from './lib/format_es_error'; - -async function getCustomEsClient(getStartServices: CoreSetup['getStartServices']) { - const [core] = await getStartServices(); - // Extend the elasticsearchJs client with additional endpoints. - const esClientConfig = { plugins: [elasticsearchJsPlugin] }; - return core.elasticsearch.legacy.createClient('crossClusterReplication', esClientConfig); -} +import { License, handleEsError } from './shared_imports'; +// TODO replace deprecated ES client after Index Management is updated const ccrDataEnricher = async (indicesList: Index[], callWithRequest: LegacyAPICaller) => { if (!indicesList?.length) { return indicesList; @@ -66,7 +57,6 @@ export class CrossClusterReplicationServerPlugin implements Plugin; private readonly license: License; private readonly logger: Logger; - private ccrEsClient?: ILegacyCustomClusterClient; constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -114,22 +104,11 @@ export class CrossClusterReplicationServerPlugin implements Plugin( - 'crossClusterReplication', - async (ctx, request) => { - this.ccrEsClient = this.ccrEsClient ?? (await getCustomEsClient(getStartServices)); - return { - client: this.ccrEsClient.asScoped(request), - }; - } - ); - registerApiRoutes({ router: http.createRouter(), license: this.license, lib: { - isEsError, - formatEsError, + handleEsError, }, }); } @@ -142,9 +121,5 @@ export class CrossClusterReplicationServerPlugin implements Plugin { registerCreateRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,8 +33,10 @@ describe('[CCR API] Create auto-follow pattern', () => { it('should throw a 409 conflict error if id already exists', async () => { const routeContextMock = mockRouteContext({ - // Fail the uniqueness check. - callAsCurrentUser: jest.fn().mockResolvedValueOnce(true), + ccr: { + // Fail the uniqueness check. + getAutoFollowPattern: jest.fn().mockResolvedValueOnce(true), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -54,11 +52,11 @@ describe('[CCR API] Create auto-follow pattern', () => { it('should return 200 status when the id does not exist', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() + ccr: { // Pass the uniqueness check. - .mockRejectedValueOnce({ statusCode: 404 }) - .mockResolvedValueOnce(true), + getAutoFollowPattern: jest.fn().mockRejectedValueOnce({ statusCode: 404 }), + putAutoFollowPattern: jest.fn().mockResolvedValueOnce(true), + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts index 608e369828de..dd1481b45f87 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_create_route.ts @@ -17,7 +17,7 @@ import { RouteDependencies } from '../../../types'; export const registerCreateRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const bodySchema = schema.object({ id: schema.string(), @@ -34,6 +34,7 @@ export const registerCreateRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id, ...rest } = request.body; const body = serializeAutoFollowPattern(rest as AutoFollowPattern); @@ -42,36 +43,29 @@ export const registerCreateRoute = ({ * the same id does not exist. */ try { - await context.crossClusterReplication!.client.callAsCurrentUser('ccr.autoFollowPattern', { - id, - }); + await client.asCurrentUser.ccr.getAutoFollowPattern({ name: id }); + // If we get here it means that an auto-follow pattern with the same id exists return response.conflict({ body: `An auto-follow pattern with the name "${id}" already exists.`, }); - } catch (err) { - if (err.statusCode !== 404) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + } catch (error) { + if (error.statusCode !== 404) { + return handleEsError({ error, response }); } } try { - return response.ok({ - body: await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.saveAutoFollowPattern', - { id, body } - ), + const { body: responseBody } = await client.asCurrentUser.ccr.putAutoFollowPattern({ + name: id, + body, }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + + return response.ok({ + body: responseBody, + }); + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.test.ts index 617ef3b5af87..6469b4a2ce57 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense, mockError } from '../test_lib'; import { registerDeleteRoute } from './register_delete_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Delete auto-follow pattern(s)', () => { registerDeleteRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,7 +33,9 @@ describe('[CCR API] Delete auto-follow pattern(s)', () => { it('deletes a single item', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + ccr: { + deleteAutoFollowPattern: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -51,11 +49,13 @@ describe('[CCR API] Delete auto-follow pattern(s)', () => { it('deletes multiple items', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }), + ccr: { + deleteAutoFollowPattern: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -69,10 +69,12 @@ describe('[CCR API] Delete auto-follow pattern(s)', () => { it('returns partial errors', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockRejectedValueOnce({ response: { error: {} } }), + ccr: { + deleteAutoFollowPattern: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce(mockError), + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts index 868e847bd6bf..63ade6d1bd07 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_delete_route.ts @@ -16,7 +16,7 @@ import { RouteDependencies } from '../../../types'; export const registerDeleteRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string(), @@ -30,29 +30,22 @@ export const registerDeleteRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; const ids = id.split(','); const itemsDeleted: string[] = []; const errors: Array<{ id: string; error: any }> = []; - const formatError = (err: any) => { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - return response.customError({ statusCode: 500, body: err }); - }; - await Promise.all( ids.map((_id) => - context - .crossClusterReplication!.client.callAsCurrentUser('ccr.deleteAutoFollowPattern', { - id: _id, + client.asCurrentUser.ccr + .deleteAutoFollowPattern({ + name: _id, }) .then(() => itemsDeleted.push(_id)) - .catch((err: any) => { - errors.push({ id: _id, error: formatError(err) }); + .catch((error: any) => { + errors.push({ id: _id, error: handleEsError({ error, response }) }); }) ) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.test.ts index ec008e1507c3..7b694cfe77ef 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense } from '../test_lib'; import { registerFetchRoute } from './register_fetch_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Fetch all auto-follow patterns', () => { registerFetchRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,21 +33,25 @@ describe('[CCR API] Fetch all auto-follow patterns', () => { it('deserializes the response from Elasticsearch', async () => { const ccrAutoFollowPatternResponseMock = { - patterns: [ - { - name: 'autoFollowPattern', - pattern: { - active: true, - remote_cluster: 'remoteCluster', - leader_index_patterns: ['leader*'], - follow_index_pattern: 'follow', + body: { + patterns: [ + { + name: 'autoFollowPattern', + pattern: { + active: true, + remote_cluster: 'remoteCluster', + leader_index_patterns: ['leader*'], + follow_index_pattern: 'follow', + }, }, - }, - ], + ], + }, }; const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest.fn().mockResolvedValueOnce(ccrAutoFollowPatternResponseMock), + ccr: { + getAutoFollowPattern: jest.fn().mockResolvedValueOnce(ccrAutoFollowPatternResponseMock), + }, }); const request = httpServerMock.createKibanaRequest(); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts index 632fdb03dd58..98ce37d1054b 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_fetch_route.ts @@ -15,7 +15,7 @@ import { RouteDependencies } from '../../../types'; export const registerFetchRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { router.get( { @@ -23,21 +23,20 @@ export const registerFetchRoute = ({ validate: false, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; + try { - const result = await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.autoFollowPatterns' - ); + const { + body: { patterns }, + } = await client.asCurrentUser.ccr.getAutoFollowPattern(); return response.ok({ body: { - patterns: deserializeListAutoFollowPatterns(result.patterns), + // @ts-expect-error Once #98266 is merged, test this again. + patterns: deserializeListAutoFollowPatterns(patterns), }, }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.test.ts index d76c77752b11..61fdd6e57ab7 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense } from '../test_lib'; import { registerGetRoute } from './register_get_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Get one auto-follow pattern', () => { registerGetRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,21 +33,25 @@ describe('[CCR API] Get one auto-follow pattern', () => { it('should return a single resource even though ES returns an array with 1 item', async () => { const ccrAutoFollowPatternResponseMock = { - patterns: [ - { - name: 'autoFollowPattern', - pattern: { - active: true, - remote_cluster: 'remoteCluster', - leader_index_patterns: ['leader*'], - follow_index_pattern: 'follow', + body: { + patterns: [ + { + name: 'autoFollowPattern', + pattern: { + active: true, + remote_cluster: 'remoteCluster', + leader_index_patterns: ['leader*'], + follow_index_pattern: 'follow', + }, }, - }, - ], + ], + }, }; const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest.fn().mockResolvedValueOnce(ccrAutoFollowPatternResponseMock), + ccr: { + getAutoFollowPattern: jest.fn().mockResolvedValueOnce(ccrAutoFollowPatternResponseMock), + }, }); const request = httpServerMock.createKibanaRequest(); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts index 3529fe313dbb..bc452982df63 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_get_route.ts @@ -17,7 +17,7 @@ import { RouteDependencies } from '../../../types'; export const registerGetRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string(), @@ -31,24 +31,22 @@ export const registerGetRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; try { - const result = await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.autoFollowPattern', - { id } - ); - const autoFollowPattern = result.patterns[0]; + const result = await client.asCurrentUser.ccr.getAutoFollowPattern({ + name: id, + }); + + const autoFollowPattern = result.body.patterns[0]; return response.ok({ + // @ts-expect-error Once #98266 is merged, test this again. body: deserializeAutoFollowPattern(autoFollowPattern), }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.test.ts index 7a90e4086cbd..f74da91047ce 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense, mockError } from '../test_lib'; import { registerPauseRoute } from './register_pause_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Pause auto-follow pattern(s)', () => { registerPauseRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,7 +33,9 @@ describe('[CCR API] Pause auto-follow pattern(s)', () => { it('pauses a single item', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + ccr: { + pauseAutoFollowPattern: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -51,11 +49,13 @@ describe('[CCR API] Pause auto-follow pattern(s)', () => { it('pauses multiple items', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }), + ccr: { + pauseAutoFollowPattern: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -69,10 +69,12 @@ describe('[CCR API] Pause auto-follow pattern(s)', () => { it('returns partial errors', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockRejectedValueOnce({ response: { error: {} } }), + ccr: { + pauseAutoFollowPattern: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce(mockError), + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts index a9aa94cdf4f2..1428e33dee66 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_pause_route.ts @@ -15,7 +15,7 @@ import { RouteDependencies } from '../../../types'; export const registerPauseRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string(), @@ -29,29 +29,22 @@ export const registerPauseRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; const ids = id.split(','); const itemsPaused: string[] = []; const errors: Array<{ id: string; error: any }> = []; - const formatError = (err: any) => { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - return response.customError({ statusCode: 500, body: err }); - }; - await Promise.all( ids.map((_id) => - context - .crossClusterReplication!.client.callAsCurrentUser('ccr.pauseAutoFollowPattern', { - id: _id, + client.asCurrentUser.ccr + .pauseAutoFollowPattern({ + name: _id, }) .then(() => itemsPaused.push(_id)) - .catch((err) => { - errors.push({ id: _id, error: formatError(err) }); + .catch((error) => { + errors.push({ id: _id, error: handleEsError({ error, response }) }); }) ) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.test.ts index 904642d292e3..1837f1c17b38 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense, mockError } from '../test_lib'; import { registerResumeRoute } from './register_resume_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Resume auto-follow pattern(s)', () => { registerResumeRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,7 +33,9 @@ describe('[CCR API] Resume auto-follow pattern(s)', () => { it('resumes a single item', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + ccr: { + resumeAutoFollowPattern: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -51,11 +49,13 @@ describe('[CCR API] Resume auto-follow pattern(s)', () => { it('resumes multiple items', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }), + ccr: { + resumeAutoFollowPattern: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -69,10 +69,12 @@ describe('[CCR API] Resume auto-follow pattern(s)', () => { it('returns partial errors', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockRejectedValueOnce({ response: { error: {} } }), + ccr: { + resumeAutoFollowPattern: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce(mockError), + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts index 1c6396d0b350..912ecd689054 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_resume_route.ts @@ -15,7 +15,7 @@ import { RouteDependencies } from '../../../types'; export const registerResumeRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string(), @@ -29,29 +29,22 @@ export const registerResumeRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; const ids = id.split(','); const itemsResumed: string[] = []; const errors: Array<{ id: string; error: any }> = []; - const formatError = (err: any) => { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - return response.customError({ statusCode: 500, body: err }); - }; - await Promise.all( ids.map((_id: string) => - context - .crossClusterReplication!.client.callAsCurrentUser('ccr.resumeAutoFollowPattern', { - id: _id, + client.asCurrentUser.ccr + .resumeAutoFollowPattern({ + name: _id, }) .then(() => itemsResumed.push(_id)) - .catch((err) => { - errors.push({ id: _id, error: formatError(err) }); + .catch((error) => { + errors.push({ id: _id, error: handleEsError({ error, response }) }); }) ) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.test.ts index 66f4ddb950ef..5ada82049632 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense } from '../test_lib'; import { registerUpdateRoute } from './register_update_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Update auto-follow pattern', () => { registerUpdateRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,8 +33,10 @@ describe('[CCR API] Update auto-follow pattern', () => { it('should serialize the payload before sending it to Elasticsearch', async () => { const routeContextMock = mockRouteContext({ - // Just echo back what we send so we can inspect it. - callAsCurrentUser: jest.fn().mockImplementation((endpoint, payload) => payload), + ccr: { + // Just echo back what we send so we can inspect it. + putAutoFollowPattern: jest.fn().mockImplementation((payload) => ({ body: payload })), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -53,7 +51,7 @@ describe('[CCR API] Update auto-follow pattern', () => { const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); expect(response.payload).toEqual({ - id: 'foo', + name: 'foo', body: { remote_cluster: 'bar1', leader_index_patterns: ['bar2'], diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts index a3e7c3544ca3..81aaa59725e3 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/auto_follow_pattern/register_update_route.ts @@ -17,7 +17,7 @@ import { RouteDependencies } from '../../../types'; export const registerUpdateRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string(), @@ -39,22 +39,21 @@ export const registerUpdateRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; const body = serializeAutoFollowPattern(request.body as AutoFollowPattern); try { - return response.ok({ - body: await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.saveAutoFollowPattern', - { id, body } - ), + const { body: responseBody } = await client.asCurrentUser.ccr.putAutoFollowPattern({ + name: id, + body, }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + + return response.ok({ + body: responseBody, + }); + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts index 130adb2e0b98..600924e14503 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_permissions_route.ts @@ -14,7 +14,7 @@ import { RouteDependencies } from '../../../types'; export const registerPermissionsRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { router.get( { @@ -22,6 +22,8 @@ export const registerPermissionsRoute = ({ validate: false, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; + if (!license.isEsSecurityEnabled) { // If security has been disabled in elasticsearch.yml. we'll just let the user use CCR // because permissions are irrelevant. @@ -35,9 +37,8 @@ export const registerPermissionsRoute = ({ try { const { - has_all_requested: hasPermission, - cluster, - } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.permissions', { + body: { has_all_requested: hasPermission, cluster }, + } = await client.asCurrentUser.security.hasPrivileges({ body: { cluster: ['manage', 'manage_ccr'], }, @@ -59,12 +60,8 @@ export const registerPermissionsRoute = ({ missingClusterPrivileges, }, }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts index 6636f6b1c5ac..fa8f792afc41 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/cross_cluster_replication/register_stats_route.ts @@ -15,7 +15,7 @@ import { RouteDependencies } from '../../../types'; export const registerStatsRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { router.get( { @@ -23,20 +23,19 @@ export const registerStatsRoute = ({ validate: false, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; + try { const { - auto_follow_stats: autoFollowStats, - } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.stats'); + body: { auto_follow_stats: autoFollowStats }, + } = await client.asCurrentUser.ccr.stats(); return response.ok({ + // @ts-expect-error Once #98266 is merged, test this again. body: deserializeAutoFollowStats(autoFollowStats), }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.test.ts index 44cb62776856..8de168e8a17d 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense } from '../test_lib'; import { registerCreateRoute } from './register_create_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Create follower index', () => { registerCreateRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,7 +33,9 @@ describe('[CCR API] Create follower index', () => { it('should return 200 status when follower index is created', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + ccr: { + follow: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts index f44e5a749baa..9ddbf76cceed 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_create_route.ts @@ -18,7 +18,7 @@ import { RouteDependencies } from '../../../types'; export const registerCreateRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const bodySchema = schema.object({ name: schema.string(), @@ -44,22 +44,21 @@ export const registerCreateRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { name, ...rest } = request.body; const body = removeEmptyFields(serializeFollowerIndex(rest as FollowerIndex)); try { - return response.ok({ - body: await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.saveFollowerIndex', - { name, body } - ), + const { body: responseBody } = await client.asCurrentUser.ccr.follow({ + index: name, + body, }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + + return response.ok({ + body: responseBody, + }); + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.test.ts index b668cea06f90..e4063e1e64df 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense } from '../test_lib'; import { registerFetchRoute } from './register_fetch_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Fetch all follower indices', () => { registerFetchRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,73 +33,77 @@ describe('[CCR API] Fetch all follower indices', () => { it('deserializes the response from Elasticsearch', async () => { const ccrInfoMockResponse = { - follower_indices: [ - { - follower_index: 'followerIndexName', - remote_cluster: 'remoteCluster', - leader_index: 'leaderIndex', - status: 'active', - parameters: { - max_read_request_operation_count: 1, - max_outstanding_read_requests: 1, - max_read_request_size: '1b', - max_write_request_operation_count: 1, - max_write_request_size: '1b', - max_outstanding_write_requests: 1, - max_write_buffer_count: 1, - max_write_buffer_size: '1b', - max_retry_delay: '1s', - read_poll_timeout: '1s', - }, - }, - ], - }; - - // These stats correlate to the above follower indices. - const ccrStatsMockResponse = { - follow_stats: { - indices: [ + body: { + follower_indices: [ { - index: 'followerIndexName', - shards: [ - { - shard_id: 1, - leader_index: 'leaderIndex', - leader_global_checkpoint: 1, - leader_max_seq_no: 1, - follower_global_checkpoint: 1, - follower_max_seq_no: 1, - last_requested_seq_no: 1, - outstanding_read_requests: 1, - outstanding_write_requests: 1, - write_buffer_operation_count: 1, - write_buffer_size_in_bytes: 1, - follower_mapping_version: 1, - follower_settings_version: 1, - total_read_time_millis: 1, - total_read_remote_exec_time_millis: 1, - successful_read_requests: 1, - failed_read_requests: 1, - operations_read: 1, - bytes_read: 1, - total_write_time_millis: 1, - successful_write_requests: 1, - failed_write_requests: 1, - operations_written: 1, - read_exceptions: 1, - time_since_last_read_millis: 1, - }, - ], + follower_index: 'followerIndexName', + remote_cluster: 'remoteCluster', + leader_index: 'leaderIndex', + status: 'active', + parameters: { + max_read_request_operation_count: 1, + max_outstanding_read_requests: 1, + max_read_request_size: '1b', + max_write_request_operation_count: 1, + max_write_request_size: '1b', + max_outstanding_write_requests: 1, + max_write_buffer_count: 1, + max_write_buffer_size: '1b', + max_retry_delay: '1s', + read_poll_timeout: '1s', + }, }, ], }, }; + // These stats correlate to the above follower indices. + const ccrStatsMockResponse = { + body: { + follow_stats: { + indices: [ + { + index: 'followerIndexName', + shards: [ + { + shard_id: 1, + leader_index: 'leaderIndex', + leader_global_checkpoint: 1, + leader_max_seq_no: 1, + follower_global_checkpoint: 1, + follower_max_seq_no: 1, + last_requested_seq_no: 1, + outstanding_read_requests: 1, + outstanding_write_requests: 1, + write_buffer_operation_count: 1, + write_buffer_size_in_bytes: 1, + follower_mapping_version: 1, + follower_settings_version: 1, + total_read_time_millis: 1, + total_read_remote_exec_time_millis: 1, + successful_read_requests: 1, + failed_read_requests: 1, + operations_read: 1, + bytes_read: 1, + total_write_time_millis: 1, + successful_write_requests: 1, + failed_write_requests: 1, + operations_written: 1, + read_exceptions: 1, + time_since_last_read_millis: 1, + }, + ], + }, + ], + }, + }, + }; + const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce(ccrInfoMockResponse) - .mockResolvedValueOnce(ccrStatsMockResponse), + ccr: { + followInfo: jest.fn().mockResolvedValueOnce(ccrInfoMockResponse), + stats: jest.fn().mockResolvedValueOnce(ccrStatsMockResponse), + }, }); const request = httpServerMock.createKibanaRequest(); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts index c72706cf5d10..b4d7ead75e83 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_fetch_route.ts @@ -15,7 +15,7 @@ import { RouteDependencies } from '../../../types'; export const registerFetchRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { router.get( { @@ -23,16 +23,18 @@ export const registerFetchRoute = ({ validate: false, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; + try { const { - follower_indices: followerIndices, - } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.info', { - id: '_all', - }); + body: { follower_indices: followerIndices }, + } = await client.asCurrentUser.ccr.followInfo({ index: '_all' }); const { - follow_stats: { indices: followerIndicesStats }, - } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.stats'); + body: { + follow_stats: { indices: followerIndicesStats }, + }, + } = await client.asCurrentUser.ccr.stats(); const followerIndicesStatsMap = followerIndicesStats.reduce((map: any, stats: any) => { map[stats.index] = stats; @@ -51,12 +53,8 @@ export const registerFetchRoute = ({ indices: deserializeListFollowerIndices(collatedFollowerIndices), }, }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.test.ts index f5d3ecdd09e8..493703ca6de2 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense } from '../test_lib'; import { registerGetRoute } from './register_get_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Get one follower index', () => { registerGetRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,71 +33,75 @@ describe('[CCR API] Get one follower index', () => { it('should return a single resource even though ES returns an array with 1 item', async () => { const ccrInfoMockResponse = { - follower_indices: [ - { - follower_index: 'followerIndexName', - remote_cluster: 'remoteCluster', - leader_index: 'leaderIndex', - status: 'active', - parameters: { - max_read_request_operation_count: 1, - max_outstanding_read_requests: 1, - max_read_request_size: '1b', - max_write_request_operation_count: 1, - max_write_request_size: '1b', - max_outstanding_write_requests: 1, - max_write_buffer_count: 1, - max_write_buffer_size: '1b', - max_retry_delay: '1s', - read_poll_timeout: '1s', + body: { + follower_indices: [ + { + follower_index: 'followerIndexName', + remote_cluster: 'remoteCluster', + leader_index: 'leaderIndex', + status: 'active', + parameters: { + max_read_request_operation_count: 1, + max_outstanding_read_requests: 1, + max_read_request_size: '1b', + max_write_request_operation_count: 1, + max_write_request_size: '1b', + max_outstanding_write_requests: 1, + max_write_buffer_count: 1, + max_write_buffer_size: '1b', + max_retry_delay: '1s', + read_poll_timeout: '1s', + }, }, - }, - ], + ], + }, }; // These stats correlate to the above follower indices. const ccrFollowerIndexStatsMockResponse = { - indices: [ - { - index: 'followerIndexName', - shards: [ - { - shard_id: 1, - leader_index: 'leaderIndex', - leader_global_checkpoint: 1, - leader_max_seq_no: 1, - follower_global_checkpoint: 1, - follower_max_seq_no: 1, - last_requested_seq_no: 1, - outstanding_read_requests: 1, - outstanding_write_requests: 1, - write_buffer_operation_count: 1, - write_buffer_size_in_bytes: 1, - follower_mapping_version: 1, - follower_settings_version: 1, - total_read_time_millis: 1, - total_read_remote_exec_time_millis: 1, - successful_read_requests: 1, - failed_read_requests: 1, - operations_read: 1, - bytes_read: 1, - total_write_time_millis: 1, - successful_write_requests: 1, - failed_write_requests: 1, - operations_written: 1, - read_exceptions: 1, - time_since_last_read_millis: 1, - }, - ], - }, - ], + body: { + indices: [ + { + index: 'followerIndexName', + shards: [ + { + shard_id: 1, + leader_index: 'leaderIndex', + leader_global_checkpoint: 1, + leader_max_seq_no: 1, + follower_global_checkpoint: 1, + follower_max_seq_no: 1, + last_requested_seq_no: 1, + outstanding_read_requests: 1, + outstanding_write_requests: 1, + write_buffer_operation_count: 1, + write_buffer_size_in_bytes: 1, + follower_mapping_version: 1, + follower_settings_version: 1, + total_read_time_millis: 1, + total_read_remote_exec_time_millis: 1, + successful_read_requests: 1, + failed_read_requests: 1, + operations_read: 1, + bytes_read: 1, + total_write_time_millis: 1, + successful_write_requests: 1, + failed_write_requests: 1, + operations_written: 1, + read_exceptions: 1, + time_since_last_read_millis: 1, + }, + ], + }, + ], + }, }; const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce(ccrInfoMockResponse) - .mockResolvedValueOnce(ccrFollowerIndexStatsMockResponse), + ccr: { + followInfo: jest.fn().mockResolvedValueOnce(ccrInfoMockResponse), + followStats: jest.fn().mockResolvedValueOnce(ccrFollowerIndexStatsMockResponse), + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts index cdcfff97e645..21cb9e08a616 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_get_route.ts @@ -16,7 +16,7 @@ import { RouteDependencies } from '../../../types'; export const registerGetRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string(), @@ -30,12 +30,13 @@ export const registerGetRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; try { const { - follower_indices: followerIndices, - } = await context.crossClusterReplication!.client.callAsCurrentUser('ccr.info', { id }); + body: { follower_indices: followerIndices }, + } = await client.asCurrentUser.ccr.followInfo({ index: id }); const followerIndexInfo = followerIndices && followerIndices[0]; @@ -48,31 +49,26 @@ export const registerGetRoute = ({ // If this follower is paused, skip call to ES stats api since it will return 404 if (followerIndexInfo.status === 'paused') { return response.ok({ + // @ts-expect-error Once #98266 is merged, test this again. body: deserializeFollowerIndex({ ...followerIndexInfo, }), }); } else { const { - indices: followerIndicesStats, - } = await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.followerIndexStats', - { id } - ); + body: { indices: followerIndicesStats }, + } = await client.asCurrentUser.ccr.followStats({ index: id }); return response.ok({ + // @ts-expect-error Once #98266 is merged, test this again. body: deserializeFollowerIndex({ ...followerIndexInfo, ...(followerIndicesStats ? followerIndicesStats[0] : {}), }), }); } - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.test.ts index 2c6854390ba5..920de2777ad2 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense, mockError } from '../test_lib'; import { registerPauseRoute } from './register_pause_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Pause follower index/indices', () => { registerPauseRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,7 +33,9 @@ describe('[CCR API] Pause follower index/indices', () => { it('pauses a single item', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + ccr: { + pauseFollow: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -51,11 +49,13 @@ describe('[CCR API] Pause follower index/indices', () => { it('pauses multiple items', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }), + ccr: { + pauseFollow: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -69,10 +69,12 @@ describe('[CCR API] Pause follower index/indices', () => { it('returns partial errors', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockRejectedValueOnce({ response: { error: {} } }), + ccr: { + pauseFollow: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce(mockError), + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts index 2e4e71278df0..6c888e0a6e8b 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_pause_route.ts @@ -15,7 +15,7 @@ import { RouteDependencies } from '../../../types'; export const registerPauseRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string() }); @@ -27,29 +27,20 @@ export const registerPauseRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; const ids = id.split(','); const itemsPaused: string[] = []; const errors: Array<{ id: string; error: any }> = []; - const formatError = (err: any) => { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - return response.customError({ statusCode: 500, body: err }); - }; - await Promise.all( ids.map((_id: string) => - context - .crossClusterReplication!.client.callAsCurrentUser('ccr.pauseFollowerIndex', { - id: _id, - }) + client.asCurrentUser.ccr + .pauseFollow({ index: _id }) .then(() => itemsPaused.push(_id)) - .catch((err) => { - errors.push({ id: _id, error: formatError(err) }); + .catch((error) => { + errors.push({ id: _id, error: handleEsError({ error, response }) }); }) ) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.test.ts index 3040bc822bf2..1cd54a898dc3 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense, mockError } from '../test_lib'; import { registerResumeRoute } from './register_resume_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Resume follower index/indices', () => { registerResumeRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,7 +33,9 @@ describe('[CCR API] Resume follower index/indices', () => { it('resumes a single item', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + ccr: { + resumeFollow: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -51,11 +49,13 @@ describe('[CCR API] Resume follower index/indices', () => { it('resumes multiple items', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }), + ccr: { + resumeFollow: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }) + .mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -69,10 +69,12 @@ describe('[CCR API] Resume follower index/indices', () => { it('returns partial errors', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockRejectedValueOnce({ response: { error: {} } }), + ccr: { + resumeFollow: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) + .mockRejectedValueOnce(mockError), + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts index 34f204f3b64b..206de7d62fb3 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_resume_route.ts @@ -15,7 +15,7 @@ import { RouteDependencies } from '../../../types'; export const registerResumeRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string() }); @@ -27,29 +27,20 @@ export const registerResumeRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; const ids = id.split(','); const itemsResumed: string[] = []; const errors: Array<{ id: string; error: any }> = []; - const formatError = (err: any) => { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - return response.customError({ statusCode: 500, body: err }); - }; - await Promise.all( ids.map((_id: string) => - context - .crossClusterReplication!.client.callAsCurrentUser('ccr.resumeFollowerIndex', { - id: _id, - }) + client.asCurrentUser.ccr + .resumeFollow({ index: _id }) .then(() => itemsResumed.push(_id)) - .catch((err) => { - errors.push({ id: _id, error: formatError(err) }); + .catch((error) => { + errors.push({ id: _id, error: handleEsError({ error, response }) }); }) ) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.test.ts index b4dfca304651..8ef9c84594a4 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.test.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.test.ts @@ -8,9 +8,8 @@ import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; -import { isEsError, License } from '../../../shared_imports'; -import { formatEsError } from '../../../lib/format_es_error'; -import { mockRouteContext } from '../test_lib'; +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense, mockError } from '../test_lib'; import { registerUnfollowRoute } from './register_unfollow_route'; const httpService = httpServiceMock.createSetupContract(); @@ -23,12 +22,9 @@ describe('[CCR API] Unfollow follower index/indices', () => { registerUnfollowRoute({ router, - license: { - guardApiRoute: (route: any) => route, - } as License, + license: mockLicense, lib: { - isEsError, - formatEsError, + handleEsError, }, }); @@ -37,12 +33,14 @@ describe('[CCR API] Unfollow follower index/indices', () => { it('unfollows a single item', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }), + ccr: { + pauseFollow: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + unfollow: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }, + indices: { + close: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + open: jest.fn().mockResolvedValueOnce({ acknowledge: true }), + }, }); const request = httpServerMock.createKibanaRequest({ @@ -56,23 +54,30 @@ describe('[CCR API] Unfollow follower index/indices', () => { it('unfollows multiple items', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - // a - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - // b - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - // c - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }), + ccr: { + pauseFollow: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) // a + .mockResolvedValueOnce({ acknowledge: true }) // b + .mockResolvedValueOnce({ acknowledge: true }), // c + unfollow: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) // a + .mockResolvedValueOnce({ acknowledge: true }) // b + .mockResolvedValueOnce({ acknowledge: true }), // c + }, + indices: { + close: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) // a + .mockResolvedValueOnce({ acknowledge: true }) // b + .mockResolvedValueOnce({ acknowledge: true }), // c + open: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) // a + .mockResolvedValueOnce({ acknowledge: true }) // b + .mockResolvedValueOnce({ acknowledge: true }), // c + }, }); const request = httpServerMock.createKibanaRequest({ @@ -86,16 +91,20 @@ describe('[CCR API] Unfollow follower index/indices', () => { it('returns partial errors', async () => { const routeContextMock = mockRouteContext({ - callAsCurrentUser: jest - .fn() - // a - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - .mockResolvedValueOnce({ acknowledge: true }) - // b - .mockResolvedValueOnce({ acknowledge: true }) - .mockRejectedValueOnce({ response: { error: {} } }), + ccr: { + pauseFollow: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) // a + .mockResolvedValueOnce({ acknowledge: true }), // B + unfollow: jest.fn().mockResolvedValueOnce({ acknowledge: true }), // a + }, + indices: { + close: jest + .fn() + .mockResolvedValueOnce({ acknowledge: true }) // a + .mockRejectedValueOnce(mockError), // b + open: jest.fn().mockResolvedValueOnce({ acknowledge: true }), // a + }, }); const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts index 848408e14662..e240a10df068 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_unfollow_route.ts @@ -15,7 +15,7 @@ import { RouteDependencies } from '../../../types'; export const registerUnfollowRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string() }); @@ -27,6 +27,7 @@ export const registerUnfollowRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; const ids = id.split(','); @@ -34,52 +35,34 @@ export const registerUnfollowRoute = ({ const itemsNotOpen: string[] = []; const errors: Array<{ id: string; error: any }> = []; - const formatError = (err: any) => { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - return response.customError({ statusCode: 500, body: err }); - }; - await Promise.all( ids.map(async (_id: string) => { try { // Try to pause follower, let it fail silently since it may already be paused try { - await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.pauseFollowerIndex', - { id: _id } - ); + await client.asCurrentUser.ccr.pauseFollow({ index: _id }); } catch (e) { // Swallow errors } // Close index - await context.crossClusterReplication!.client.callAsCurrentUser('indices.close', { - index: _id, - }); + await client.asCurrentUser.indices.close({ index: _id }); // Unfollow leader - await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.unfollowLeaderIndex', - { id: _id } - ); + await client.asCurrentUser.ccr.unfollow({ index: _id }); // Try to re-open the index, store failures in a separate array to surface warnings in the UI // This will allow users to query their index normally after unfollowing try { - await context.crossClusterReplication!.client.callAsCurrentUser('indices.open', { - index: _id, - }); + await client.asCurrentUser.indices.open({ index: _id }); } catch (e) { itemsNotOpen.push(_id); } // Push success itemsUnfollowed.push(_id); - } catch (err) { - errors.push({ id: _id, error: formatError(err) }); + } catch (error) { + errors.push({ id: _id, error: handleEsError({ error, response }) }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.test.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.test.ts new file mode 100644 index 000000000000..92b9c3dded08 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.test.ts @@ -0,0 +1,79 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { httpServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; + +import { handleEsError } from '../../../shared_imports'; +import { mockRouteContext, mockLicense } from '../test_lib'; +import { registerUpdateRoute } from './register_update_route'; + +const httpService = httpServiceMock.createSetupContract(); + +describe('[CCR API] Update follower index', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const router = httpService.createRouter(); + + registerUpdateRoute({ + router, + license: mockLicense, + lib: { + handleEsError, + }, + }); + + routeHandler = router.put.mock.calls[0][1]; + }); + + it('should serialize the payload before sending it to Elasticsearch', async () => { + const routeContextMock = mockRouteContext({ + ccr: { + followInfo: jest + .fn() + .mockResolvedValueOnce({ body: { follower_indices: [{ status: 'paused' }] } }), + // Just echo back what we send so we can inspect it. + resumeFollow: jest.fn().mockImplementation((payload) => ({ body: payload })), + }, + }); + + const request = httpServerMock.createKibanaRequest({ + params: { id: 'foo' }, + body: { + maxReadRequestOperationCount: 1, + maxOutstandingReadRequests: 1, + maxReadRequestSize: '1b', + maxWriteRequestOperationCount: 1, + maxWriteRequestSize: '1b', + maxOutstandingWriteRequests: 1, + maxWriteBufferCount: 1, + maxWriteBufferSize: '1b', + maxRetryDelay: '1s', + readPollTimeout: '1s', + }, + }); + + const response = await routeHandler(routeContextMock, request, kibanaResponseFactory); + + expect(response.payload).toEqual({ + index: 'foo', + body: { + max_outstanding_read_requests: 1, + max_outstanding_write_requests: 1, + max_read_request_operation_count: 1, + max_read_request_size: '1b', + max_retry_delay: '1s', + max_write_buffer_count: 1, + max_write_buffer_size: '1b', + max_write_request_operation_count: 1, + max_write_request_size: '1b', + read_poll_timeout: '1s', + }, + }); + }); +}); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts index 933d13a0a223..5029c8be9604 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/follower_index/register_update_route.ts @@ -18,7 +18,7 @@ import { RouteDependencies } from '../../../types'; export const registerUpdateRoute = ({ router, license, - lib: { isEsError, formatEsError }, + lib: { handleEsError }, }: RouteDependencies) => { const paramsSchema = schema.object({ id: schema.string() }); @@ -44,13 +44,14 @@ export const registerUpdateRoute = ({ }, }, license.guardApiRoute(async (context, request, response) => { + const { client } = context.core.elasticsearch; const { id } = request.params; // We need to first pause the follower and then resume it by passing the advanced settings try { const { - follower_indices: followerIndices, - } = await context.crossClusterReplication.client.callAsCurrentUser('ccr.info', { id }); + body: { follower_indices: followerIndices }, + } = await client.asCurrentUser.ccr.followInfo({ index: id }); const followerIndexInfo = followerIndices && followerIndices[0]; @@ -63,12 +64,7 @@ export const registerUpdateRoute = ({ // Pause follower if not already paused if (!isPaused) { - await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.pauseFollowerIndex', - { - id, - } - ); + await client.asCurrentUser.ccr.pauseFollow({ index: id }); } // Resume follower @@ -76,18 +72,16 @@ export const registerUpdateRoute = ({ serializeAdvancedSettings(request.body as FollowerIndexAdvancedSettings) ); - return response.ok({ - body: await context.crossClusterReplication!.client.callAsCurrentUser( - 'ccr.resumeFollowerIndex', - { id, body } - ), + const { body: responseBody } = await client.asCurrentUser.ccr.resumeFollow({ + index: id, + body, }); - } catch (err) { - if (isEsError(err)) { - return response.customError(formatEsError(err)); - } - // Case: default - throw err; + + return response.ok({ + body: responseBody, + }); + } catch (error) { + return handleEsError({ error, response }); } }) ); diff --git a/x-pack/plugins/cross_cluster_replication/server/routes/api/test_lib.ts b/x-pack/plugins/cross_cluster_replication/server/routes/api/test_lib.ts index e2aa8a478e91..3a5a31779793 100644 --- a/x-pack/plugins/cross_cluster_replication/server/routes/api/test_lib.ts +++ b/x-pack/plugins/cross_cluster_replication/server/routes/api/test_lib.ts @@ -6,19 +6,24 @@ */ import { RequestHandlerContext } from 'src/core/server'; +import { License } from '../../shared_imports'; -export function mockRouteContext({ - callAsCurrentUser, -}: { - callAsCurrentUser: any; -}): RequestHandlerContext { +export function mockRouteContext(mockedFunctions: unknown): RequestHandlerContext { const routeContextMock = ({ - crossClusterReplication: { - client: { - callAsCurrentUser, + core: { + elasticsearch: { + client: { + asCurrentUser: mockedFunctions, + }, }, }, } as unknown) as RequestHandlerContext; return routeContextMock; } + +export const mockLicense = { + guardApiRoute: (route: any) => route, +} as License; + +export const mockError = { name: 'ResponseError', statusCode: 400 }; diff --git a/x-pack/plugins/cross_cluster_replication/server/shared_imports.ts b/x-pack/plugins/cross_cluster_replication/server/shared_imports.ts index 4252a2a5c32d..e9e3ed72aed6 100644 --- a/x-pack/plugins/cross_cluster_replication/server/shared_imports.ts +++ b/x-pack/plugins/cross_cluster_replication/server/shared_imports.ts @@ -5,5 +5,5 @@ * 2.0. */ -export { isEsError } from '../../../../src/plugins/es_ui_shared/server'; +export { handleEsError } from '../../../../src/plugins/es_ui_shared/server'; export { License } from '../../license_api_guard/server'; diff --git a/x-pack/plugins/cross_cluster_replication/server/types.ts b/x-pack/plugins/cross_cluster_replication/server/types.ts index 7314fda70027..18d2c45039eb 100644 --- a/x-pack/plugins/cross_cluster_replication/server/types.ts +++ b/x-pack/plugins/cross_cluster_replication/server/types.ts @@ -5,13 +5,12 @@ * 2.0. */ -import { IRouter, ILegacyScopedClusterClient, RequestHandlerContext } from 'src/core/server'; +import { IRouter } from 'src/core/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server'; import { IndexManagementPluginSetup } from '../../index_management/server'; import { RemoteClustersPluginSetup } from '../../remote_clusters/server'; -import { License, isEsError } from './shared_imports'; -import { formatEsError } from './lib/format_es_error'; +import { License, handleEsError } from './shared_imports'; export interface SetupDependencies { licensing: LicensingPluginSetup; @@ -25,24 +24,9 @@ export interface StartDependencies { } export interface RouteDependencies { - router: CcrPluginRouter; + router: IRouter; license: License; lib: { - isEsError: typeof isEsError; - formatEsError: typeof formatEsError; + handleEsError: typeof handleEsError; }; } - -/** - * @internal - */ -export interface CcrRequestHandlerContext extends RequestHandlerContext { - crossClusterReplication: { - client: ILegacyScopedClusterClient; - }; -} - -/** - * @internal - */ -type CcrPluginRouter = IRouter; diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.js index be15b9586bb5..111aa43d95f9 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/auto_follow_pattern.js @@ -45,7 +45,7 @@ export default function ({ getService }) { }); expect(body.statusCode).to.be(404); - expect(body.attributes.cause[0]).to.contain('no such remote cluster'); + expect(body.attributes.error.reason).to.contain('no such remote cluster'); }); }); @@ -70,7 +70,7 @@ export default function ({ getService }) { it('should return a 404 when the auto-follow pattern is not found', async () => { const { body } = await getAutoFollowPattern('missing-pattern'); expect(body.statusCode).to.be(404); - expect(body.attributes.cause).not.to.be(undefined); + expect(body.attributes.error.reason).not.to.be(undefined); }); it('should return an auto-follow pattern that was created', async () => { diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/fixtures.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/fixtures.js index a2ea349ae7af..69485b479f25 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/fixtures.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/fixtures.js @@ -6,10 +6,9 @@ */ import { REMOTE_CLUSTER_NAME } from './constants'; -import { getRandomString } from './lib'; export const getFollowerIndexPayload = ( - leaderIndexName = getRandomString(), + leaderIndexName = 'test-leader-index', remoteCluster = REMOTE_CLUSTER_NAME, advancedSettings = {} ) => ({ diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.helpers.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.helpers.js index fe8f260aef94..7890679d59c2 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.helpers.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.helpers.js @@ -6,7 +6,6 @@ */ import { API_BASE_PATH } from './constants'; -import { getRandomString } from './lib'; import { getFollowerIndexPayload } from './fixtures'; export const registerHelpers = (supertest) => { @@ -51,7 +50,7 @@ export const registerHelpers = (supertest) => { }; }; - const createFollowerIndex = (name = getRandomString(), payload = getFollowerIndexPayload()) => { + const createFollowerIndex = (name, payload = getFollowerIndexPayload()) => { followerIndicesCreated.push(name); return supertest diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js index e31340021448..7f5df1e4632f 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FOLLOWER_INDEX_ADVANCED_SETTINGS } from '../../../../../plugins/cross_cluster_replication/common/constants'; import { getFollowerIndexPayload } from './fixtures'; -import { registerHelpers as registerElasticSearchHelpers, getRandomString } from './lib'; +import { registerHelpers as registerElasticSearchHelpers } from './lib'; import { registerHelpers as registerRemoteClustersHelpers } from './remote_clusters.helpers'; import { registerHelpers as registerFollowerIndicesnHelpers } from './follower_indices.helpers'; @@ -47,23 +47,23 @@ export default function ({ getService }) { const payload = getFollowerIndexPayload(); payload.remoteCluster = 'unknown-cluster'; - const { body } = await createFollowerIndex(undefined, payload).expect(404); - expect(body.attributes.cause[0]).to.contain('no such remote cluster'); + const { body } = await createFollowerIndex('test', payload).expect(404); + expect(body.attributes.error.reason).to.contain('no such remote cluster'); }); it('should throw a 404 error trying to follow an unknown index', async () => { const payload = getFollowerIndexPayload(); - const { body } = await createFollowerIndex(undefined, payload).expect(404); - expect(body.attributes.cause[0]).to.contain('no such index'); + const { body } = await createFollowerIndex('test', payload).expect(404); + expect(body.attributes.error.reason).to.contain('no such index'); }); // NOTE: If this test fails locally it's probably because you have another cluster running. it('should create a follower index that follows an existing leader index', async () => { // First let's create an index to follow - const leaderIndex = await createIndex(); + const leaderIndex = await createIndex('leader1'); const payload = getFollowerIndexPayload(leaderIndex); - const { body } = await createFollowerIndex(undefined, payload).expect(200); + const { body } = await createFollowerIndex('index1', payload).expect(200); // There is a race condition in which Elasticsearch can respond without acknowledging, // i.e. `body.follow_index_shards_acked` is sometimes true and sometimes false. @@ -74,17 +74,17 @@ export default function ({ getService }) { describe('get()', () => { it('should return a 404 when the follower index does not exist', async () => { - const name = getRandomString(); + const name = 'test'; const { body } = await getFollowerIndex(name).expect(404); - expect(body.attributes.cause[0]).to.contain('no such index'); + expect(body.attributes.error.reason).to.contain('no such index'); }); // NOTE: If this test fails locally it's probably because you have another cluster running. it('should return a follower index that was created', async () => { - const leaderIndex = await createIndex(); + const leaderIndex = await createIndex('leader2'); - const name = getRandomString(); + const name = 'index2'; const payload = getFollowerIndexPayload(leaderIndex); await createFollowerIndex(name, payload); @@ -98,8 +98,8 @@ export default function ({ getService }) { describe('update()', () => { it('should update a follower index advanced settings', async () => { // Create a follower index - const leaderIndex = await createIndex(); - const followerIndex = getRandomString(); + const leaderIndex = await createIndex('leader3'); + const followerIndex = 'index3'; const initialValue = 1234; const payload = getFollowerIndexPayload(leaderIndex, undefined, { maxReadRequestOperationCount: initialValue, @@ -128,9 +128,8 @@ export default function ({ getService }) { * When we then retrieve the follower index it will have all the advanced settings * coming from ES. We can then compare those settings with our hard-coded values. */ - const leaderIndex = await createIndex(); - - const name = getRandomString(); + const leaderIndex = await createIndex('leader4'); + const name = 'index4'; const payload = getFollowerIndexPayload(leaderIndex); await createFollowerIndex(name, payload); diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/elasticsearch.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/elasticsearch.js index 326dbf1ef185..37d6ade2a7d4 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/elasticsearch.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/elasticsearch.js @@ -5,20 +5,18 @@ * 2.0. */ -import { getRandomString } from './random'; - /** * Helpers to create and delete indices on the Elasticsearch instance * during our tests. * @param {ElasticsearchClient} es The Elasticsearch client instance */ export const registerHelpers = (getService) => { - const es = getService('legacyEs'); + const es = getService('es'); const esDeleteAllIndices = getService('esDeleteAllIndices'); let indicesCreated = []; - const createIndex = (index = getRandomString()) => { + const createIndex = (index) => { indicesCreated.push(index); return es.indices.create({ index }).then(() => index); }; diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/index.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/index.js index 38eb2016cdb6..40fc2e37ed4e 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/index.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/index.js @@ -6,5 +6,3 @@ */ export { registerHelpers } from './elasticsearch'; - -export { getRandomString } from './random'; diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/random.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/random.js deleted file mode 100644 index 9c8a4cc5ff9d..000000000000 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/random.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * 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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import Chance from 'chance'; - -const chance = new Chance(); -const CHARS_POOL = 'abcdefghijklmnopqrstuvwxyz'; - -export const getRandomString = () => `${chance.string({ pool: CHARS_POOL })}-${Date.now()}`;