Show interstitial prompt when ES is upgrading (#31309)
* Show interstitial prompt when ES is upgrading * Update copy
This commit is contained in:
parent
9458387333
commit
46edc4fe8c
|
@ -4,12 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SemVer } from 'semver';
|
||||
import pkg from '../../../package.json';
|
||||
|
||||
// Extract version information
|
||||
const currentVersionNum = pkg.version as string;
|
||||
const matches = currentVersionNum.match(/^([1-9]+)\.([0-9]+)\.([0-9]+)$/)!;
|
||||
|
||||
export const CURRENT_MAJOR_VERSION = matches[1];
|
||||
export const NEXT_MAJOR_VERSION = (parseInt(CURRENT_MAJOR_VERSION, 10) + 1).toString();
|
||||
export const PREV_MAJOR_VERSION = (parseInt(CURRENT_MAJOR_VERSION, 10) - 1).toString();
|
||||
export const CURRENT_VERSION = new SemVer(pkg.version as string);
|
||||
export const CURRENT_MAJOR_VERSION = CURRENT_VERSION.major;
|
||||
export const NEXT_MAJOR_VERSION = CURRENT_VERSION.major + 1;
|
||||
export const PREV_MAJOR_VERSION = CURRENT_VERSION.major - 1;
|
||||
|
|
|
@ -59,4 +59,14 @@ describe('UpgradeAssistantTabs', () => {
|
|||
// Should pass down error status to child component
|
||||
expect(wrapper.find(OverviewTab).prop('loadingState')).toEqual(LoadingState.Error);
|
||||
});
|
||||
|
||||
it('upgrade error', async () => {
|
||||
// @ts-ignore
|
||||
axios.get.mockRejectedValue({ response: { status: 426 } });
|
||||
const wrapper = mountWithIntl(<UpgradeAssistantTabs />);
|
||||
await promisesToResolve();
|
||||
wrapper.update();
|
||||
// Should display an informative message if the cluster is currently mid-upgrade
|
||||
expect(wrapper.find('EuiEmptyPrompt').exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,11 +5,17 @@
|
|||
*/
|
||||
|
||||
import axios from 'axios';
|
||||
import { findIndex, set } from 'lodash';
|
||||
import { findIndex, get, set } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import { EuiTabbedContent, EuiTabbedContentTab } from '@elastic/eui';
|
||||
import { injectI18n } from '@kbn/i18n/react';
|
||||
import {
|
||||
EuiEmptyPrompt,
|
||||
EuiPageContent,
|
||||
EuiPageContentBody,
|
||||
EuiTabbedContent,
|
||||
EuiTabbedContentTab,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage, injectI18n } from '@kbn/i18n/react';
|
||||
|
||||
import chrome from 'ui/chrome';
|
||||
import { kfetch } from 'ui/kfetch';
|
||||
|
@ -26,6 +32,7 @@ interface TabsState {
|
|||
checkupData?: UpgradeAssistantStatus;
|
||||
selectedTabIndex: number;
|
||||
telemetryState: TelemetryState;
|
||||
upgradeableCluster: boolean;
|
||||
}
|
||||
|
||||
export class UpgradeAssistantTabsUI extends React.Component<
|
||||
|
@ -37,6 +44,7 @@ export class UpgradeAssistantTabsUI extends React.Component<
|
|||
|
||||
this.state = {
|
||||
loadingState: LoadingState.Loading,
|
||||
upgradeableCluster: true,
|
||||
selectedTabIndex: 0,
|
||||
telemetryState: TelemetryState.Complete,
|
||||
};
|
||||
|
@ -50,9 +58,38 @@ export class UpgradeAssistantTabsUI extends React.Component<
|
|||
}
|
||||
|
||||
public render() {
|
||||
const { selectedTabIndex, telemetryState } = this.state;
|
||||
const { selectedTabIndex, telemetryState, upgradeableCluster } = this.state;
|
||||
const tabs = this.tabs;
|
||||
|
||||
if (!upgradeableCluster) {
|
||||
return (
|
||||
<EuiPageContent>
|
||||
<EuiPageContentBody>
|
||||
<EuiEmptyPrompt
|
||||
iconType="logoElasticsearch"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingTitle"
|
||||
defaultMessage="Your cluster is upgrading"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.upgradeAssistant.tabs.upgradingInterstitial.upgradingDescription"
|
||||
defaultMessage="One or more Elasticsearch nodes have a newer version of
|
||||
Elasticsearch than Kibana. Once all your nodes are upgraded, install the latest version of Kibana."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</EuiPageContentBody>
|
||||
</EuiPageContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiTabbedContent
|
||||
data-test-subj={
|
||||
|
@ -94,7 +131,14 @@ export class UpgradeAssistantTabsUI extends React.Component<
|
|||
checkupData: resp.data,
|
||||
});
|
||||
} catch (e) {
|
||||
this.setState({ loadingState: LoadingState.Error, loadingError: e });
|
||||
if (get(e, 'response.status') === 426) {
|
||||
this.setState({
|
||||
loadingState: LoadingState.Success,
|
||||
upgradeableCluster: false,
|
||||
});
|
||||
} else {
|
||||
this.setState({ loadingState: LoadingState.Error, loadingError: e });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export const mockedEsVersionPrecheckMethod = jest.fn().mockResolvedValue(true);
|
||||
export const EsVersionPrecheck = {
|
||||
assign: 'esVersionCheck',
|
||||
method: mockedEsVersionPrecheckMethod,
|
||||
};
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SemVer } from 'semver';
|
||||
import { CURRENT_VERSION } from '../../common/version';
|
||||
import { getAllNodeVersions, verifyAllMatchKibanaVersion } from './es_version_precheck';
|
||||
|
||||
describe('getAllNodeVersions', () => {
|
||||
it('returns a list of unique node versions', async () => {
|
||||
const callCluster = jest.fn().mockResolvedValue({
|
||||
nodes: {
|
||||
node1: { version: '7.0.0' },
|
||||
node2: { version: '7.0.0' },
|
||||
node3: { version: '6.0.0' },
|
||||
},
|
||||
});
|
||||
|
||||
await expect(getAllNodeVersions(callCluster)).resolves.toEqual([
|
||||
new SemVer('6.0.0'),
|
||||
new SemVer('7.0.0'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('verifyAllMatchKibanaVersion', () => {
|
||||
it('throws if any are higher version', () => {
|
||||
expect(() =>
|
||||
verifyAllMatchKibanaVersion([new SemVer('99999.0.0')])
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"There are some nodes running a different version of Elasticsearch"`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if any are lower version', () => {
|
||||
expect(() =>
|
||||
verifyAllMatchKibanaVersion([new SemVer('0.0.0')])
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"There are some nodes running a different version of Elasticsearch"`
|
||||
);
|
||||
});
|
||||
|
||||
it('does not throw if all are same major', () => {
|
||||
const versions = [
|
||||
CURRENT_VERSION,
|
||||
CURRENT_VERSION.inc('minor'),
|
||||
CURRENT_VERSION.inc('minor').inc('minor'),
|
||||
];
|
||||
|
||||
expect(() => verifyAllMatchKibanaVersion(versions)).not.toThrow();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import { Request, RouteOptionsPreObject } from 'hapi';
|
||||
import { uniq } from 'lodash';
|
||||
import { SemVer } from 'semver';
|
||||
|
||||
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
|
||||
import { CURRENT_VERSION } from '../../common/version';
|
||||
|
||||
/**
|
||||
* Returns an array of all the unique Elasticsearch Node Versions in the Elasticsearch cluster.
|
||||
* @param request
|
||||
*/
|
||||
export const getAllNodeVersions = async (callCluster: CallCluster) => {
|
||||
// Get the version information for all nodes in the cluster.
|
||||
const { nodes } = (await callCluster('nodes.info', {
|
||||
filterPath: 'nodes.*.version',
|
||||
})) as { nodes: { [nodeId: string]: { version: string } } };
|
||||
|
||||
const versionStrings = Object.values(nodes).map(({ version }) => version);
|
||||
|
||||
return uniq(versionStrings)
|
||||
.sort()
|
||||
.map(version => new SemVer(version));
|
||||
};
|
||||
|
||||
export const verifyAllMatchKibanaVersion = (allNodeVersions: SemVer[]) => {
|
||||
// Determine if all nodes in the cluster are running the same major version as Kibana.
|
||||
const anyDifferentEsNodes = !!allNodeVersions.find(
|
||||
esNodeVersion => esNodeVersion.major !== CURRENT_VERSION.major
|
||||
);
|
||||
|
||||
if (anyDifferentEsNodes) {
|
||||
throw new Boom(`There are some nodes running a different version of Elasticsearch`, {
|
||||
// 426 means "Upgrade Required" and is used when semver compatibility is not met.
|
||||
statusCode: 426,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const EsVersionPrecheck = {
|
||||
assign: 'esVersionCheck',
|
||||
async method(request: Request) {
|
||||
const { callWithRequest } = request.server.plugins.elasticsearch.getCluster('admin');
|
||||
const callCluster = callWithRequest.bind(callWithRequest, request) as CallCluster;
|
||||
|
||||
const allNodeVersions = await getAllNodeVersions(callCluster);
|
||||
// This will throw if there is an issue
|
||||
verifyAllMatchKibanaVersion(allNodeVersions);
|
||||
|
||||
return true;
|
||||
},
|
||||
} as RouteOptionsPreObject;
|
|
@ -4,7 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import { Server } from 'hapi';
|
||||
|
||||
jest.mock('../lib/es_version_precheck');
|
||||
import { EsVersionPrecheck } from '../lib/es_version_precheck';
|
||||
import { registerClusterCheckupRoutes } from './cluster_checkup';
|
||||
|
||||
// Need to require to get mock on named export to work.
|
||||
|
@ -71,5 +75,18 @@ describe('cluster checkup API', () => {
|
|||
|
||||
expect(resp.statusCode).toEqual(500);
|
||||
});
|
||||
|
||||
it('returns a 426 if EsVersionCheck throws', async () => {
|
||||
(EsVersionPrecheck.method as jest.Mock).mockRejectedValue(
|
||||
new Boom(`blah`, { statusCode: 426 })
|
||||
);
|
||||
|
||||
const resp = await server.inject({
|
||||
method: 'GET',
|
||||
url: '/api/upgrade_assistant/status',
|
||||
});
|
||||
|
||||
expect(resp.statusCode).toEqual(426);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@ import Boom from 'boom';
|
|||
import { Legacy } from 'kibana';
|
||||
|
||||
import { getUpgradeAssistantStatus } from '../lib/es_migration_apis';
|
||||
import { EsVersionPrecheck } from '../lib/es_version_precheck';
|
||||
|
||||
export function registerClusterCheckupRoutes(server: Legacy.Server) {
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
|
||||
|
@ -16,6 +17,9 @@ export function registerClusterCheckupRoutes(server: Legacy.Server) {
|
|||
server.route({
|
||||
path: '/api/upgrade_assistant/status',
|
||||
method: 'GET',
|
||||
options: {
|
||||
pre: [EsVersionPrecheck],
|
||||
},
|
||||
async handler(request) {
|
||||
try {
|
||||
return await getUpgradeAssistantStatus(callWithRequest, request, isCloudEnabled);
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
*/
|
||||
|
||||
import { Server } from 'hapi';
|
||||
|
||||
jest.mock('../lib/es_version_precheck');
|
||||
import { registerDeprecationLoggingRoutes } from './deprecation_logging';
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
getDeprecationLoggingStatus,
|
||||
setDeprecationLogging,
|
||||
} from '../lib/es_deprecation_logging_apis';
|
||||
import { EsVersionPrecheck } from '../lib/es_version_precheck';
|
||||
|
||||
export function registerDeprecationLoggingRoutes(server: Legacy.Server) {
|
||||
const { callWithRequest } = server.plugins.elasticsearch.getCluster('admin');
|
||||
|
@ -19,6 +20,9 @@ export function registerDeprecationLoggingRoutes(server: Legacy.Server) {
|
|||
server.route({
|
||||
path: '/api/upgrade_assistant/deprecation_logging',
|
||||
method: 'GET',
|
||||
options: {
|
||||
pre: [EsVersionPrecheck],
|
||||
},
|
||||
async handler(request) {
|
||||
try {
|
||||
return await getDeprecationLoggingStatus(callWithRequest, request);
|
||||
|
@ -32,6 +36,7 @@ export function registerDeprecationLoggingRoutes(server: Legacy.Server) {
|
|||
path: '/api/upgrade_assistant/deprecation_logging',
|
||||
method: 'PUT',
|
||||
options: {
|
||||
pre: [EsVersionPrecheck],
|
||||
validate: {
|
||||
payload: Joi.object({
|
||||
isEnabled: Joi.boolean(),
|
||||
|
|
|
@ -18,6 +18,7 @@ const mockReindexService = {
|
|||
cancelReindexing: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('../lib/es_version_precheck');
|
||||
jest.mock('../lib/reindexing', () => {
|
||||
return {
|
||||
reindexServiceFactory: () => mockReindexService,
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Server } from 'hapi';
|
|||
import { CallCluster } from 'src/legacy/core_plugins/elasticsearch';
|
||||
import { SavedObjectsClient } from 'src/legacy/server/saved_objects';
|
||||
import { ReindexStatus } from '../../common/types';
|
||||
import { EsVersionPrecheck } from '../lib/es_version_precheck';
|
||||
import { reindexServiceFactory, ReindexWorker } from '../lib/reindexing';
|
||||
import { CredentialStore } from '../lib/reindexing/credential_store';
|
||||
import { reindexActionsFactory } from '../lib/reindexing/reindex_actions';
|
||||
|
@ -65,6 +66,9 @@ export function registerReindexIndicesRoutes(
|
|||
server.route({
|
||||
path: `${BASE_PATH}/{indexName}`,
|
||||
method: 'POST',
|
||||
options: {
|
||||
pre: [EsVersionPrecheck],
|
||||
},
|
||||
async handler(request) {
|
||||
const client = request.getSavedObjectsClient();
|
||||
const { indexName } = request.params;
|
||||
|
@ -106,6 +110,9 @@ export function registerReindexIndicesRoutes(
|
|||
server.route({
|
||||
path: `${BASE_PATH}/{indexName}`,
|
||||
method: 'GET',
|
||||
options: {
|
||||
pre: [EsVersionPrecheck],
|
||||
},
|
||||
async handler(request) {
|
||||
const client = request.getSavedObjectsClient();
|
||||
const { indexName } = request.params;
|
||||
|
@ -142,6 +149,9 @@ export function registerReindexIndicesRoutes(
|
|||
server.route({
|
||||
path: `${BASE_PATH}/{indexName}/cancel`,
|
||||
method: 'POST',
|
||||
options: {
|
||||
pre: [EsVersionPrecheck],
|
||||
},
|
||||
async handler(request) {
|
||||
const client = request.getSavedObjectsClient();
|
||||
const { indexName } = request.params;
|
||||
|
|
Loading…
Reference in a new issue