diff --git a/package.json b/package.json
index 97bc80005c09b..24909111afc95 100644
--- a/package.json
+++ b/package.json
@@ -615,7 +615,7 @@
"@types/redux-actions": "^2.6.1",
"@types/redux-logger": "^3.0.8",
"@types/seedrandom": ">=2.0.0 <4.0.0",
- "@types/selenium-webdriver": "^4.0.16",
+ "@types/selenium-webdriver": "^4.0.18",
"@types/semver": "^7",
"@types/set-value": "^2.0.0",
"@types/sinon": "^7.0.13",
@@ -672,7 +672,7 @@
"callsites": "^3.1.0",
"chai": "3.5.0",
"chance": "1.0.18",
- "chromedriver": "^97.0.2",
+ "chromedriver": "^98.0.0",
"clean-webpack-plugin": "^3.0.0",
"cmd-shim": "^2.1.0",
"compression-webpack-plugin": "^4.0.0",
diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js
index ecd64c051a5a4..ec689e67cefcb 100644
--- a/packages/kbn-es/src/cluster.js
+++ b/packages/kbn-es/src/cluster.js
@@ -252,6 +252,7 @@ exports.Cluster = class Cluster {
const esArgs = [
'action.destructive_requires_name=true',
'ingest.geoip.downloader.enabled=false',
+ 'cluster.routing.allocation.disk.threshold_enabled=false',
].concat(options.esArgs || []);
// Add to esArgs if ssl is enabled
diff --git a/packages/kbn-es/src/integration_tests/cluster.test.js b/packages/kbn-es/src/integration_tests/cluster.test.js
index c4b1512331483..f3bdd49103614 100644
--- a/packages/kbn-es/src/integration_tests/cluster.test.js
+++ b/packages/kbn-es/src/integration_tests/cluster.test.js
@@ -309,6 +309,7 @@ describe('#start(installPath)', () => {
Array [
"action.destructive_requires_name=true",
"ingest.geoip.downloader.enabled=false",
+ "cluster.routing.allocation.disk.threshold_enabled=false",
],
undefined,
Object {
@@ -387,6 +388,7 @@ describe('#run()', () => {
Array [
"action.destructive_requires_name=true",
"ingest.geoip.downloader.enabled=false",
+ "cluster.routing.allocation.disk.threshold_enabled=false",
],
undefined,
Object {
diff --git a/x-pack/plugins/ml/common/types/annotations.ts b/x-pack/plugins/ml/common/types/annotations.ts
index dbc146c1175d8..57b7551c2308a 100644
--- a/x-pack/plugins/ml/common/types/annotations.ts
+++ b/x-pack/plugins/ml/common/types/annotations.ts
@@ -6,10 +6,10 @@
*/
// The Annotation interface is based on annotation documents stored in the
-// `.ml-annotations-6` index, accessed via the `.ml-annotations-[read|write]` aliases.
+// `.ml-annotations-*` index, accessed via the `.ml-annotations-[read|write]` aliases.
// Annotation document mapping:
-// PUT .ml-annotations-6
+// PUT .ml-annotations-000001
// {
// "mappings": {
// "annotation": {
@@ -54,8 +54,8 @@
// POST /_aliases
// {
// "actions" : [
-// { "add" : { "index" : ".ml-annotations-6", "alias" : ".ml-annotations-read" } },
-// { "add" : { "index" : ".ml-annotations-6", "alias" : ".ml-annotations-write" } }
+// { "add" : { "index" : ".ml-annotations-000001", "alias" : ".ml-annotations-read" } },
+// { "add" : { "index" : ".ml-annotations-000001", "alias" : ".ml-annotations-write" } }
// ]
// }
diff --git a/x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json b/x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json
index a0b8f6b242319..829b9c6581bea 100644
--- a/x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json
+++ b/x-pack/plugins/ml/server/models/annotation_service/__mocks__/get_annotations_response.json
@@ -15,8 +15,7 @@
"max_score": 0,
"hits": [
{
- "_index": ".ml-annotations-6",
- "_type": "doc",
+ "_index": ".ml-annotations-000001",
"_id": "T-CNvmgBQUJYQVn7TCPA",
"_score": 0,
"_source": {
@@ -32,8 +31,7 @@
}
},
{
- "_index": ".ml-annotations-6",
- "_type": "doc",
+ "_index": ".ml-annotations-000001",
"_id": "3lVpvmgB5xYzd3PM-MSe",
"_score": 0,
"_source": {
diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts
index 975070e92a7ec..5663ee6ac79ca 100644
--- a/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts
+++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.test.ts
@@ -41,7 +41,7 @@ describe('annotation_service', () => {
const annotationMockId = 'mockId';
const deleteParamsMock: DeleteParams = {
- index: '.ml-annotations-6',
+ index: '.ml-annotations-000001',
id: annotationMockId,
refresh: 'wait_for',
};
diff --git a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts
index 227bcee256fb4..f018d44f53c84 100644
--- a/x-pack/plugins/ml/server/models/annotation_service/annotation.ts
+++ b/x-pack/plugins/ml/server/models/annotation_service/annotation.ts
@@ -72,6 +72,31 @@ export interface DeleteParams {
}
export function annotationProvider({ asInternalUser }: IScopedClusterClient) {
+ // Find the index the annotation is stored in.
+ async function fetchAnnotationIndex(id: string) {
+ const searchParams: estypes.SearchRequest = {
+ index: ML_ANNOTATIONS_INDEX_ALIAS_READ,
+ size: 1,
+ body: {
+ query: {
+ ids: {
+ values: [id],
+ },
+ },
+ },
+ };
+
+ const { body } = await asInternalUser.search(searchParams);
+ const totalCount =
+ typeof body.hits.total === 'number' ? body.hits.total : body.hits.total!.value;
+
+ if (totalCount === 0) {
+ throw Boom.notFound(`Cannot find annotation with ID ${id}`);
+ }
+
+ return body.hits.hits[0]._index;
+ }
+
async function indexAnnotation(annotation: Annotation, username: string) {
if (isAnnotation(annotation) === false) {
// No need to translate, this will not be exposed in the UI.
@@ -95,6 +120,8 @@ export function annotationProvider({ asInternalUser }: IScopedClusterClient) {
if (typeof annotation._id !== 'undefined') {
params.id = annotation._id;
+ params.index = await fetchAnnotationIndex(annotation._id);
+ params.require_alias = false;
delete params.body._id;
delete params.body.key;
}
@@ -385,28 +412,7 @@ export function annotationProvider({ asInternalUser }: IScopedClusterClient) {
}
async function deleteAnnotation(id: string) {
- // Find the index the annotation is stored in.
- const searchParams: estypes.SearchRequest = {
- index: ML_ANNOTATIONS_INDEX_ALIAS_READ,
- size: 1,
- body: {
- query: {
- ids: {
- values: [id],
- },
- },
- },
- };
-
- const { body } = await asInternalUser.search(searchParams);
- const totalCount =
- typeof body.hits.total === 'number' ? body.hits.total : body.hits.total.value;
-
- if (totalCount === 0) {
- throw Boom.notFound(`Cannot find annotation with ID ${id}`);
- }
-
- const index = body.hits.hits[0]._index;
+ const index = await fetchAnnotationIndex(id);
const deleteParams: DeleteParams = {
index,
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts
index 25742958aa243..559b01f1cc6ab 100644
--- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts
@@ -18,7 +18,6 @@ const defaultReindexStatusMeta: ReindexStatusResponse['meta'] = {
aliases: [],
};
-// Note: The reindexing flyout UX is subject to change; more tests should be added here once functionality is built out
describe('Reindex deprecation flyout', () => {
let testBed: ElasticsearchTestBed;
const { server, httpRequestsMockHelpers } = setupEnvironment();
@@ -50,6 +49,7 @@ describe('Reindex deprecation flyout', () => {
aliases: [],
},
});
+ httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([]);
await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
@@ -236,4 +236,32 @@ describe('Reindex deprecation flyout', () => {
expect(find('reindexChecklistTitle').text()).toEqual('Reindexing process');
});
});
+
+ describe('low disk space', () => {
+ it('renders a warning callout if nodes detected with low disk space', async () => {
+ httpRequestsMockHelpers.setLoadNodeDiskSpaceResponse([
+ {
+ nodeId: '9OFkjpAKS_aPzJAuEOSg7w',
+ nodeName: 'MacBook-Pro.local',
+ available: '25%',
+ lowDiskWatermarkSetting: '50%',
+ },
+ ]);
+
+ await act(async () => {
+ testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
+ });
+
+ testBed.component.update();
+ const { actions, find } = testBed;
+
+ await actions.table.clickDeprecationRowAt('reindex', 0);
+
+ expect(find('lowDiskSpaceCallout').text()).toContain('Nodes with low disk space');
+ expect(find('impactedNodeListItem').length).toEqual(1);
+ expect(find('impactedNodeListItem').at(0).text()).toContain(
+ 'MacBook-Pro.local (25% available)'
+ );
+ });
+ });
});
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 7599491ed244f..7e417b5d40936 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
@@ -214,6 +214,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};
+ const setLoadNodeDiskSpaceResponse = (response?: object, error?: ResponseError) => {
+ const status = error ? error.statusCode || 400 : 200;
+ const body = error ? error : response;
+
+ server.respondWith('GET', `${API_BASE_PATH}/node_disk_space`, [
+ status,
+ { 'Content-Type': 'application/json' },
+ JSON.stringify(body),
+ ]);
+ };
+
const setClusterSettingsResponse = (response?: object, error?: ResponseError) => {
const status = error ? error.statusCode || 400 : 200;
const body = error ? error : response;
@@ -242,6 +253,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setLoadMlUpgradeModeResponse,
setGetUpgradeStatusResponse,
setLoadRemoteClustersResponse,
+ setLoadNodeDiskSpaceResponse,
setClusterSettingsResponse,
};
};
diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/logs_step/logs_step.test.tsx b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/logs_step/logs_step.test.tsx
new file mode 100644
index 0000000000000..31cbb8ebef456
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/overview/logs_step/logs_step.test.tsx
@@ -0,0 +1,159 @@
+/*
+ * 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 { act } from 'react-dom/test-utils';
+import { DEPRECATION_LOGS_INDEX } from '../../../../common/constants';
+import { setupEnvironment } from '../../helpers';
+import { OverviewTestBed, setupOverviewPage } from '../overview.helpers';
+
+describe('Overview - Logs Step', () => {
+ let testBed: OverviewTestBed;
+
+ const { server, httpRequestsMockHelpers } = setupEnvironment();
+
+ afterAll(() => {
+ server.restore();
+ });
+
+ describe('error state', () => {
+ beforeEach(async () => {
+ const error = {
+ statusCode: 500,
+ error: 'Internal server error',
+ message: 'Internal server error',
+ };
+
+ httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse(undefined, error);
+
+ await act(async () => {
+ testBed = await setupOverviewPage();
+ });
+
+ testBed.component.update();
+ });
+
+ test('is rendered', () => {
+ const { exists } = testBed;
+ expect(exists('deprecationLogsErrorCallout')).toBe(true);
+ expect(exists('deprecationLogsRetryButton')).toBe(true);
+ });
+ });
+
+ describe('success state', () => {
+ describe('logging enabled', () => {
+ beforeEach(() => {
+ httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
+ isDeprecationLogIndexingEnabled: true,
+ isDeprecationLoggingEnabled: true,
+ });
+ });
+
+ test('renders step as complete when a user has 0 logs', async () => {
+ httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({
+ count: 0,
+ });
+
+ await act(async () => {
+ testBed = await setupOverviewPage();
+ });
+
+ const { component, exists } = testBed;
+
+ component.update();
+
+ expect(exists('logsStep-complete')).toBe(true);
+ });
+
+ test('renders step as incomplete when a user has >0 logs', async () => {
+ httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({
+ count: 10,
+ });
+
+ await act(async () => {
+ testBed = await setupOverviewPage();
+ });
+
+ const { component, exists } = testBed;
+
+ component.update();
+
+ expect(exists('logsStep-incomplete')).toBe(true);
+ });
+
+ test('renders deprecation issue count and button to view logs', async () => {
+ httpRequestsMockHelpers.setLoadDeprecationLogsCountResponse({
+ count: 10,
+ });
+
+ await act(async () => {
+ testBed = await setupOverviewPage();
+ });
+
+ const { component, find } = testBed;
+
+ component.update();
+
+ expect(find('logsCountDescription').text()).toContain('You have 10 deprecation issues');
+ expect(find('viewLogsLink').text()).toContain('View logs');
+ });
+ });
+
+ describe('logging disabled', () => {
+ beforeEach(async () => {
+ httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
+ isDeprecationLogIndexingEnabled: false,
+ isDeprecationLoggingEnabled: true,
+ });
+
+ await act(async () => {
+ testBed = await setupOverviewPage();
+ });
+
+ const { component } = testBed;
+
+ component.update();
+ });
+
+ test('renders button to enable logs', () => {
+ const { find, exists } = testBed;
+
+ expect(exists('logsCountDescription')).toBe(false);
+ expect(find('enableLogsLink').text()).toContain('Enable logging');
+ });
+ });
+ });
+
+ describe('privileges', () => {
+ beforeEach(async () => {
+ httpRequestsMockHelpers.setLoadDeprecationLoggingResponse({
+ isDeprecationLogIndexingEnabled: true,
+ isDeprecationLoggingEnabled: true,
+ });
+
+ await act(async () => {
+ testBed = await setupOverviewPage({
+ privileges: {
+ hasAllPrivileges: true,
+ missingPrivileges: {
+ index: [DEPRECATION_LOGS_INDEX],
+ },
+ },
+ });
+ });
+
+ const { component } = testBed;
+
+ component.update();
+ });
+
+ test('warns the user of missing index privileges', () => {
+ const { exists } = testBed;
+
+ expect(exists('missingPrivilegesCallout')).toBe(true);
+ });
+ });
+});
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/index.ts
index c0af5524e3a14..ffc1a60e8a2fb 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/index.ts
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/fix_deprecation_logs/index.ts
@@ -6,3 +6,4 @@
*/
export { FixDeprecationLogs } from './fix_deprecation_logs';
+export { useDeprecationLogging } from './use_deprecation_logging';
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/index.ts
index 336aa14642f7d..978fb18cbe2a7 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/index.ts
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecation_logs/index.ts
@@ -6,3 +6,4 @@
*/
export { EsDeprecationLogs } from './es_deprecation_logs';
+export { useDeprecationLogging } from './fix_deprecation_logs';
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.test.tsx
index d8e95cd2456de..e181ca97d21fa 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.test.tsx
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.test.tsx
@@ -23,6 +23,9 @@ jest.mock('../../../../../app_context', () => {
useAppContext: () => {
return {
services: {
+ api: {
+ useLoadNodeDiskSpace: () => [],
+ },
core: {
docLinks: docLinksServiceMock.createStartContract(),
},
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.tsx
index e416198a69318..3b64a3e050345 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.tsx
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/es_deprecations/deprecation_types/reindex/flyout/checklist_step.tsx
@@ -79,6 +79,7 @@ export const ChecklistFlyoutStep: React.FunctionComponent<{
}> = ({ closeFlyout, reindexState, startReindex, cancelReindex }) => {
const {
services: {
+ api,
core: { docLinks },
},
} = useAppContext();
@@ -89,6 +90,8 @@ export const ChecklistFlyoutStep: React.FunctionComponent<{
const hasFetchFailed = status === ReindexStatus.fetchFailed;
const hasReindexingFailed = status === ReindexStatus.failed;
+ const { data: nodes } = api.useLoadNodeDiskSpace();
+
return (
@@ -107,6 +110,48 @@ export const ChecklistFlyoutStep: React.FunctionComponent<{
/>
)}
+
+ {nodes && nodes.length > 0 && (
+ <>
+
+ }
+ >
+ <>
+
+
+
+
+
+ {nodes.map(({ nodeName, available, nodeId }) => (
+ -
+
+
+ ))}
+
+ >
+
+
+ >
+ )}
+
{(hasFetchFailed || hasReindexingFailed) && (
<>
>
)}
+
= ({ setIsComplete }) => {
);
};
-interface CustomProps {
- navigateToEsDeprecationLogs: () => void;
-}
-
-const AccessDeprecationLogsMessage = ({ navigateToEsDeprecationLogs }: CustomProps) => {
- return (
-
- {({ hasPrivileges, isLoading }) => {
- if (isLoading || !hasPrivileges) {
- // Don't show the message with the link to access deprecation logs
- // to users who can't access the UI anyways.
- return null;
- }
-
- return (
-
- {i18n.translate('xpack.upgradeAssistant.overview.esDeprecationLogsLink', {
- defaultMessage: 'Elasticsearch deprecation logs',
- })}
-
- ),
- }}
- />
- );
- }}
-
- );
-};
-
export const getFixIssuesStep = ({
isComplete,
setIsComplete,
- navigateToEsDeprecationLogs,
-}: OverviewStepProps & CustomProps): EuiStepProps => {
+}: OverviewStepProps): EuiStepProps => {
const status = isComplete ? 'complete' : 'incomplete';
return {
@@ -105,14 +65,7 @@ export const getFixIssuesStep = ({
- ),
- }}
+ defaultMessage="You must resolve any critical Elasticsearch and Kibana configuration issues before upgrading to Elastic 8.x. Ignoring warnings might result in differences in behavior after you upgrade."
/>
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/logs_step/index.ts b/x-pack/plugins/upgrade_assistant/public/application/components/overview/logs_step/index.ts
new file mode 100644
index 0000000000000..96e6cf4d71c08
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/logs_step/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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.
+ */
+
+export { getLogsStep } from './logs_step';
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/logs_step/logs_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/logs_step/logs_step.tsx
new file mode 100644
index 0000000000000..6f4bfcfa2791e
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/logs_step/logs_step.tsx
@@ -0,0 +1,243 @@
+/*
+ * 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 React, { useEffect } from 'react';
+
+import {
+ EuiText,
+ EuiSpacer,
+ EuiButton,
+ EuiCallOut,
+ EuiLoadingContent,
+ EuiCode,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedDate, FormattedTime, FormattedMessage } from '@kbn/i18n/react';
+import type { EuiStepProps } from '@elastic/eui/src/components/steps/step';
+
+import { DEPRECATION_LOGS_INDEX } from '../../../../../common/constants';
+import { WithPrivileges, MissingPrivileges } from '../../../../shared_imports';
+import { useAppContext } from '../../../app_context';
+import { loadLogsCheckpoint } from '../../../lib/logs_checkpoint';
+import type { OverviewStepProps } from '../../types';
+import { useDeprecationLogging } from '../../es_deprecation_logs';
+
+const i18nTexts = {
+ logsStepTitle: i18n.translate('xpack.upgradeAssistant.overview.logsStep.title', {
+ defaultMessage: 'Address API deprecations',
+ }),
+ logsStepDescription: i18n.translate('xpack.upgradeAssistant.overview.logsStep.description', {
+ defaultMessage: `Review the Elasticsearch deprecation logs to ensure you're not using deprecated APIs.`,
+ }),
+ viewLogsButtonLabel: i18n.translate(
+ 'xpack.upgradeAssistant.overview.logsStep.viewLogsButtonLabel',
+ {
+ defaultMessage: 'View logs',
+ }
+ ),
+ enableLogsButtonLabel: i18n.translate(
+ 'xpack.upgradeAssistant.overview.logsStep.enableLogsButtonLabel',
+ {
+ defaultMessage: 'Enable logging',
+ }
+ ),
+ logsCountDescription: (deprecationCount: number, checkpoint: string) => (
+
+ {' '}
+
+ >
+ ),
+ }}
+ />
+ ),
+ missingPrivilegesTitle: i18n.translate(
+ 'xpack.upgradeAssistant.overview.logsStep.missingPrivilegesTitle',
+ {
+ defaultMessage: 'You require index privileges to analyze the deprecation logs',
+ }
+ ),
+ missingPrivilegesDescription: (privilegesMissing: MissingPrivileges) => (
+ {privilegesMissing?.index?.join(', ')}
+ ),
+ privilegesCount: privilegesMissing?.index?.length,
+ }}
+ />
+ ),
+ loadingError: i18n.translate('xpack.upgradeAssistant.overview.logsStep.loadingError', {
+ defaultMessage: 'An error occurred while retrieving the deprecation log count',
+ }),
+ retryButton: i18n.translate('xpack.upgradeAssistant.overview.logsStep.retryButton', {
+ defaultMessage: 'Try again',
+ }),
+};
+
+interface LogStepProps {
+ setIsComplete: (isComplete: boolean) => void;
+ hasPrivileges: boolean;
+ privilegesMissing: MissingPrivileges;
+ navigateToEsDeprecationLogs: () => void;
+}
+
+const LogStepDescription = () => (
+
+ {i18nTexts.logsStepDescription}
+
+);
+
+const LogsStep = ({
+ setIsComplete,
+ hasPrivileges,
+ privilegesMissing,
+ navigateToEsDeprecationLogs,
+}: LogStepProps) => {
+ const {
+ services: { api },
+ } = useAppContext();
+
+ const { isDeprecationLogIndexingEnabled } = useDeprecationLogging();
+
+ const checkpoint = loadLogsCheckpoint();
+
+ const {
+ data: logsCount,
+ error,
+ isLoading,
+ resendRequest,
+ isInitialRequest,
+ } = api.getDeprecationLogsCount(checkpoint);
+
+ useEffect(() => {
+ if (!isDeprecationLogIndexingEnabled) {
+ setIsComplete(false);
+ }
+
+ setIsComplete(logsCount?.count === 0);
+
+ // Depending upon setIsComplete would create an infinite loop.
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [isDeprecationLogIndexingEnabled, logsCount]);
+
+ if (hasPrivileges === false && isDeprecationLogIndexingEnabled) {
+ return (
+ <>
+
+
+
+
+
+ {i18nTexts.missingPrivilegesDescription(privilegesMissing)}
+
+ >
+ );
+ }
+
+ if (isLoading && isInitialRequest) {
+ return ;
+ }
+
+ if (hasPrivileges && error) {
+ return (
+
+
+ {error.statusCode} - {error.message}
+
+
+
+ {i18nTexts.retryButton}
+
+
+ );
+ }
+
+ return (
+ <>
+
+
+ {isDeprecationLogIndexingEnabled && logsCount ? (
+ <>
+
+
+
+
+ {i18nTexts.logsCountDescription(logsCount.count, checkpoint)}
+
+
+
+
+
+
+ {i18nTexts.viewLogsButtonLabel}
+
+ >
+ ) : (
+ <>
+
+
+
+ {i18nTexts.enableLogsButtonLabel}
+
+ >
+ )}
+
+ >
+ );
+};
+
+interface CustomProps {
+ navigateToEsDeprecationLogs: () => void;
+}
+
+export const getLogsStep = ({
+ isComplete,
+ setIsComplete,
+ navigateToEsDeprecationLogs,
+}: OverviewStepProps & CustomProps): EuiStepProps => {
+ const status = isComplete ? 'complete' : 'incomplete';
+
+ return {
+ status,
+ title: i18nTexts.logsStepTitle,
+ 'data-test-subj': `logsStep-${status}`,
+ children: (
+
+ {({ hasPrivileges, isLoading, privilegesMissing }) => (
+
+ )}
+
+ ),
+ };
+};
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx
index 567b3b7bbea0d..b6230393dacb8 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx
+++ b/x-pack/plugins/upgrade_assistant/public/application/components/overview/overview.tsx
@@ -28,8 +28,9 @@ import { getBackupStep } from './backup_step';
import { getFixIssuesStep } from './fix_issues_step';
import { getUpgradeStep } from './upgrade_step';
import { getMigrateSystemIndicesStep } from './migrate_system_indices';
+import { getLogsStep } from './logs_step';
-type OverviewStep = 'backup' | 'migrate_system_indices' | 'fix_issues';
+type OverviewStep = 'backup' | 'migrate_system_indices' | 'fix_issues' | 'logs';
export const Overview = withRouter(({ history }: RouteComponentProps) => {
const {
@@ -52,6 +53,7 @@ export const Overview = withRouter(({ history }: RouteComponentProps) => {
backup: false,
migrate_system_indices: false,
fix_issues: false,
+ logs: false,
});
const isStepComplete = (step: OverviewStep) => completedStepsMap[step];
@@ -114,6 +116,10 @@ export const Overview = withRouter(({ history }: RouteComponentProps) => {
getFixIssuesStep({
isComplete: isStepComplete('fix_issues'),
setIsComplete: setCompletedStep.bind(null, 'fix_issues'),
+ }),
+ getLogsStep({
+ isComplete: isStepComplete('logs'),
+ setIsComplete: setCompletedStep.bind(null, 'logs'),
navigateToEsDeprecationLogs: () => history.push('/es_deprecation_logs'),
}),
getUpgradeStep(),
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 cdf5cb5fa57dc..f55a6c04c640e 100644
--- a/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts
+++ b/x-pack/plugins/upgrade_assistant/public/application/lib/api.ts
@@ -256,6 +256,20 @@ export class ApiService {
method: 'get',
});
}
+
+ public useLoadNodeDiskSpace() {
+ return this.useRequest<
+ Array<{
+ nodeId: string;
+ nodeName: string;
+ available: string;
+ lowDiskWatermarkSetting: string;
+ }>
+ >({
+ path: `${API_BASE_PATH}/node_disk_space`,
+ method: 'get',
+ });
+ }
}
export const apiService = new ApiService();
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/node_disk_space.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/node_disk_space.test.ts
new file mode 100644
index 0000000000000..7d4d8da1c585e
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/routes/node_disk_space.test.ts
@@ -0,0 +1,258 @@
+/*
+ * 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 { kibanaResponseFactory } from 'src/core/server';
+import { createMockRouter, MockRouter, routeHandlerContextMock } from './__mocks__/routes.mock';
+import { createRequestMock } from './__mocks__/request.mock';
+import { handleEsError } from '../shared_imports';
+
+jest.mock('../lib/es_version_precheck', () => ({
+ versionCheckHandlerWrapper: (a: any) => a,
+}));
+
+import { registerNodeDiskSpaceRoute } from './node_disk_space';
+
+describe('Disk space API', () => {
+ let mockRouter: MockRouter;
+ let routeDependencies: any;
+
+ beforeEach(() => {
+ mockRouter = createMockRouter();
+ routeDependencies = {
+ router: mockRouter,
+ lib: { handleEsError },
+ };
+ registerNodeDiskSpaceRoute(routeDependencies);
+ });
+
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
+ describe('GET /api/upgrade_assistant/node_disk_space', () => {
+ beforeEach(() => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.nodes.stats as jest.Mock
+ ).mockResolvedValue({
+ body: {
+ nodes: {
+ '1YOaoS9lTNOiTxR1uzSgRA': {
+ name: 'node_name',
+ fs: {
+ total: {
+ // Keeping these numbers (inaccurately) small so it's easier to reason the math when scanning through :)
+ total_in_bytes: 100,
+ available_in_bytes: 20,
+ },
+ },
+ },
+ },
+ },
+ });
+ });
+
+ it('returns the default low watermark disk usage setting', async () => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
+ .getSettings as jest.Mock
+ ).mockResolvedValue({
+ body: {
+ defaults: {
+ 'cluster.routing.allocation.disk.watermark.low': '75%',
+ },
+ transient: {},
+ persistent: {},
+ },
+ });
+
+ const resp = await routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/node_disk_space',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
+
+ expect(resp.status).toEqual(200);
+ expect(resp.payload).toEqual([
+ {
+ nodeName: 'node_name',
+ nodeId: '1YOaoS9lTNOiTxR1uzSgRA',
+ available: '20%',
+ lowDiskWatermarkSetting: '75%',
+ },
+ ]);
+ });
+
+ it('returns the persistent low watermark disk usage setting', async () => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
+ .getSettings as jest.Mock
+ ).mockResolvedValue({
+ body: {
+ defaults: {},
+ transient: {},
+ persistent: { 'cluster.routing.allocation.disk.watermark.low': '75%' },
+ },
+ });
+
+ const resp = await routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/node_disk_space',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
+
+ expect(resp.status).toEqual(200);
+ expect(resp.payload).toEqual([
+ {
+ nodeName: 'node_name',
+ nodeId: '1YOaoS9lTNOiTxR1uzSgRA',
+ available: '20%',
+ lowDiskWatermarkSetting: '75%',
+ },
+ ]);
+ });
+
+ it('returns the transient low watermark disk usage setting', async () => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
+ .getSettings as jest.Mock
+ ).mockResolvedValue({
+ body: {
+ defaults: {},
+ transient: { 'cluster.routing.allocation.disk.watermark.low': '80%' },
+ persistent: { 'cluster.routing.allocation.disk.watermark.low': '85%' },
+ },
+ });
+
+ const resp = await routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/node_disk_space',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
+
+ expect(resp.status).toEqual(200);
+ expect(resp.payload).toEqual([
+ {
+ nodeName: 'node_name',
+ nodeId: '1YOaoS9lTNOiTxR1uzSgRA',
+ available: '20%',
+ lowDiskWatermarkSetting: '80%',
+ },
+ ]);
+ });
+
+ it('returns nodes with low disk space when low watermark disk usage setting is bytes value', async () => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
+ .getSettings as jest.Mock
+ ).mockResolvedValue({
+ body: {
+ defaults: {
+ 'cluster.routing.allocation.disk.watermark.low': '80b',
+ },
+ transient: {},
+ persistent: {},
+ },
+ });
+
+ const resp = await routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/node_disk_space',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
+
+ expect(resp.status).toEqual(200);
+ expect(resp.payload).toEqual([
+ {
+ nodeName: 'node_name',
+ nodeId: '1YOaoS9lTNOiTxR1uzSgRA',
+ available: '20%',
+ lowDiskWatermarkSetting: '80b',
+ },
+ ]);
+ });
+
+ it('returns empty array if low watermark disk usage setting is undefined', async () => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
+ .getSettings as jest.Mock
+ ).mockResolvedValue({
+ body: {
+ defaults: {},
+ transient: {},
+ persistent: {},
+ },
+ });
+
+ const resp = await routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/node_disk_space',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
+
+ expect(resp.status).toEqual(200);
+ expect(resp.payload).toEqual([]);
+ });
+
+ it('returns empty array if nodes have not reached low disk usage', async () => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
+ .getSettings as jest.Mock
+ ).mockResolvedValue({
+ body: {
+ defaults: {
+ 'cluster.routing.allocation.disk.watermark.low': '10%',
+ },
+ transient: {},
+ persistent: {},
+ },
+ });
+
+ const resp = await routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/node_disk_space',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory);
+
+ expect(resp.status).toEqual(200);
+ expect(resp.payload).toEqual([]);
+ });
+
+ describe('Error handling', () => {
+ it('returns an error if cluster.getSettings throws', async () => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
+ .getSettings as jest.Mock
+ ).mockRejectedValue(new Error('scary error!'));
+ await expect(
+ routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/node_disk_space',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory)
+ ).rejects.toThrow('scary error!');
+ });
+
+ it('returns an error if node.stats throws', async () => {
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster
+ .getSettings as jest.Mock
+ ).mockResolvedValue({
+ body: {
+ defaults: {
+ 'cluster.routing.allocation.disk.watermark.low': '85%',
+ },
+ transient: {},
+ persistent: {},
+ },
+ });
+
+ (
+ routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.nodes.stats as jest.Mock
+ ).mockRejectedValue(new Error('scary error!'));
+ await expect(
+ routeDependencies.router.getHandler({
+ method: 'get',
+ pathPattern: '/api/upgrade_assistant/node_disk_space',
+ })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory)
+ ).rejects.toThrow('scary error!');
+ });
+ });
+ });
+});
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/node_disk_space.ts b/x-pack/plugins/upgrade_assistant/server/routes/node_disk_space.ts
new file mode 100644
index 0000000000000..cd93742b83bc1
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/routes/node_disk_space.ts
@@ -0,0 +1,138 @@
+/*
+ * 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 { ClusterGetSettingsResponse } from '@elastic/elasticsearch/api/types';
+import { ByteSizeValue } from '@kbn/config-schema';
+import { API_BASE_PATH } from '../../common/constants';
+import { versionCheckHandlerWrapper } from '../lib/es_version_precheck';
+import { RouteDependencies } from '../types';
+
+interface NodeWithLowDiskSpace {
+ nodeId: string;
+ nodeName: string;
+ available: string;
+ lowDiskWatermarkSetting: string;
+}
+
+const getLowDiskWatermarkSetting = (
+ clusterSettings: ClusterGetSettingsResponse
+): string | undefined => {
+ const { defaults, persistent, transient } = clusterSettings;
+
+ const defaultLowDiskWatermarkSetting =
+ defaults && defaults['cluster.routing.allocation.disk.watermark.low'];
+ const transientLowDiskWatermarkSetting =
+ transient && transient['cluster.routing.allocation.disk.watermark.low'];
+ const persistentLowDiskWatermarkSetting =
+ persistent && persistent['cluster.routing.allocation.disk.watermark.low'];
+
+ // ES applies cluster settings in the following order of precendence: transient, persistent, default
+ if (transientLowDiskWatermarkSetting) {
+ return transientLowDiskWatermarkSetting;
+ } else if (persistentLowDiskWatermarkSetting) {
+ return persistentLowDiskWatermarkSetting;
+ } else if (defaultLowDiskWatermarkSetting) {
+ return defaultLowDiskWatermarkSetting;
+ }
+
+ // May be undefined if defined in elasticsearch.yml
+ return undefined;
+};
+
+export function registerNodeDiskSpaceRoute({ router, lib: { handleEsError } }: RouteDependencies) {
+ router.get(
+ {
+ path: `${API_BASE_PATH}/node_disk_space`,
+ validate: false,
+ },
+ versionCheckHandlerWrapper(
+ async (
+ {
+ core: {
+ elasticsearch: { client },
+ },
+ },
+ request,
+ response
+ ) => {
+ try {
+ const { body: clusterSettings } = await client.asCurrentUser.cluster.getSettings({
+ flat_settings: true,
+ include_defaults: true,
+ });
+
+ const lowDiskWatermarkSetting = getLowDiskWatermarkSetting(clusterSettings);
+
+ if (lowDiskWatermarkSetting) {
+ const { body: nodeStats } = await client.asCurrentUser.nodes.stats({
+ metric: 'fs',
+ });
+
+ const nodeIds = Object.keys(nodeStats.nodes);
+
+ const nodesWithLowDiskSpace: NodeWithLowDiskSpace[] = [];
+
+ nodeIds.forEach((nodeId) => {
+ const node = nodeStats.nodes[nodeId];
+ const byteStats = node.fs.total;
+ const { total_in_bytes: totalInBytes, available_in_bytes: availableInBytes } =
+ byteStats;
+
+ // Regex to determine if the low disk watermark setting is configured as a percentage value
+ // Elasticsearch accepts a percentage or bytes value
+ const isLowDiskWatermarkPercentage = /^(\d+|(\.\d+))(\.\d+)?%$/.test(
+ lowDiskWatermarkSetting!
+ );
+
+ if (isLowDiskWatermarkPercentage) {
+ const percentageAvailable = (availableInBytes / totalInBytes) * 100;
+ const rawLowDiskWatermarkPercentageValue = Number(
+ lowDiskWatermarkSetting!.replace('%', '')
+ );
+
+ // If the percentage available is < the low disk watermark setting, mark node as having low disk space
+ if (percentageAvailable < rawLowDiskWatermarkPercentageValue) {
+ nodesWithLowDiskSpace.push({
+ nodeId,
+ nodeName: node.name,
+ available: `${Math.round(percentageAvailable)}%`,
+ lowDiskWatermarkSetting: lowDiskWatermarkSetting!,
+ });
+ }
+ } else {
+ // If not a percentage value, assume user configured low disk watermark setting in bytes
+ const rawLowDiskWatermarkBytesValue = ByteSizeValue.parse(
+ lowDiskWatermarkSetting!
+ ).getValueInBytes();
+
+ const percentageAvailable = (availableInBytes / totalInBytes) * 100;
+
+ // If bytes available < the low disk watermarket setting, mark node as having low disk space
+ if (availableInBytes < rawLowDiskWatermarkBytesValue) {
+ nodesWithLowDiskSpace.push({
+ nodeId,
+ nodeName: node.name,
+ available: `${Math.round(percentageAvailable)}%`,
+ lowDiskWatermarkSetting: lowDiskWatermarkSetting!,
+ });
+ }
+ }
+ });
+
+ return response.ok({ body: nodesWithLowDiskSpace });
+ }
+
+ // If the low disk watermark setting is undefined, send empty array
+ // This could occur if the setting is configured in elasticsearch.yml
+ return response.ok({ body: [] });
+ } catch (error) {
+ return handleEsError({ error, response });
+ }
+ }
+ )
+ );
+}
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 2be0d1fafc6ae..ddac8c2576ee6 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/register_routes.ts
@@ -19,6 +19,7 @@ import { registerMlSnapshotRoutes } from './ml_snapshots';
import { ReindexWorker } from '../lib/reindexing';
import { registerUpgradeStatusRoute } from './status';
import { registerRemoteClustersRoute } from './remote_clusters';
+import { registerNodeDiskSpaceRoute } from './node_disk_space';
import { registerClusterSettingsRoute } from './cluster_settings';
export function registerRoutes(dependencies: RouteDependencies, getWorker: () => ReindexWorker) {
@@ -35,5 +36,6 @@ export function registerRoutes(dependencies: RouteDependencies, getWorker: () =>
// Route for cloud to retrieve the upgrade status for ES and Kibana
registerUpgradeStatusRoute(dependencies);
registerRemoteClustersRoute(dependencies);
+ registerNodeDiskSpaceRoute(dependencies);
registerClusterSettingsRoute(dependencies);
}
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts
index c5f7aebc208c9..2d19674d63a75 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/status.test.ts
@@ -121,8 +121,7 @@ describe('Status API', () => {
expect(resp.payload).toEqual({
readyForUpgrade: false,
details:
- 'You have 0 system indices that must be migrated and ' +
- '1 Elasticsearch deprecation issue and 1 Kibana deprecation issue that must be resolved before upgrading.',
+ 'The following issues must be resolved before upgrading: 1 Elasticsearch deprecation issue, 1 Kibana deprecation issue.',
});
});
@@ -144,8 +143,7 @@ describe('Status API', () => {
expect(resp.payload).toEqual({
readyForUpgrade: false,
details:
- 'You have 1 system index that must be migrated and ' +
- '1 Elasticsearch deprecation issue and 1 Kibana deprecation issue that must be resolved before upgrading.',
+ 'The following issues must be resolved before upgrading: 1 unmigrated system index, 1 Elasticsearch deprecation issue, 1 Kibana deprecation issue.',
});
});
@@ -167,8 +165,7 @@ describe('Status API', () => {
expect(resp.payload).toEqual({
readyForUpgrade: false,
details:
- 'You have 1 system index that must be migrated and ' +
- '0 Elasticsearch deprecation issues and 0 Kibana deprecation issues that must be resolved before upgrading.',
+ 'The following issues must be resolved before upgrading: 1 unmigrated system index.',
});
});
diff --git a/x-pack/plugins/upgrade_assistant/server/routes/status.ts b/x-pack/plugins/upgrade_assistant/server/routes/status.ts
index cfa5d6d14c85c..9e8d3f9b2734b 100644
--- a/x-pack/plugins/upgrade_assistant/server/routes/status.ts
+++ b/x-pack/plugins/upgrade_assistant/server/routes/status.ts
@@ -63,12 +63,44 @@ export function registerUpgradeStatusRoute({ router, lib: { handleEsError } }: R
);
}
+ const upgradeIssues: string[] = [];
+
+ if (notMigratedSystemIndices) {
+ upgradeIssues.push(
+ i18n.translate('xpack.upgradeAssistant.status.systemIndicesMessage', {
+ defaultMessage:
+ '{notMigratedSystemIndices} unmigrated system {notMigratedSystemIndices, plural, one {index} other {indices}}',
+ values: { notMigratedSystemIndices },
+ })
+ );
+ }
+
+ if (esTotalCriticalDeps) {
+ upgradeIssues.push(
+ i18n.translate('xpack.upgradeAssistant.status.esTotalCriticalDepsMessage', {
+ defaultMessage:
+ '{esTotalCriticalDeps} Elasticsearch deprecation {esTotalCriticalDeps, plural, one {issue} other {issues}}',
+ values: { esTotalCriticalDeps },
+ })
+ );
+ }
+
+ if (kibanaTotalCriticalDeps) {
+ upgradeIssues.push(
+ i18n.translate('xpack.upgradeAssistant.status.kibanaTotalCriticalDepsMessage', {
+ defaultMessage:
+ '{kibanaTotalCriticalDeps} Kibana deprecation {kibanaTotalCriticalDeps, plural, one {issue} other {issues}}',
+ values: { kibanaTotalCriticalDeps },
+ })
+ );
+ }
+
return i18n.translate('xpack.upgradeAssistant.status.deprecationsUnresolvedMessage', {
defaultMessage:
- 'You have {notMigratedSystemIndices} system {notMigratedSystemIndices, plural, one {index} other {indices}} that must be migrated ' +
- 'and {esTotalCriticalDeps} Elasticsearch deprecation {esTotalCriticalDeps, plural, one {issue} other {issues}} ' +
- 'and {kibanaTotalCriticalDeps} Kibana deprecation {kibanaTotalCriticalDeps, plural, one {issue} other {issues}} that must be resolved before upgrading.',
- values: { esTotalCriticalDeps, kibanaTotalCriticalDeps, notMigratedSystemIndices },
+ 'The following issues must be resolved before upgrading: {upgradeIssues}.',
+ values: {
+ upgradeIssues: upgradeIssues.join(', '),
+ },
});
};
diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/index_detail.js b/x-pack/test/api_integration/apis/monitoring/elasticsearch/index_detail.js
index 87371187c83e8..e243c3a372f7b 100644
--- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/index_detail.js
+++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/index_detail.js
@@ -41,9 +41,6 @@ export default function ({ getService }) {
})
.expect(200);
- // Work around ESTF failure outlined in https://github.com/elastic/kibana/issues/124594
- indexDetailFixture.logs = body.logs;
-
expect(body).to.eql(indexDetailFixture);
});
diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/node_disk_space.ts b/x-pack/test/api_integration/apis/upgrade_assistant/node_disk_space.ts
new file mode 100644
index 0000000000000..fce68f4a344c9
--- /dev/null
+++ b/x-pack/test/api_integration/apis/upgrade_assistant/node_disk_space.ts
@@ -0,0 +1,31 @@
+/*
+ * 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');
+
+ describe('Node disk space', () => {
+ describe('GET /api/upgrade_assistant/node_disk_space', () => {
+ it('returns an array of nodes', async () => {
+ const { body: apiRequestResponse } = await supertest
+ .get(`${API_BASE_PATH}/node_disk_space`)
+ .set('kbn-xsrf', 'xxx')
+ .expect(200);
+
+ // It's tricky to assert the correct node values on CI
+ // For now, this serves as a smoke test and verifies an array is returned
+ // There are jest unit tests that test additional logic
+ expect(Array.isArray(apiRequestResponse)).be(true);
+ });
+ });
+ });
+}
diff --git a/x-pack/test/functional/apps/snapshot_restore/home_page.ts b/x-pack/test/functional/apps/snapshot_restore/home_page.ts
index b72656a96980f..7e8962b3acc82 100644
--- a/x-pack/test/functional/apps/snapshot_restore/home_page.ts
+++ b/x-pack/test/functional/apps/snapshot_restore/home_page.ts
@@ -12,9 +12,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['common', 'snapshotRestore']);
const log = getService('log');
const es = getService('es');
+ const security = getService('security');
describe('Home page', function () {
before(async () => {
+ await security.testUser.setRoles(['snapshot_restore_user'], false);
await pageObjects.common.navigateToApp('snapshotRestore');
});
@@ -46,9 +48,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('cleanup repository', async () => {
await pageObjects.snapshotRestore.viewRepositoryDetails('my-repository');
- await pageObjects.common.sleep(25000);
const cleanupResponse = await pageObjects.snapshotRestore.performRepositoryCleanup();
- await pageObjects.common.sleep(25000);
expect(cleanupResponse).to.contain('results');
expect(cleanupResponse).to.contain('deleted_bytes');
expect(cleanupResponse).to.contain('deleted_blobs');
@@ -57,6 +57,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await es.snapshot.deleteRepository({
repository: 'my-repository',
});
+ await security.testUser.restoreDefaults();
});
});
});
diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js
index 91879031327a1..646c7c04cb1c3 100644
--- a/x-pack/test/functional/config.js
+++ b/x-pack/test/functional/config.js
@@ -541,6 +541,25 @@ export default async function ({ readConfigFile }) {
},
],
},
+ // https://www.elastic.co/guide/en/elasticsearch/reference/master/snapshots-register-repository.html#snapshot-repo-prereqs
+ snapshot_restore_user: {
+ elasticsearch: {
+ cluster: [
+ 'monitor',
+ 'manage_slm',
+ 'cluster:admin/snapshot',
+ 'cluster:admin/repository',
+ ],
+ },
+ kibana: [
+ {
+ feature: {
+ advancedSettings: ['read'],
+ },
+ spaces: ['*'],
+ },
+ ],
+ },
ingest_pipelines_user: {
elasticsearch: {
diff --git a/x-pack/test/functional/page_objects/snapshot_restore_page.ts b/x-pack/test/functional/page_objects/snapshot_restore_page.ts
index 216b2bfff9d7e..e1fc50ed86fa2 100644
--- a/x-pack/test/functional/page_objects/snapshot_restore_page.ts
+++ b/x-pack/test/functional/page_objects/snapshot_restore_page.ts
@@ -49,12 +49,15 @@ export function SnapshotRestorePageProvider({ getService }: FtrProviderContext)
const repoToView = repos.filter((r) => (r.repoName = name))[0];
await repoToView.repoLink.click();
}
- await retry.waitForWithTimeout(`Repo title should be ${name}`, 10000, async () => {
+ await retry.waitForWithTimeout(`Repo title should be ${name}`, 25000, async () => {
return (await testSubjects.getVisibleText('title')) === name;
});
},
async performRepositoryCleanup() {
await testSubjects.click('cleanupRepositoryButton');
+ await retry.waitForWithTimeout(`wait for code block to be visible`, 25000, async () => {
+ return await testSubjects.isDisplayed('cleanupCodeBlock');
+ });
return await testSubjects.getVisibleText('cleanupCodeBlock');
},
};
diff --git a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts
index f59cf660139b9..933880dac739e 100644
--- a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts
+++ b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts
@@ -31,9 +31,9 @@ export class UpgradeAssistantPageObject extends FtrService {
async navigateToEsDeprecationLogs() {
return await this.retry.try(async () => {
- await this.common.navigateToApp('settings');
- await this.testSubjects.click('upgrade_assistant');
- await this.testSubjects.click('viewElasticsearchDeprecationLogs');
+ await this.common.navigateToUrl('management', 'stack/upgrade_assistant/es_deprecation_logs', {
+ shouldUseHashForSubUrl: false,
+ });
await this.retry.waitFor(
'url to contain /upgrade_assistant/es_deprecation_logs',
async () => {
diff --git a/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts b/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
index 20fc34f77dbf8..e7769f2761f3f 100644
--- a/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
+++ b/x-pack/test/upgrade/apps/reporting/reporting_smoke_tests.ts
@@ -71,6 +71,21 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
}
const advOpt = await find.byXPath(`//button[descendant::*[text()='Advanced options']]`);
await advOpt.click();
+ // Workaround for: https://github.com/elastic/kibana/issues/126540
+ const isUrlTooLong = await testSubjects.exists('urlTooLongErrorMessage');
+ if (isUrlTooLong) {
+ // Save dashboard
+ await PageObjects.dashboard.switchToEditMode();
+ await PageObjects.dashboard.clickQuickSave();
+ await PageObjects.share.openShareMenuItem(link);
+ if (type === 'pdf_optimize') {
+ await testSubjects.click('usePrintLayout');
+ }
+ const advOpt2 = await find.byXPath(
+ `//button[descendant::*[text()='Advanced options']]`
+ );
+ await advOpt2.click();
+ }
const postUrl = await find.byXPath(`//button[descendant::*[text()='Copy POST URL']]`);
await postUrl.click();
const url = await browser.getClipboardValue();
diff --git a/yarn.lock b/yarn.lock
index fa8a09740ca39..eae3de6aa0fbc 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6354,10 +6354,10 @@
resolved "https://registry.yarnpkg.com/@types/seedrandom/-/seedrandom-2.4.28.tgz#9ce8fa048c1e8c85cb71d7fe4d704e000226036f"
integrity sha512-SMA+fUwULwK7sd/ZJicUztiPs8F1yCPwF3O23Z9uQ32ME5Ha0NmDK9+QTsYE4O2tHXChzXomSWWeIhCnoN1LqA==
-"@types/selenium-webdriver@^4.0.16":
- version "4.0.16"
- resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.16.tgz#c3205c6691a1d645cf4163684bd119230a60e6f5"
- integrity sha512-0UAzu2lFXpLK4lU4yhgUtM/KxoN8hIpyI+q22KAwzIDHNk4kLJ/Ut5mJZLFSxfQx58OBQ9SJXZkSL065fe/WdQ==
+"@types/selenium-webdriver@^4.0.18":
+ version "4.0.18"
+ resolved "https://registry.yarnpkg.com/@types/selenium-webdriver/-/selenium-webdriver-4.0.18.tgz#98f6e1ccd2d92f6fddaccfc7c148d2e158da0f92"
+ integrity sha512-gkrUo3QldGr8V9im/DjgKkX4UVd1rtflfEBuPG9hPSA1keu7A0rF8h/MQjpTMm2EPVhBCd2K8tn5nlC9Vsd5Xw==
"@types/semver@^7":
version "7.3.4"
@@ -9590,10 +9590,10 @@ chrome-trace-event@^1.0.2:
dependencies:
tslib "^1.9.0"
-chromedriver@^97.0.2:
- version "97.0.2"
- resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-97.0.2.tgz#b6c26f6667ad40dc8cf08818878cc064787116fc"
- integrity sha512-sOAfKCR3WsHvmKedZoWa+3tBVGdPtxq4zKxgKZCoJ2c924olBTW4Bnha6SHl93Yo7+QqsNn6ZpAC0ojhutacAg==
+chromedriver@^98.0.0:
+ version "98.0.0"
+ resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-98.0.0.tgz#b2c3c1941fad4cdfadad5d4c46923e02f089fd30"
+ integrity sha512-Oi6Th5teK+VI4nti+423/dFkENYHEMOdUvqwJHzOaNwXqLwZ8FuSaKBybgALCctGapwJbd+tmPv3qSd6tUUIHQ==
dependencies:
"@testim/chrome-version" "^1.1.2"
axios "^0.24.0"