mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
This commit is contained in:
parent
9cba32e630
commit
f7f0e7095a
12 changed files with 273 additions and 7 deletions
|
@ -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,
|
||||
|
|
44
x-pack/plugins/remote_clusters/public/locator.ts
Normal file
44
x-pack/plugins/remote_clusters/public/locator.ts
Normal file
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<EuiLink href={remoteClustersUrl} data-test-subj="remoteClustersLink">
|
||||
{i18nTexts.remoteClustersLinkText}
|
||||
</EuiLink>
|
||||
);
|
||||
};
|
||||
|
||||
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) => {
|
|||
</>
|
||||
}
|
||||
>
|
||||
<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" />
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
|
@ -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'));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue