diff --git a/src/core/server/opensearch/client/cluster_client.ts b/src/core/server/opensearch/client/cluster_client.ts index 9dfac6daf44b..84cd9e8c6da0 100644 --- a/src/core/server/opensearch/client/cluster_client.ts +++ b/src/core/server/opensearch/client/cluster_client.ts @@ -30,7 +30,7 @@ * GitHub history for details. */ -import { Client } from '@opensearch-project/opensearch'; +import { Client } from '@opensearch-project/opensearch/api/new'; import { Logger } from '../../logging'; import { GetAuthHeaders, Headers, isOpenSearchDashboardsRequest, isRealRequest } from '../../http'; import { ensureRawRequest, filterHeaders } from '../../http/router'; diff --git a/src/core/server/opensearch/client/configure_client.ts b/src/core/server/opensearch/client/configure_client.ts index 792daff56cf5..39a982d0eef6 100644 --- a/src/core/server/opensearch/client/configure_client.ts +++ b/src/core/server/opensearch/client/configure_client.ts @@ -31,7 +31,7 @@ */ import { Buffer } from 'buffer'; import { stringify } from 'querystring'; -import { Client } from '@opensearch-project/opensearch'; +import { Client } from '@opensearch-project/opensearch/api/new'; import { RequestBody } from '@opensearch-project/opensearch/lib/Transport'; import { Logger } from '../../logging'; diff --git a/src/core/server/opensearch/client/mocks.test.ts b/src/core/server/opensearch/client/mocks.test.ts index d36b06bf2408..e07ba3b0580a 100644 --- a/src/core/server/opensearch/client/mocks.test.ts +++ b/src/core/server/opensearch/client/mocks.test.ts @@ -54,7 +54,6 @@ describe('Mocked client', () => { }); it('nested level API methods should be mocked', () => { - expectMocked(client.asyncSearch.get); expectMocked(client.nodes.info); }); diff --git a/src/core/server/opensearch/client/mocks.ts b/src/core/server/opensearch/client/mocks.ts index 9d770b1c615b..c6bcc1e748dc 100644 --- a/src/core/server/opensearch/client/mocks.ts +++ b/src/core/server/opensearch/client/mocks.ts @@ -29,7 +29,7 @@ * Modifications Copyright OpenSearch Contributors. See * GitHub history for details. */ -import { Client, ApiResponse } from '@opensearch-project/opensearch'; +import { Client, ApiResponse } from '@opensearch-project/opensearch/api/new'; import { TransportRequestPromise } from '@opensearch-project/opensearch/lib/Transport'; import { OpenSearchClient } from './types'; import { ICustomClusterClient } from './cluster_client'; @@ -176,9 +176,9 @@ const createErrorTransportRequestPromise = (err: any): MockedTransportRequestPro return promise as MockedTransportRequestPromise; }; -function createApiResponse(opts: Partial = {}): ApiResponse { +function createApiResponse>(opts: Partial = {}): ApiResponse { return { - body: {}, + body: {} as any, statusCode: 200, headers: {}, warnings: [], diff --git a/src/core/server/opensearch/client/retry_call_cluster.test.ts b/src/core/server/opensearch/client/retry_call_cluster.test.ts deleted file mode 100644 index fce4d3228462..000000000000 --- a/src/core/server/opensearch/client/retry_call_cluster.test.ts +++ /dev/null @@ -1,313 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - */ - -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/* - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -import { errors } from '@opensearch-project/opensearch'; -import { opensearchClientMock } from './mocks'; -import { loggingSystemMock } from '../../logging/logging_system.mock'; -import { retryCallCluster, migrationRetryCallCluster } from './retry_call_cluster'; - -const dummyBody = { foo: 'bar' }; -const createErrorReturn = (err: any) => - opensearchClientMock.createErrorTransportRequestPromise(err); - -describe('retryCallCluster', () => { - let client: ReturnType; - - beforeEach(() => { - client = opensearchClientMock.createOpenSearchClient(); - }); - - it('returns response from OpenSearch API call in case of success', async () => { - const successReturn = opensearchClientMock.createSuccessTransportRequestPromise({ - ...dummyBody, - }); - - client.asyncSearch.get.mockReturnValue(successReturn); - - const result = await retryCallCluster(() => client.asyncSearch.get()); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects with `NoLivingConnectionsError`', async () => { - const successReturn = opensearchClientMock.createSuccessTransportRequestPromise({ - ...dummyBody, - }); - - client.asyncSearch.get - .mockImplementationOnce(() => - createErrorReturn(new errors.NoLivingConnectionsError('no living connections', {} as any)) - ) - .mockImplementationOnce(() => successReturn); - - const result = await retryCallCluster(() => client.asyncSearch.get()); - expect(result.body).toEqual(dummyBody); - }); - - it('rejects when OpenSearch API calls reject with other errors', async () => { - client.ping - .mockImplementationOnce(() => createErrorReturn(new Error('unknown error'))) - .mockImplementationOnce(() => - opensearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody }) - ); - - await expect(retryCallCluster(() => client.ping())).rejects.toMatchInlineSnapshot( - `[Error: unknown error]` - ); - }); - - it('stops retrying when OpenSearch API calls reject with other errors', async () => { - client.ping - .mockImplementationOnce(() => - createErrorReturn(new errors.NoLivingConnectionsError('no living connections', {} as any)) - ) - .mockImplementationOnce(() => - createErrorReturn(new errors.NoLivingConnectionsError('no living connections', {} as any)) - ) - .mockImplementationOnce(() => createErrorReturn(new Error('unknown error'))) - .mockImplementationOnce(() => - opensearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody }) - ); - - await expect(retryCallCluster(() => client.ping())).rejects.toMatchInlineSnapshot( - `[Error: unknown error]` - ); - }); -}); - -describe('migrationRetryCallCluster', () => { - let client: ReturnType; - let logger: ReturnType; - - beforeEach(() => { - client = opensearchClientMock.createOpenSearchClient(); - logger = loggingSystemMock.createLogger(); - }); - - const mockClientPingWithErrorBeforeSuccess = (error: any) => { - client.ping - .mockImplementationOnce(() => createErrorReturn(error)) - .mockImplementationOnce(() => createErrorReturn(error)) - .mockImplementationOnce(() => - opensearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody }) - ); - }; - - it('retries OpenSearch API calls that rejects with `NoLivingConnectionsError`', async () => { - mockClientPingWithErrorBeforeSuccess( - new errors.NoLivingConnectionsError('no living connections', {} as any) - ); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects with `ConnectionError`', async () => { - mockClientPingWithErrorBeforeSuccess(new errors.ConnectionError('connection error', {} as any)); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects with `TimeoutError`', async () => { - mockClientPingWithErrorBeforeSuccess(new errors.TimeoutError('timeout error', {} as any)); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects with 503 `ResponseError`', async () => { - mockClientPingWithErrorBeforeSuccess( - new errors.ResponseError({ - statusCode: 503, - } as any) - ); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects 401 `ResponseError`', async () => { - mockClientPingWithErrorBeforeSuccess( - new errors.ResponseError({ - statusCode: 401, - } as any) - ); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects with 403 `ResponseError`', async () => { - mockClientPingWithErrorBeforeSuccess( - new errors.ResponseError({ - statusCode: 403, - } as any) - ); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects with 408 `ResponseError`', async () => { - mockClientPingWithErrorBeforeSuccess( - new errors.ResponseError({ - statusCode: 408, - } as any) - ); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects with 410 `ResponseError`', async () => { - mockClientPingWithErrorBeforeSuccess( - new errors.ResponseError({ - statusCode: 410, - } as any) - ); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('retries OpenSearch API calls that rejects with `snapshot_in_progress_exception` `ResponseError`', async () => { - mockClientPingWithErrorBeforeSuccess( - new errors.ResponseError({ - statusCode: 500, - body: { - error: { - type: 'snapshot_in_progress_exception', - }, - }, - } as any) - ); - - const result = await migrationRetryCallCluster(() => client.ping(), logger, 1); - expect(result.body).toEqual(dummyBody); - }); - - it('logs only once for each unique error message', async () => { - client.ping - .mockImplementationOnce(() => - createErrorReturn( - new errors.ResponseError({ - statusCode: 503, - } as any) - ) - ) - .mockImplementationOnce(() => - createErrorReturn(new errors.ConnectionError('connection error', {} as any)) - ) - .mockImplementationOnce(() => - createErrorReturn( - new errors.ResponseError({ - statusCode: 503, - } as any) - ) - ) - .mockImplementationOnce(() => - createErrorReturn(new errors.ConnectionError('connection error', {} as any)) - ) - .mockImplementationOnce(() => - createErrorReturn( - new errors.ResponseError({ - statusCode: 500, - body: { - error: { - type: 'snapshot_in_progress_exception', - }, - }, - } as any) - ) - ) - .mockImplementationOnce(() => - opensearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody }) - ); - - await migrationRetryCallCluster(() => client.ping(), logger, 1); - - expect(loggingSystemMock.collect(logger).warn).toMatchInlineSnapshot(` - Array [ - Array [ - "Unable to connect to OpenSearch. Error: Response Error", - ], - Array [ - "Unable to connect to OpenSearch. Error: connection error", - ], - Array [ - "Unable to connect to OpenSearch. Error: snapshot_in_progress_exception", - ], - ] - `); - }); - - it('rejects when OpenSearch API calls reject with other errors', async () => { - client.ping - .mockImplementationOnce(() => - createErrorReturn( - new errors.ResponseError({ - statusCode: 418, - body: { - error: { - type: `I'm a teapot`, - }, - }, - } as any) - ) - ) - .mockImplementationOnce(() => - opensearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody }) - ); - - await expect( - migrationRetryCallCluster(() => client.ping(), logger, 1) - ).rejects.toMatchInlineSnapshot(`[ResponseError: I'm a teapot]`); - }); - - it('stops retrying when OpenSearch API calls reject with other errors', async () => { - client.ping - .mockImplementationOnce(() => - createErrorReturn(new errors.TimeoutError('timeout error', {} as any)) - ) - .mockImplementationOnce(() => - createErrorReturn(new errors.TimeoutError('timeout error', {} as any)) - ) - .mockImplementationOnce(() => createErrorReturn(new Error('unknown error'))) - .mockImplementationOnce(() => - opensearchClientMock.createSuccessTransportRequestPromise({ ...dummyBody }) - ); - - await expect( - migrationRetryCallCluster(() => client.ping(), logger, 1) - ).rejects.toMatchInlineSnapshot(`[Error: unknown error]`); - }); -}); diff --git a/src/core/server/opensearch/version_check/ensure_opensearch_version.test.ts b/src/core/server/opensearch/version_check/ensure_opensearch_version.test.ts index dbbc0f2236ee..d9435a5cea0c 100644 --- a/src/core/server/opensearch/version_check/ensure_opensearch_version.test.ts +++ b/src/core/server/opensearch/version_check/ensure_opensearch_version.test.ts @@ -169,7 +169,7 @@ describe('pollOpenSearchNodesVersion', () => { internalClient.nodes.info.mockImplementationOnce(() => createOpenSearchError(error)); }; - it('returns iscCompatible=false and keeps polling when a poll request throws', (done) => { + it('returns isCompatible=false and keeps polling when a poll request throws', (done) => { expect.assertions(3); const expectedCompatibilityResults = [false, false, true]; jest.clearAllMocks(); diff --git a/src/core/server/opensearch/version_check/ensure_opensearch_version.ts b/src/core/server/opensearch/version_check/ensure_opensearch_version.ts index 6850f2373b71..4e3dcb76308a 100644 --- a/src/core/server/opensearch/version_check/ensure_opensearch_version.ts +++ b/src/core/server/opensearch/version_check/ensure_opensearch_version.ts @@ -44,6 +44,7 @@ import { } from './opensearch_opensearch_dashboards_version_compatability'; import { Logger } from '../../logging'; import type { OpenSearchClient } from '../client'; +import { ApiResponse } from '@opensearch-project/opensearch'; /** * Checks if all nodes in the cluster have the same cluster id node attribute @@ -63,7 +64,7 @@ export const getNodeId = async ( try { const state = await internalClient.cluster.state({ filter_path: [`nodes.*.attributes.${healthcheckAttributeName}`], - }); + }) as ApiResponse; /* Aggregate different cluster_ids from the OpenSearch nodes * if all the nodes have the same cluster_id, retrieve nodes.info from _local node only * Using _cluster/state/nodes to retrieve the cluster_id of each node from master node which is considered to be a lightweight operation @@ -97,7 +98,7 @@ export interface PollOpenSearchNodesVersionOptions { interface NodeInfo { version: string; ip: string; - http: { + http?: { publish_address: string; }; name: string; @@ -115,6 +116,7 @@ export interface NodesVersionCompatibility { incompatibleNodes: NodeInfo[]; warningNodes: NodeInfo[]; opensearchDashboardsVersion: string; + nodesInfoRequestError?: Error; } function getHumanizedNodeName(node: NodeInfo) { @@ -225,8 +227,8 @@ export const pollOpenSearchNodesVersion = ({ }) ).pipe( map(({ body }) => body), - catchError((_err: any) => { - return of({ nodes: {} }); + catchError((nodesInfoRequestError: any) => { + return of({ nodes: {}, nodesInfoRequestError}); }) ) ) @@ -238,8 +240,8 @@ export const pollOpenSearchNodesVersion = ({ }) ).pipe( map(({ body }) => body), - catchError((_err) => { - return of({ nodes: {} }); + catchError((nodesInfoRequestError: any) => { + return of({ nodes: {}, nodesInfoRequestError }); }) ); }