Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Upgrade Assistant] Add warning when remote clusters are configured #125296

Merged
merged 1 commit into from
Feb 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion x-pack/plugins/remote_clusters/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
44 changes: 44 additions & 0 deletions x-pack/plugins/remote_clusters/public/locator.ts
Original file line number Diff line number Diff line change
@@ -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<RemoteClustersLocatorParams>
{
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,
};
}
}
};
}
9 changes: 8 additions & 1 deletion x-pack/plugins/remote_clusters/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 },
Expand Down Expand Up @@ -79,6 +80,12 @@ export class RemoteClustersUIPlugin
};
},
});

share.url.locators.create(
new RemoteClustersLocatorDefinition({
managementAppLocator: management.locator,
})
);
}

return {
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/remote_clusters/public/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
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';

export interface Dependencies {
management: ManagementSetup;
usageCollection: UsageCollectionSetup;
cloud: CloudSetup;
share: SharePluginSetup;
}

export interface ClientConfigType {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe('ES deprecations table', () => {
aliases: [],
},
});
httpRequestsMockHelpers.setLoadRemoteClustersResponse([]);

await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -220,6 +231,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setReindexStatusResponse,
setLoadMlUpgradeModeResponse,
setGetUpgradeStatusResponse,
setLoadRemoteClustersResponse,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) => {
Expand All @@ -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 (
<EuiLink href={remoteClustersUrl} data-test-subj="remoteClustersLink">
{i18nTexts.remoteClustersLinkText}
</EuiLink>
);
};

export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => {
const {
services: {
Expand All @@ -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;
Expand Down Expand Up @@ -140,10 +177,29 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => {
</>
}
>
<DeprecationCount
totalCriticalDeprecations={deprecationsCountByLevel.criticalDeprecations}
totalWarningDeprecations={deprecationsCountByLevel.warningDeprecations}
/>
<>
{remoteClusters && remoteClusters.length > 0 && (
<>
<EuiCallOut
title={i18nTexts.remoteClustersDetectedTitle}
color="warning"
iconType="help"
data-test-subj="remoteClustersWarningCallout"
>
<p>
{i18nTexts.getRemoteClustersDetectedDescription(remoteClusters.length)}{' '}
<RemoteClustersAppLink />
</p>
</EuiCallOut>
<EuiSpacer />
</>
)}

<DeprecationCount
totalCriticalDeprecations={deprecationsCountByLevel.criticalDeprecations}
totalWarningDeprecations={deprecationsCountByLevel.warningDeprecations}
/>
</>
</EuiPageHeader>

<EuiSpacer size="l" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,13 @@ export class ApiService {
method: 'get',
});
}

public useLoadRemoteClusters() {
return this.useRequest<string[]>({
path: `${API_BASE_PATH}/remote_clusters`,
method: 'get',
});
}
}

export const apiService = new ApiService();
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
}
Original file line number Diff line number Diff line change
@@ -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 });
}
}
)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});
}
Loading