From d01a0bfc59fa0eabaa17db33502d0a491866b28d Mon Sep 17 00:00:00 2001 From: Alison Goryachev Date: Fri, 11 Feb 2022 09:39:11 -0500 Subject: [PATCH] [Upgrade Assistant] Add warning when remote clusters are configured (#125138) (#125296) (#125368) --- x-pack/plugins/remote_clusters/kibana.json | 2 +- .../plugins/remote_clusters/public/locator.ts | 44 +++++++++++ .../plugins/remote_clusters/public/plugin.ts | 9 ++- .../plugins/remote_clusters/public/types.ts | 2 + .../es_deprecations/deprecations_list.test.ts | 20 +++++ .../helpers/http_requests.ts | 12 +++ .../es_deprecations/es_deprecations.tsx | 66 ++++++++++++++-- .../public/application/lib/api.ts | 7 ++ .../server/routes/register_routes.ts | 2 + .../server/routes/remote_clusters.ts | 40 ++++++++++ .../apis/upgrade_assistant/index.ts | 1 + .../apis/upgrade_assistant/remote_clusters.ts | 75 +++++++++++++++++++ 12 files changed, 273 insertions(+), 7 deletions(-) create mode 100644 x-pack/plugins/remote_clusters/public/locator.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/routes/remote_clusters.ts create mode 100644 x-pack/test/api_integration/apis/upgrade_assistant/remote_clusters.ts diff --git a/x-pack/plugins/remote_clusters/kibana.json b/x-pack/plugins/remote_clusters/kibana.json index 192a1308c265a..2c5e0c3cea831 100644 --- a/x-pack/plugins/remote_clusters/kibana.json +++ b/x-pack/plugins/remote_clusters/kibana.json @@ -6,7 +6,7 @@ "name": "Stack Management", "githubTeam": "kibana-stack-management" }, - "requiredPlugins": ["licensing", "management", "indexManagement", "features"], + "requiredPlugins": ["licensing", "management", "indexManagement", "features", "share"], "optionalPlugins": ["usageCollection", "cloud"], "server": true, "ui": true, diff --git a/x-pack/plugins/remote_clusters/public/locator.ts b/x-pack/plugins/remote_clusters/public/locator.ts new file mode 100644 index 0000000000000..c6ea66f4c4328 --- /dev/null +++ b/x-pack/plugins/remote_clusters/public/locator.ts @@ -0,0 +1,44 @@ +/* + * 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 type { SerializableRecord } from '@kbn/utility-types'; +import { ManagementAppLocator } from 'src/plugins/management/common'; +import { LocatorDefinition } from '../../../../src/plugins/share/public/'; + +export const REMOTE_CLUSTERS_LOCATOR_ID = 'REMOTE_CLUSTERS_LOCATOR'; + +export interface RemoteClustersLocatorParams extends SerializableRecord { + page: 'remoteClusters'; +} + +export interface RemoteClustersLocatorDefinitionDependencies { + managementAppLocator: ManagementAppLocator; +} + +export class RemoteClustersLocatorDefinition + implements LocatorDefinition +{ + constructor(protected readonly deps: RemoteClustersLocatorDefinitionDependencies) {} + + public readonly id = REMOTE_CLUSTERS_LOCATOR_ID; + + public readonly getLocation = async (params: RemoteClustersLocatorParams) => { + const location = await this.deps.managementAppLocator.getLocation({ + sectionId: 'data', + appId: 'remote_clusters', + }); + + switch (params.page) { + case 'remoteClusters': { + return { + ...location, + path: location.path, + }; + } + } + }; +} diff --git a/x-pack/plugins/remote_clusters/public/plugin.ts b/x-pack/plugins/remote_clusters/public/plugin.ts index 4b47d76944b77..c6de539d1e6ed 100644 --- a/x-pack/plugins/remote_clusters/public/plugin.ts +++ b/x-pack/plugins/remote_clusters/public/plugin.ts @@ -16,6 +16,7 @@ import { init as initUiMetric } from './application/services/ui_metric'; import { init as initNotification } from './application/services/notification'; import { init as initRedirect } from './application/services/redirect'; import { Dependencies, ClientConfigType } from './types'; +import { RemoteClustersLocatorDefinition } from './locator'; export interface RemoteClustersPluginSetup { isUiEnabled: boolean; @@ -28,7 +29,7 @@ export class RemoteClustersUIPlugin setup( { notifications: { toasts }, http, getStartServices }: CoreSetup, - { management, usageCollection, cloud }: Dependencies + { management, usageCollection, cloud, share }: Dependencies ) { const { ui: { enabled: isRemoteClustersUiEnabled }, @@ -79,6 +80,12 @@ export class RemoteClustersUIPlugin }; }, }); + + share.url.locators.create( + new RemoteClustersLocatorDefinition({ + managementAppLocator: management.locator, + }) + ); } return { diff --git a/x-pack/plugins/remote_clusters/public/types.ts b/x-pack/plugins/remote_clusters/public/types.ts index bcd162599ab77..ad26e388c9fcd 100644 --- a/x-pack/plugins/remote_clusters/public/types.ts +++ b/x-pack/plugins/remote_clusters/public/types.ts @@ -8,6 +8,7 @@ import { ManagementSetup } from 'src/plugins/management/public'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/public'; import { RegisterManagementAppArgs } from 'src/plugins/management/public'; +import { SharePluginSetup } from 'src/plugins/share/public'; import { I18nStart } from 'kibana/public'; import { CloudSetup } from '../../cloud/public'; @@ -15,6 +16,7 @@ export interface Dependencies { management: ManagementSetup; usageCollection: UsageCollectionSetup; cloud: CloudSetup; + share: SharePluginSetup; } export interface ClientConfigType { diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts index 49c084eb9f27d..c118eed5e2616 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts @@ -44,6 +44,7 @@ describe('ES deprecations table', () => { aliases: [], }, }); + httpRequestsMockHelpers.setLoadRemoteClustersResponse([]); await act(async () => { testBed = await setupElasticsearchPage({ isReadOnlyMode: false }); @@ -105,6 +106,25 @@ describe('ES deprecations table', () => { expect(find('warningDeprecationsCount').text()).toContain(warningDeprecations.length); }); + describe('remote clusters callout', () => { + beforeEach(async () => { + httpRequestsMockHelpers.setLoadRemoteClustersResponse(['test_remote_cluster']); + + await act(async () => { + testBed = await setupElasticsearchPage({ isReadOnlyMode: false }); + }); + + testBed.component.update(); + }); + + it('shows a warning message if a user has remote clusters configured', () => { + const { exists } = testBed; + + // Verify warning exists + expect(exists('remoteClustersWarningCallout')).toBe(true); + }); + }); + describe('search bar', () => { it('filters results by "critical" status', async () => { const { find, actions } = testBed; diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts index b29b6d4fbff37..774dfba4c1b9f 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/http_requests.ts @@ -203,6 +203,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setLoadRemoteClustersResponse = (response?: object, error?: ResponseError) => { + const status = error ? error.statusCode || 400 : 200; + const body = error ? error : response; + + server.respondWith('GET', `${API_BASE_PATH}/remote_clusters`, [ + status, + { 'Content-Type': 'application/json' }, + JSON.stringify(body), + ]); + }; + return { setLoadCloudBackupStatusResponse, setLoadEsDeprecationsResponse, @@ -220,6 +231,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { setReindexStatusResponse, setLoadMlUpgradeModeResponse, setGetUpgradeStatusResponse, + setLoadRemoteClustersResponse, }; }; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx index d4bbab5102d99..b585ea20b1714 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx @@ -8,7 +8,7 @@ import React, { useEffect, useMemo } from 'react'; import { withRouter, RouteComponentProps } from 'react-router-dom'; -import { EuiPageHeader, EuiSpacer, EuiPageContent, EuiLink } from '@elastic/eui'; +import { EuiPageHeader, EuiSpacer, EuiPageContent, EuiLink, EuiCallOut } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { DocLinksStart } from 'kibana/public'; @@ -51,6 +51,26 @@ const i18nTexts = { isLoading: i18n.translate('xpack.upgradeAssistant.esDeprecations.loadingText', { defaultMessage: 'Loading deprecation issues…', }), + remoteClustersDetectedTitle: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.remoteClustersDetectedTitle', + { + defaultMessage: 'Remote cluster compatibility', + } + ), + getRemoteClustersDetectedDescription: (remoteClustersCount: number) => + i18n.translate('xpack.upgradeAssistant.esDeprecations.remoteClustersDetectedDescription', { + defaultMessage: + 'You have {remoteClustersCount} {remoteClustersCount, plural, one {remote cluster} other {remote clusters}} configured. If you use cross-cluster search, note that 8.x can only search remote clusters running the previous minor version or later. If you use cross-cluster replication, a cluster that contains follower indices must run the same or newer version as the remote cluster.', + values: { + remoteClustersCount, + }, + }), + remoteClustersLinkText: i18n.translate( + 'xpack.upgradeAssistant.esDeprecations.remoteClustersLinkText', + { + defaultMessage: 'View remote clusters.', + } + ), }; const getBatchReindexLink = (docLinks: DocLinksStart) => { @@ -75,6 +95,22 @@ const getBatchReindexLink = (docLinks: DocLinksStart) => { ); }; +const RemoteClustersAppLink: React.FunctionComponent = () => { + const { + plugins: { share }, + } = useAppContext(); + + const remoteClustersUrl = share.url.locators + .get('REMOTE_CLUSTERS_LOCATOR') + ?.useUrl({ page: 'remoteClusters' }); + + return ( + + {i18nTexts.remoteClustersLinkText} + + ); +}; + export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => { const { services: { @@ -85,6 +121,7 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => { } = useAppContext(); const { data: esDeprecations, isLoading, error, resendRequest } = api.useLoadEsDeprecations(); + const { data: remoteClusters } = api.useLoadRemoteClusters(); const deprecationsCountByLevel: { warningDeprecations: number; @@ -140,10 +177,29 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => { } > - + <> + {remoteClusters && remoteClusters.length > 0 && ( + <> + +

+ {i18nTexts.getRemoteClustersDetectedDescription(remoteClusters.length)}{' '} + +

+
+ + + )} + + + diff --git a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts index f3c5680b56e20..49be16b44236d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts +++ b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts @@ -239,6 +239,13 @@ export class ApiService { method: 'get', }); } + + public useLoadRemoteClusters() { + return this.useRequest({ + path: `${API_BASE_PATH}/remote_clusters`, + method: 'get', + }); + } } export const apiService = new ApiService(); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts index b6c8850376684..c0f422711901b 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts @@ -18,6 +18,7 @@ import { registerUpdateSettingsRoute } from './update_index_settings'; import { registerMlSnapshotRoutes } from './ml_snapshots'; import { ReindexWorker } from '../lib/reindexing'; import { registerUpgradeStatusRoute } from './status'; +import { registerRemoteClustersRoute } from './remote_clusters'; export function registerRoutes(dependencies: RouteDependencies, getWorker: () => ReindexWorker) { registerAppRoutes(dependencies); @@ -32,4 +33,5 @@ export function registerRoutes(dependencies: RouteDependencies, getWorker: () => registerMlSnapshotRoutes(dependencies); // Route for cloud to retrieve the upgrade status for ES and Kibana registerUpgradeStatusRoute(dependencies); + registerRemoteClustersRoute(dependencies); } diff --git a/x-pack/plugins/upgrade_assistant/server/routes/remote_clusters.ts b/x-pack/plugins/upgrade_assistant/server/routes/remote_clusters.ts new file mode 100644 index 0000000000000..de2177cffa1fe --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/routes/remote_clusters.ts @@ -0,0 +1,40 @@ +/* + * 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 { API_BASE_PATH } from '../../common/constants'; +import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; +import { RouteDependencies } from '../types'; + +export function registerRemoteClustersRoute({ router, lib: { handleEsError } }: RouteDependencies) { + router.get( + { + path: `${API_BASE_PATH}/remote_clusters`, + validate: false, + }, + versionCheckHandlerWrapper( + async ( + { + core: { + elasticsearch: { client }, + }, + }, + request, + response + ) => { + try { + const { body: clustersByName } = await client.asCurrentUser.cluster.remoteInfo(); + + const remoteClusters = Object.keys(clustersByName); + + return response.ok({ body: remoteClusters }); + } catch (error) { + return handleEsError({ error, response }); + } + } + ) + ); +} diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/index.ts b/x-pack/test/api_integration/apis/upgrade_assistant/index.ts index 4d92d2e2c76df..20d6e57a71c12 100644 --- a/x-pack/test/api_integration/apis/upgrade_assistant/index.ts +++ b/x-pack/test/api_integration/apis/upgrade_assistant/index.ts @@ -14,5 +14,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./privileges')); loadTestFile(require.resolve('./es_deprecations')); loadTestFile(require.resolve('./es_deprecation_logs')); + loadTestFile(require.resolve('./remote_clusters')); }); } diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/remote_clusters.ts b/x-pack/test/api_integration/apis/upgrade_assistant/remote_clusters.ts new file mode 100644 index 0000000000000..5d8dcaf339068 --- /dev/null +++ b/x-pack/test/api_integration/apis/upgrade_assistant/remote_clusters.ts @@ -0,0 +1,75 @@ +/* + * 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 expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; +import { API_BASE_PATH } from '../../../../plugins/upgrade_assistant/common/constants'; + +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('es'); + const log = getService('log'); + + describe('Remote clusters', () => { + describe('GET /api/upgrade_assistant/remote_clusters', () => { + before(async () => { + try { + // Configure a remote cluster + await es.cluster.putSettings({ + body: { + persistent: { + cluster: { + remote: { + test_cluster: { + seeds: ['127.0.0.1:9400'], + }, + }, + }, + }, + }, + }); + } catch (e) { + log.debug('Error creating remote cluster'); + throw e; + } + }); + + after(async () => { + try { + // Delete remote cluster + await es.cluster.putSettings({ + body: { + persistent: { + cluster: { + remote: { + test_cluster: { + seeds: null, + }, + }, + }, + }, + }, + }); + } catch (e) { + log.debug('Error deleting remote cluster'); + throw e; + } + }); + + it('returns an array of remote clusters', async () => { + const { body: apiRequestResponse } = await supertest + .get(`${API_BASE_PATH}/remote_clusters`) + .set('kbn-xsrf', 'xxx') + .expect(200); + + expect(Array.isArray(apiRequestResponse)).be(true); + expect(apiRequestResponse.length).be(1); + }); + }); + }); +}