From 3e7fb18c12cabaac800447cc886a165aecd8a36a Mon Sep 17 00:00:00 2001 From: Ahmad Bamieh Date: Mon, 2 Dec 2024 22:53:24 +0100 Subject: [PATCH] [UA] Support Deprecated Data Streams Migrations (#202204) ## Summary - [x] Fix UA currently failing to return upgrade status - [x] Support surfacing `data_streams` migrations in UA under the ES tab - [x] Refactor code for better readablity - [x] Add more test cases across the board for all the es migrations status feature in UA - [x] Add a `featureSet.migrateDataStreams` to enable surfacing data streams migrations - [x] Surface data streams in UA UI - [x] Take screenshots for a product review discussions - [x] Unskip api_integration test cases ### Imporant Notes ES deprecations are hidden behind the `featureSet` flag and will only be shown in `8.last` for users. This gives us time to review the copy and implement the corrective action for reindexing data streams which is still pending implementaiton from ES side. For now we will merge this to unblock upgrades in `8.17` and support surfacing data_streams deprecations and add tests. Follow up work for `8.18` - Add integration Tests - Update copy of flyout and documentation link - Reindexing data streams corrective action closes https://github.com/elastic/kibana-team/issues/1293 ## Screenshots #### Overview Page image #### Elasticsearch deprecation issues Page image #### Data streams deprecation details flyout image --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .../test_suites/core_plugins/rendering.ts | 1 + x-pack/plugins/upgrade_assistant/README.md | 1 + .../helpers/app_context.mock.ts | 1 + .../plugins/upgrade_assistant/common/types.ts | 3 +- .../application/components/constants.tsx | 6 +- .../upgrade_assistant/server/config.ts | 5 + .../lib/__fixtures__/es_deprecations.ts | 77 ++++ .../lib/__fixtures__/fake_deprecations.json | 26 ++ .../server/lib/es_deprecations_status.ts | 340 ------------------ .../__snapshots__/index.test.ts.snap} | 17 +- .../get_corrective_actions.ts | 68 ++++ .../health_indicators.test.ts | 131 +++++++ .../health_indicators.ts | 123 +++++++ .../index.test.ts} | 204 +++-------- .../lib/es_deprecations_status/index.ts | 69 ++++ .../lib/es_deprecations_status/migrations.ts | 157 ++++++++ .../plugins/upgrade_assistant/tsconfig.json | 3 +- .../apis/upgrade_assistant/config.ts | 10 + .../upgrade_assistant/upgrade_assistant.ts | 3 +- 19 files changed, 743 insertions(+), 502 deletions(-) create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/es_deprecations.ts delete mode 100644 x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts rename x-pack/plugins/upgrade_assistant/server/lib/{__snapshots__/es_deprecations_status.test.ts.snap => es_deprecations_status/__snapshots__/index.test.ts.snap} (92%) create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/get_corrective_actions.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/health_indicators.test.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/health_indicators.ts rename x-pack/plugins/upgrade_assistant/server/lib/{es_deprecations_status.test.ts => es_deprecations_status/index.test.ts} (56%) create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/index.ts create mode 100644 x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/migrations.ts diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 7305824696aec..8047994039e71 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -354,6 +354,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.upgrade_assistant.featureSet.migrateSystemIndices (boolean?)', 'xpack.upgrade_assistant.featureSet.mlSnapshots (boolean?)', 'xpack.upgrade_assistant.featureSet.reindexCorrectiveActions (boolean?)', + 'xpack.upgrade_assistant.featureSet.migrateDataStreams (boolean?)', 'xpack.upgrade_assistant.ui.enabled (boolean?)', 'xpack.observability.unsafe.alertDetails.metrics.enabled (boolean?)', 'xpack.observability.unsafe.alertDetails.logs.enabled (boolean?)', diff --git a/x-pack/plugins/upgrade_assistant/README.md b/x-pack/plugins/upgrade_assistant/README.md index 9425e36472d7f..39bebc15e44b1 100644 --- a/x-pack/plugins/upgrade_assistant/README.md +++ b/x-pack/plugins/upgrade_assistant/README.md @@ -15,6 +15,7 @@ Some features of the UA are only needed when upgrading to a new major version. T * ML Snapshots (`featureSet.mlSnapshots`): Machine learning Upgrade mode can be toggled from outside Kibana, the purpose of this feature guard is to hide all ML related deprecations from the end user until the next major upgrade. When we want to enable ML model snapshot deprecation warnings again we need to change the constant `MachineLearningField.MIN_CHECKED_SUPPORTED_SNAPSHOT_VERSION` to something higher than 7.0.0 in the Elasticsearch code. * Migrating system indices (`featureSet.migrateSystemIndices`): Migrating system indices should only be enabled for major version upgrades. This config hides the second step from the UA UI for migrating system indices. +* Reindex Data Streams (`featureSet.migrateDataStreams`): Migrating deprecated Data streams should only be enabled for major version upgrades. The purpose of this feature guard is to hide all data streams related deprecations from the end user until the next major upgrade. * Reindex corrective actions (`featureSet.reindexCorrectiveActions`): Deprecations with reindexing corrective actions are only enabled for major version upgrades. Currently, the reindex actions include some logic that is specific to the [8.0 upgrade](https://github.com/elastic/kibana/blob/main/x-pack/plugins/upgrade_assistant/server/lib/reindexing/index_settings.ts). End users could get into a bad situation if this is enabled before this logic is fixed. ## Deprecations diff --git a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts index 31fd69648418f..010790e3c5e15 100644 --- a/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts +++ b/x-pack/plugins/upgrade_assistant/__jest__/client_integration/helpers/app_context.mock.ts @@ -80,6 +80,7 @@ export const getAppContextMock = (kibanaVersion: SemVer) => ({ featureSet: { mlSnapshots: true, migrateSystemIndices: true, + migrateDataStreams: true, reindexCorrectiveActions: true, }, kibanaVersionInfo: { diff --git a/x-pack/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts index 6d7166569d8db..781e4865ee568 100644 --- a/x-pack/plugins/upgrade_assistant/common/types.ts +++ b/x-pack/plugins/upgrade_assistant/common/types.ts @@ -215,7 +215,7 @@ export interface HealthIndicatorAction { export interface EnrichedDeprecationInfo extends Omit { - type: keyof estypes.MigrationDeprecationsResponse | 'health_indicator'; + type: keyof estypes.MigrationDeprecationsResponse | 'health_indicator' | 'data_streams'; isCritical: boolean; status?: estypes.HealthReportIndicatorHealthStatus; index?: string; @@ -296,4 +296,5 @@ export interface FeatureSet { migrateSystemIndices: boolean; mlSnapshots: boolean; reindexCorrectiveActions: boolean; + migrateDataStreams: boolean; } diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/constants.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/constants.tsx index 12babe0b9b468..d0743230f909c 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/constants.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/constants.tsx @@ -6,8 +6,9 @@ */ import { i18n } from '@kbn/i18n'; +import { EnrichedDeprecationInfo } from '../../../common/types'; -export const DEPRECATION_TYPE_MAP = { +export const DEPRECATION_TYPE_MAP: Record = { cluster_settings: i18n.translate( 'xpack.upgradeAssistant.esDeprecations.clusterDeprecationTypeLabel', { @@ -32,6 +33,9 @@ export const DEPRECATION_TYPE_MAP = { defaultMessage: 'Health Indicator', } ), + data_streams: i18n.translate('xpack.upgradeAssistant.esDeprecations.dataStreamsTypeLabel', { + defaultMessage: 'Data Stream', + }), }; export const PAGINATION_CONFIG = { diff --git a/x-pack/plugins/upgrade_assistant/server/config.ts b/x-pack/plugins/upgrade_assistant/server/config.ts index e603780be785b..ecf168c297c96 100644 --- a/x-pack/plugins/upgrade_assistant/server/config.ts +++ b/x-pack/plugins/upgrade_assistant/server/config.ts @@ -47,6 +47,11 @@ const configSchema = schema.object({ * End users could get into a bad situation if this is enabled before this logic is fixed. */ reindexCorrectiveActions: schema.boolean({ defaultValue: false }), + /** + * Migrating deprecated data streams should only be enabled for major version upgrades. + * Currently this is manually set to `true` on every `x.last` version. + */ + migrateDataStreams: schema.boolean({ defaultValue: false }), }), /** * This config allows to hide the UI without disabling the plugin. diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/es_deprecations.ts b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/es_deprecations.ts new file mode 100644 index 0000000000000..4ced006a9bb43 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/es_deprecations.ts @@ -0,0 +1,77 @@ +/* + * 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 const getMockEsDeprecations = () => { + return { + cluster_settings: [], + node_settings: [], + ml_settings: [], + index_settings: {}, + data_streams: {}, + }; +}; + +export const getMockMlSettingsDeprecations = () => { + return { + ml_settings: [ + { + level: 'warning', + message: 'Datafeed [deprecation-datafeed] uses deprecated query options', + url: 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-7.0.html#breaking_70_search_changes', + details: + '[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]', + // @ts-ignore + resolve_during_rolling_upgrade: false, + }, + { + level: 'critical', + message: + 'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded', + url: '', + details: 'details', + // @ts-ignore + _meta: { snapshot_id: '1', job_id: 'deprecation_check_job' }, + // @ts-ignore + resolve_during_rolling_upgrade: false, + }, + ], + }; +}; + +export const getMockDataStreamDeprecations = () => { + return { + data_streams: { + 'my-v7-data-stream': [ + { + level: 'critical', + message: 'Old data stream with a compatibility version < 8.0', + url: 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html', + details: + 'This data stream has backing indices that were created before Elasticsearch 8.0.0', + resolve_during_rolling_upgrade: false, + _meta: { + backing_indices: { + count: 52, + need_upgrading: { + count: 37, + searchable_snapshot: { + count: 23, + fully_mounted: { + count: 7, + }, + partially_mounted: { + count: 16, + }, + }, + }, + }, + }, + }, + ], + }, + }; +}; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json index a83d8d231d142..f53e4176096cc 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json +++ b/x-pack/plugins/upgrade_assistant/server/lib/__fixtures__/fake_deprecations.json @@ -148,5 +148,31 @@ "resolve_during_rolling_upgrade": false } ] + }, + "data_streams": { + "my-v7-data-stream" : [{ + "level" : "critical", + "message" : "Old data stream with a compatibility version < 8.0", + "url" : "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html", + "details" : "This data stream has backing indices that were created before Elasticsearch 8.0.0", + "resolve_during_rolling_upgrade" : false, + "_meta": { + "backing_indices": { + "count": 52, + "need_upgrading": { + "count": 37, + "searchable_snapshot": { + "count": 23, + "fully_mounted": { + "count": 7 + }, + "partially_mounted": { + "count": 16 + } + } + } + } + } + }] } } diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts deleted file mode 100644 index 05fc4f666f7ba..0000000000000 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.ts +++ /dev/null @@ -1,340 +0,0 @@ -/* - * 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { IScopedClusterClient } from '@kbn/core/server'; -import { i18n } from '@kbn/i18n'; -import { EnrichedDeprecationInfo, ESUpgradeStatus, FeatureSet } from '../../common/types'; -import { esIndicesStateCheck } from './es_indices_state_check'; -import { - getESSystemIndicesMigrationStatus, - convertFeaturesToIndicesArray, -} from './es_system_indices_migration'; - -export function getShardCapacityDeprecationInfo({ - symptom, - details, -}: { - details: any; - symptom: any; -}) { - // When we dont have a details field for our indicator, we can only report - // the symptom to the user given that's the only information about the deprecation - // we have. - if (!details) { - return { - details: symptom, - message: symptom, - url: null, - resolveDuringUpgrade: false, - }; - } - - const causes = []; - if (details.indices_with_readonly_block > 0) { - causes.push( - i18n.translate( - 'xpack.upgradeAssistant.esDeprecationsStatus.indicesWithReadonlyBlockCauseMessage', - { - defaultMessage: - 'The number of indices the system enforced a read-only index block (`index.blocks.read_only_allow_delete`) on because the cluster is running out of space.', - } - ) - ); - } - - if (details.nodes_over_high_watermark > 0) { - causes.push( - i18n.translate( - 'xpack.upgradeAssistant.esDeprecationsStatus.nodesOverHighWatermarkCauseMessage', - { - defaultMessage: - 'The number of nodes that are running low on disk and it is likely that they will run out of space. Their disk usage has tripped the <>.', - ignoreTag: true, - } - ) - ); - } - - if (details.nodes_over_flood_stage_watermark > 0) { - causes.push( - i18n.translate( - 'xpack.upgradeAssistant.esDeprecationsStatus.nodesOverFloodStageWatermarkCauseMessage', - { - defaultMessage: - 'The number of nodes that have run out of disk. Their disk usage has tripped the <>.', - ignoreTag: true, - } - ) - ); - } - - return { - details: symptom, - message: symptom, - url: null, - resolveDuringUpgrade: false, - correctiveAction: { - type: 'healthIndicator', - impacts: details, - cause: causes.join('\n'), - }, - }; -} - -export async function getHealthIndicators( - dataClient: IScopedClusterClient -): Promise { - const healthIndicators = await dataClient.asCurrentUser.healthReport(); - const isStatusNotGreen = (indicator?: estypes.HealthReportBaseIndicator): boolean => { - return !!(indicator?.status && indicator?.status !== 'green'); - }; - - // Temporarily ignoring due to untyped ES indicators - // types will be available during 8.9.0 - // @ts-ignore - return [ - ...[ - // @ts-ignore - healthIndicators.indicators.shards_capacity as estypes.HealthReportBaseIndicator, - ] - .filter(isStatusNotGreen) - .flatMap(({ status, symptom, impacts, diagnosis }) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - return (diagnosis || []).map(({ cause, action, help_url }) => ({ - type: 'health_indicator', - details: symptom, - message: cause, - url: help_url, - isCritical: status === 'red', - resolveDuringUpgrade: false, - correctiveAction: { type: 'healthIndicator', cause, action, impacts }, - })); - }), - ...[healthIndicators.indicators.disk as estypes.HealthReportDiskIndicator] - .filter(isStatusNotGreen) - .flatMap(({ status, symptom, details }) => { - return { - type: 'health_indicator', - isCritical: status === 'red', - ...getShardCapacityDeprecationInfo({ symptom, details }), - }; - }), - ]; -} - -export async function getESUpgradeStatus( - dataClient: IScopedClusterClient, - featureSet: FeatureSet -): Promise { - const getCombinedDeprecations = async () => { - const healthIndicators = await getHealthIndicators(dataClient); - const deprecations = await dataClient.asCurrentUser.migration.deprecations(); - const indices = await getCombinedIndexInfos(deprecations, dataClient); - const systemIndices = await getESSystemIndicesMigrationStatus(dataClient.asCurrentUser); - const systemIndicesList = convertFeaturesToIndicesArray(systemIndices.features); - - const enrichedDeprecations = Object.keys(deprecations).reduce( - (combinedDeprecations, deprecationType) => { - if (deprecationType === 'index_settings') { - // We need to exclude all index related deprecations for system indices since - // they are resolved separately through the system indices upgrade section in - // the Overview page. - const withoutSystemIndices = indices.filter( - (index) => !systemIndicesList.includes(index.index!) - ); - - combinedDeprecations = combinedDeprecations.concat(withoutSystemIndices); - } else { - const deprecationsByType = deprecations[ - deprecationType as keyof estypes.MigrationDeprecationsResponse - ] as estypes.MigrationDeprecationsDeprecation[]; - - const enrichedDeprecationInfo = deprecationsByType - .map( - ({ - details, - level, - message, - url, - // @ts-expect-error @elastic/elasticsearch _meta not available yet in MigrationDeprecationInfoResponse - _meta: metadata, - // @ts-expect-error @elastic/elasticsearch resolve_during_rolling_upgrade not available yet in MigrationDeprecationInfoResponse - resolve_during_rolling_upgrade: resolveDuringUpgrade, - }) => { - return { - details, - message, - url, - type: deprecationType as keyof estypes.MigrationDeprecationsResponse, - isCritical: level === 'critical', - resolveDuringUpgrade, - correctiveAction: getCorrectiveAction(message, metadata), - }; - } - ) - .filter(({ correctiveAction, type }) => { - /** - * This disables showing the ML deprecations in the UA if `featureSet.mlSnapshots` - * is set to `false`. - * - * This config should be set to true only on the `x.last` versions, or when - * the constant `MachineLearningField.MIN_CHECKED_SUPPORTED_SNAPSHOT_VERSION` - * is incremented to something higher than 7.0.0 in the Elasticsearch code. - */ - if (!featureSet.mlSnapshots) { - if (type === 'ml_settings' || correctiveAction?.type === 'mlSnapshot') { - return false; - } - } - - /** - * This disables showing the reindexing deprecations in the UA if - * `featureSet.reindexCorrectiveActions` is set to `false`. - */ - if (!featureSet.reindexCorrectiveActions && correctiveAction?.type === 'reindex') { - return false; - } - - return true; - }); - - combinedDeprecations = combinedDeprecations.concat(enrichedDeprecationInfo); - } - - return combinedDeprecations; - }, - [] as EnrichedDeprecationInfo[] - ); - - const enrichedHealthIndicators = healthIndicators.filter(({ status }) => { - return status !== 'green'; - }) as EnrichedDeprecationInfo[]; - - return [...enrichedHealthIndicators, ...enrichedDeprecations]; - }; - - const combinedDeprecations = await getCombinedDeprecations(); - const criticalWarnings = combinedDeprecations.filter(({ isCritical }) => isCritical === true); - - return { - totalCriticalDeprecations: criticalWarnings.length, - deprecations: combinedDeprecations, - }; -} - -// Reformats the index deprecations to an array of deprecation warnings extended with an index field. -const getCombinedIndexInfos = async ( - deprecations: estypes.MigrationDeprecationsResponse, - dataClient: IScopedClusterClient -) => { - const indices = Object.keys(deprecations.index_settings).reduce( - (indexDeprecations, indexName) => { - return indexDeprecations.concat( - deprecations.index_settings[indexName].map( - ({ - details, - message, - url, - level, - // @ts-expect-error @elastic/elasticsearch _meta not available yet in MigrationDeprecationInfoResponse - _meta: metadata, - // @ts-expect-error @elastic/elasticsearch resolve_during_rolling_upgrade not available yet in MigrationDeprecationInfoResponse - resolve_during_rolling_upgrade: resolveDuringUpgrade, - }) => - ({ - details, - message, - url, - index: indexName, - type: 'index_settings', - isCritical: level === 'critical', - correctiveAction: getCorrectiveAction(message, metadata, indexName), - resolveDuringUpgrade, - } as EnrichedDeprecationInfo) - ) - ); - }, - [] as EnrichedDeprecationInfo[] - ); - - const indexNames = indices.map(({ index }) => index!); - - // If we have found deprecation information for index/indices - // check whether the index is open or closed. - if (indexNames.length) { - const indexStates = await esIndicesStateCheck(dataClient.asCurrentUser, indexNames); - - indices.forEach((indexData) => { - if (indexData.correctiveAction?.type === 'reindex') { - indexData.correctiveAction.blockerForReindexing = - indexStates[indexData.index!] === 'closed' ? 'index-closed' : undefined; - } - }); - } - return indices as EnrichedDeprecationInfo[]; -}; - -interface Action { - action_type: 'remove_settings'; - objects: string[]; -} - -interface Actions { - actions: Action[]; -} - -type EsMetadata = Actions & { - [key: string]: string; -}; - -const getCorrectiveAction = ( - message: string, - metadata: EsMetadata, - indexName?: string -): EnrichedDeprecationInfo['correctiveAction'] => { - const indexSettingDeprecation = metadata?.actions?.find( - (action) => action.action_type === 'remove_settings' && indexName - ); - const clusterSettingDeprecation = metadata?.actions?.find( - (action) => action.action_type === 'remove_settings' && typeof indexName === 'undefined' - ); - const requiresReindexAction = /Index created before/.test(message); - const requiresIndexSettingsAction = Boolean(indexSettingDeprecation); - const requiresClusterSettingsAction = Boolean(clusterSettingDeprecation); - const requiresMlAction = /[Mm]odel snapshot/.test(message); - - if (requiresReindexAction) { - return { - type: 'reindex', - }; - } - - if (requiresIndexSettingsAction) { - return { - type: 'indexSetting', - deprecatedSettings: indexSettingDeprecation!.objects, - }; - } - - if (requiresClusterSettingsAction) { - return { - type: 'clusterSetting', - deprecatedSettings: clusterSettingDeprecation!.objects, - }; - } - - if (requiresMlAction) { - const { snapshot_id: snapshotId, job_id: jobId } = metadata!; - - return { - type: 'mlSnapshot', - snapshotId, - jobId, - }; - } -}; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_deprecations_status.test.ts.snap b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/__snapshots__/index.test.ts.snap similarity index 92% rename from x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_deprecations_status.test.ts.snap rename to x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/__snapshots__/index.test.ts.snap index de0154037f9ba..b0d5d99a37497 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/__snapshots__/es_deprecations_status.test.ts.snap +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/__snapshots__/index.test.ts.snap @@ -6,6 +6,7 @@ Object { Object { "correctiveAction": undefined, "details": "templates using \`template\` field: security_audit_log,watches,.monitoring-alerts,triggered_watches,.ml-anomalies-,.ml-notifications,.ml-meta,.monitoring-kibana,.monitoring-es,.monitoring-logstash,.watch-history-6,.ml-state,security-index-template", + "index": undefined, "isCritical": false, "message": "Template patterns are no longer using \`template\` field, but \`index_patterns\` instead", "resolveDuringUpgrade": false, @@ -15,6 +16,7 @@ Object { Object { "correctiveAction": undefined, "details": "{.monitoring-logstash=[Coercion of boolean fields], .monitoring-es=[Coercion of boolean fields], .ml-anomalies-=[Coercion of boolean fields], .watch-history-6=[Coercion of boolean fields], .monitoring-kibana=[Coercion of boolean fields], security-index-template=[Coercion of boolean fields]}", + "index": undefined, "isCritical": false, "message": "one or more templates use deprecated mapping settings", "resolveDuringUpgrade": false, @@ -24,6 +26,7 @@ Object { Object { "correctiveAction": undefined, "details": "[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]", + "index": undefined, "isCritical": false, "message": "Datafeed [deprecation-datafeed] uses deprecated query options", "resolveDuringUpgrade": false, @@ -37,6 +40,7 @@ Object { "type": "mlSnapshot", }, "details": "details", + "index": undefined, "isCritical": true, "message": "model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded", "resolveDuringUpgrade": false, @@ -46,6 +50,7 @@ Object { Object { "correctiveAction": undefined, "details": "This node thing is wrong", + "index": undefined, "isCritical": true, "message": "A node-level issue", "resolveDuringUpgrade": true, @@ -153,7 +158,17 @@ Object { "type": "index_settings", "url": "https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_fields", }, + Object { + "correctiveAction": undefined, + "details": "This data stream has backing indices that were created before Elasticsearch 8.0.0", + "index": "my-v7-data-stream", + "isCritical": true, + "message": "Old data stream with a compatibility version < 8.0", + "resolveDuringUpgrade": false, + "type": "data_streams", + "url": "https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-9.0.html", + }, ], - "totalCriticalDeprecations": 4, + "totalCriticalDeprecations": 5, } `; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/get_corrective_actions.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/get_corrective_actions.ts new file mode 100644 index 0000000000000..be696acb18095 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/get_corrective_actions.ts @@ -0,0 +1,68 @@ +/* + * 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 { EnrichedDeprecationInfo } from '../../../common/types'; + +interface Action { + action_type: 'remove_settings'; + objects: string[]; +} + +interface Actions { + actions: Action[]; +} + +type EsMetadata = Actions & { + [key: string]: string; +}; + +export const getCorrectiveAction = ( + message: string, + metadata: EsMetadata, + indexName?: string +): EnrichedDeprecationInfo['correctiveAction'] => { + const indexSettingDeprecation = metadata?.actions?.find( + (action) => action.action_type === 'remove_settings' && indexName + ); + const clusterSettingDeprecation = metadata?.actions?.find( + (action) => action.action_type === 'remove_settings' && typeof indexName === 'undefined' + ); + const requiresReindexAction = /Index created before/.test(message); + const requiresIndexSettingsAction = Boolean(indexSettingDeprecation); + const requiresClusterSettingsAction = Boolean(clusterSettingDeprecation); + const requiresMlAction = /[Mm]odel snapshot/.test(message); + + if (requiresReindexAction) { + return { + type: 'reindex', + }; + } + + if (requiresIndexSettingsAction) { + return { + type: 'indexSetting', + deprecatedSettings: indexSettingDeprecation!.objects, + }; + } + + if (requiresClusterSettingsAction) { + return { + type: 'clusterSetting', + deprecatedSettings: clusterSettingDeprecation!.objects, + }; + } + + if (requiresMlAction) { + const { snapshot_id: snapshotId, job_id: jobId } = metadata!; + + return { + type: 'mlSnapshot', + snapshotId, + jobId, + }; + } +}; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/health_indicators.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/health_indicators.test.ts new file mode 100644 index 0000000000000..3f16d0f9e94ce --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/health_indicators.test.ts @@ -0,0 +1,131 @@ +/* + * 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 { elasticsearchServiceMock, ScopedClusterClientMock } from '@kbn/core/server/mocks'; +import { getHealthIndicators } from './health_indicators'; +import * as healthIndicatorsMock from '../__fixtures__/health_indicators'; + +describe('getHealthIndicators', () => { + let esClient: ScopedClusterClientMock; + beforeEach(() => { + esClient = elasticsearchServiceMock.createScopedClusterClient(); + }); + + it('returns empty array on green indicators', async () => { + esClient.asCurrentUser.healthReport.mockResponse({ + cluster_name: 'mock', + indicators: { + disk: healthIndicatorsMock.diskIndicatorGreen, + // @ts-ignore + shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen, + }, + }); + + const result = await getHealthIndicators(esClient); + expect(result).toEqual([]); + }); + + it('returns unknown indicators', async () => { + esClient.asCurrentUser.healthReport.mockResponse({ + cluster_name: 'mock', + indicators: { + disk: healthIndicatorsMock.diskIndicatorUnknown, + // @ts-ignore + shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen, + }, + }); + + const result = await getHealthIndicators(esClient); + expect(result[0]).toEqual( + expect.objectContaining({ + details: 'No disk usage data.', + }) + ); + }); + + it('returns unhealthy shards_capacity indicator', async () => { + esClient.asCurrentUser.healthReport.mockResponse({ + cluster_name: 'mock', + indicators: { + disk: healthIndicatorsMock.diskIndicatorGreen, + // @ts-ignore + shards_capacity: healthIndicatorsMock.shardCapacityIndicatorRed, + }, + }); + + const result = await getHealthIndicators(esClient); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveAction": Object { + "action": "Increase the value of [cluster.max_shards_per_node] cluster setting or remove data indices to clear up resources.", + "cause": "Elasticsearch is about to reach the maximum number of shards it can host, based on your current settings.", + "impacts": Array [ + Object { + "description": "The cluster has too many used shards to be able to upgrade.", + "id": "elasticsearch:health:shards_capacity:impact:upgrade_blocked", + "impact_areas": "[Array]", + "severity": 1, + }, + Object { + "description": "The cluster is running low on room to add new shards. Adding data to new indices is at risk", + "id": "elasticsearch:health:shards_capacity:impact:creation_of_new_indices_blocked", + "impact_areas": "[Array]", + "severity": 1, + }, + ], + "type": "healthIndicator", + }, + "details": "Cluster is close to reaching the configured maximum number of shards for data nodes.", + "isCritical": true, + "message": "Elasticsearch is about to reach the maximum number of shards it can host, based on your current settings.", + "resolveDuringUpgrade": false, + "type": "health_indicator", + "url": "https://ela.st/fix-shards-capacity", + }, + ] + `); + }); + + it('returns unhealthy disk indicator', async () => { + esClient.asCurrentUser.healthReport.mockResponse({ + cluster_name: 'mock', + indicators: { + disk: healthIndicatorsMock.diskIndicatorRed, + // @ts-ignore + shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen, + }, + }); + + const result = await getHealthIndicators(esClient); + expect(result).toMatchInlineSnapshot(` + Array [ + Object { + "correctiveAction": Object { + "cause": "The number of indices the system enforced a read-only index block (\`index.blocks.read_only_allow_delete\`) on because the cluster is running out of space. + The number of nodes that are running low on disk and it is likely that they will run out of space. Their disk usage has tripped the <>. + The number of nodes that have run out of disk. Their disk usage has tripped the <>.", + "impacts": Object { + "indices_with_readonly_block": 1, + "nodes_over_flood_stage_watermark": 1, + "nodes_over_high_watermark": 1, + "nodes_with_enough_disk_space": 1, + "nodes_with_unknown_disk_status": 1, + }, + "type": "healthIndicator", + }, + "details": "The cluster does not have enough available disk space.", + "isCritical": true, + "message": "The cluster does not have enough available disk space.", + "resolveDuringUpgrade": false, + "type": "health_indicator", + "url": null, + }, + ] + `); + }); +}); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/health_indicators.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/health_indicators.ts new file mode 100644 index 0000000000000..372582220659a --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/health_indicators.ts @@ -0,0 +1,123 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { IScopedClusterClient } from '@kbn/core/server'; +import { EnrichedDeprecationInfo } from '../../../common/types'; + +export async function getHealthIndicators( + dataClient: IScopedClusterClient +): Promise { + const healthIndicators = await dataClient.asCurrentUser.healthReport(); + const isStatusNotGreen = (indicator?: estypes.HealthReportBaseIndicator): boolean => { + return !!(indicator?.status && indicator?.status !== 'green'); + }; + + // Temporarily ignoring due to untyped ES indicators + // types will be available during 8.9.0 + // @ts-ignore + return [ + ...[ + // @ts-ignore + healthIndicators.indicators.shards_capacity as estypes.HealthReportBaseIndicator, + ] + .filter(isStatusNotGreen) + .flatMap(({ status, symptom, impacts, diagnosis }) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + return (diagnosis || []).map(({ cause, action, help_url }) => ({ + type: 'health_indicator', + details: symptom, + message: cause, + url: help_url, + isCritical: status === 'red', + resolveDuringUpgrade: false, + correctiveAction: { type: 'healthIndicator', cause, action, impacts }, + })); + }), + ...[healthIndicators.indicators.disk as estypes.HealthReportDiskIndicator] + .filter(isStatusNotGreen) + .flatMap(({ status, symptom, details }) => { + return { + type: 'health_indicator', + isCritical: status === 'red', + ...getShardCapacityDeprecationInfo({ symptom, details }), + }; + }), + ]; +} + +export function getShardCapacityDeprecationInfo({ + symptom, + details, +}: { + details: any; + symptom: any; +}) { + // When we dont have a details field for our indicator, we can only report + // the symptom to the user given that's the only information about the deprecation + // we have. + if (!details) { + return { + details: symptom, + message: symptom, + url: null, + resolveDuringUpgrade: false, + }; + } + + const causes = []; + if (details.indices_with_readonly_block > 0) { + causes.push( + i18n.translate( + 'xpack.upgradeAssistant.esDeprecationsStatus.indicesWithReadonlyBlockCauseMessage', + { + defaultMessage: + 'The number of indices the system enforced a read-only index block (`index.blocks.read_only_allow_delete`) on because the cluster is running out of space.', + } + ) + ); + } + + if (details.nodes_over_high_watermark > 0) { + causes.push( + i18n.translate( + 'xpack.upgradeAssistant.esDeprecationsStatus.nodesOverHighWatermarkCauseMessage', + { + defaultMessage: + 'The number of nodes that are running low on disk and it is likely that they will run out of space. Their disk usage has tripped the <>.', + ignoreTag: true, + } + ) + ); + } + + if (details.nodes_over_flood_stage_watermark > 0) { + causes.push( + i18n.translate( + 'xpack.upgradeAssistant.esDeprecationsStatus.nodesOverFloodStageWatermarkCauseMessage', + { + defaultMessage: + 'The number of nodes that have run out of disk. Their disk usage has tripped the <>.', + ignoreTag: true, + } + ) + ); + } + + return { + details: symptom, + message: symptom, + url: null, + resolveDuringUpgrade: false, + correctiveAction: { + type: 'healthIndicator', + impacts: details, + cause: causes.join('\n'), + }, + }; +} diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/index.test.ts similarity index 56% rename from x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.test.ts rename to x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/index.test.ts index 3a373cbffbfe7..c7a8c13993099 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/index.test.ts @@ -6,142 +6,22 @@ */ import _ from 'lodash'; -import { elasticsearchServiceMock, ScopedClusterClientMock } from '@kbn/core/server/mocks'; +import { elasticsearchServiceMock } from '@kbn/core/server/mocks'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { getESUpgradeStatus, getHealthIndicators } from './es_deprecations_status'; -import fakeDeprecations from './__fixtures__/fake_deprecations.json'; -import * as healthIndicatorsMock from './__fixtures__/health_indicators'; - -import type { FeatureSet } from '../../common/types'; +import fakeDeprecations from '../__fixtures__/fake_deprecations.json'; +import * as healthIndicatorsMock from '../__fixtures__/health_indicators'; +import * as esMigrationsMock from '../__fixtures__/es_deprecations'; +import type { FeatureSet } from '../../../common/types'; +import { getESUpgradeStatus } from '.'; const fakeIndexNames = Object.keys(fakeDeprecations.index_settings); -describe('getHealthIndicators', () => { - let esClient: ScopedClusterClientMock; - beforeEach(() => { - esClient = elasticsearchServiceMock.createScopedClusterClient(); - }); - - it('returns empty array on green indicators', async () => { - esClient.asCurrentUser.healthReport.mockResponse({ - cluster_name: 'mock', - indicators: { - disk: healthIndicatorsMock.diskIndicatorGreen, - // @ts-ignore - shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen, - }, - }); - - const result = await getHealthIndicators(esClient); - expect(result).toEqual([]); - }); - - it('returns unknown indicators', async () => { - esClient.asCurrentUser.healthReport.mockResponse({ - cluster_name: 'mock', - indicators: { - disk: healthIndicatorsMock.diskIndicatorUnknown, - // @ts-ignore - shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen, - }, - }); - - const result = await getHealthIndicators(esClient); - expect(result[0]).toEqual( - expect.objectContaining({ - details: 'No disk usage data.', - }) - ); - }); - - it('returns unhealthy shards_capacity indicator', async () => { - esClient.asCurrentUser.healthReport.mockResponse({ - cluster_name: 'mock', - indicators: { - disk: healthIndicatorsMock.diskIndicatorGreen, - // @ts-ignore - shards_capacity: healthIndicatorsMock.shardCapacityIndicatorRed, - }, - }); - - const result = await getHealthIndicators(esClient); - expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "correctiveAction": Object { - "action": "Increase the value of [cluster.max_shards_per_node] cluster setting or remove data indices to clear up resources.", - "cause": "Elasticsearch is about to reach the maximum number of shards it can host, based on your current settings.", - "impacts": Array [ - Object { - "description": "The cluster has too many used shards to be able to upgrade.", - "id": "elasticsearch:health:shards_capacity:impact:upgrade_blocked", - "impact_areas": "[Array]", - "severity": 1, - }, - Object { - "description": "The cluster is running low on room to add new shards. Adding data to new indices is at risk", - "id": "elasticsearch:health:shards_capacity:impact:creation_of_new_indices_blocked", - "impact_areas": "[Array]", - "severity": 1, - }, - ], - "type": "healthIndicator", - }, - "details": "Cluster is close to reaching the configured maximum number of shards for data nodes.", - "isCritical": true, - "message": "Elasticsearch is about to reach the maximum number of shards it can host, based on your current settings.", - "resolveDuringUpgrade": false, - "type": "health_indicator", - "url": "https://ela.st/fix-shards-capacity", - }, - ] - `); - }); - - it('returns unhealthy disk indicator', async () => { - esClient.asCurrentUser.healthReport.mockResponse({ - cluster_name: 'mock', - indicators: { - disk: healthIndicatorsMock.diskIndicatorRed, - // @ts-ignore - shards_capacity: healthIndicatorsMock.shardCapacityIndicatorGreen, - }, - }); - - const result = await getHealthIndicators(esClient); - expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "correctiveAction": Object { - "cause": "The number of indices the system enforced a read-only index block (\`index.blocks.read_only_allow_delete\`) on because the cluster is running out of space. - The number of nodes that are running low on disk and it is likely that they will run out of space. Their disk usage has tripped the <>. - The number of nodes that have run out of disk. Their disk usage has tripped the <>.", - "impacts": Object { - "indices_with_readonly_block": 1, - "nodes_over_flood_stage_watermark": 1, - "nodes_over_high_watermark": 1, - "nodes_with_enough_disk_space": 1, - "nodes_with_unknown_disk_status": 1, - }, - "type": "healthIndicator", - }, - "details": "The cluster does not have enough available disk space.", - "isCritical": true, - "message": "The cluster does not have enough available disk space.", - "resolveDuringUpgrade": false, - "type": "health_indicator", - "url": null, - }, - ] - `); - }); -}); - describe('getESUpgradeStatus', () => { const featureSet: FeatureSet = { reindexCorrectiveActions: true, migrateSystemIndices: true, mlSnapshots: true, + migrateDataStreams: true, }; const resolvedIndices = { @@ -200,6 +80,7 @@ describe('getESUpgradeStatus', () => { node_settings: [], ml_settings: [], index_settings: {}, + data_streams: {}, }); await expect(getESUpgradeStatus(esClient, featureSet)).resolves.toHaveProperty( @@ -215,6 +96,7 @@ describe('getESUpgradeStatus', () => { node_settings: [], ml_settings: [], index_settings: {}, + data_streams: {}, }); await expect(getESUpgradeStatus(esClient, featureSet)).resolves.toHaveProperty( @@ -240,6 +122,7 @@ describe('getESUpgradeStatus', () => { }, ], }, + data_streams: {}, }); const upgradeStatus = await getESUpgradeStatus(esClient, featureSet); @@ -249,38 +132,44 @@ describe('getESUpgradeStatus', () => { }); it('filters out ml_settings if featureSet.mlSnapshots is set to false', async () => { - esClient.asCurrentUser.migration.deprecations.mockResponse({ - cluster_settings: [], - node_settings: [], - ml_settings: [ - { - level: 'warning', - message: 'Datafeed [deprecation-datafeed] uses deprecated query options', - url: 'https://www.elastic.co/guide/en/elasticsearch/reference/master/breaking-changes-7.0.html#breaking_70_search_changes', - details: - '[Deprecated field [use_dis_max] used, replaced by [Set [tie_breaker] to 1 instead]]', - // @ts-ignore - resolve_during_rolling_upgrade: false, - }, - { - level: 'critical', - message: - 'model snapshot [1] for job [deprecation_check_job] needs to be deleted or upgraded', - url: '', - details: 'details', - // @ts-ignore - _meta: { snapshot_id: '1', job_id: 'deprecation_check_job' }, - // @ts-ignore - resolve_during_rolling_upgrade: false, - }, - ], - index_settings: {}, + const mockResponse = { + ...esMigrationsMock.getMockEsDeprecations(), + ...esMigrationsMock.getMockMlSettingsDeprecations(), + }; + // @ts-ignore missing property definitions in ES resolve_during_rolling_upgrade and _meta + esClient.asCurrentUser.migration.deprecations.mockResponse(mockResponse); + + const enabledUpgradeStatus = await getESUpgradeStatus(esClient, { ...featureSet }); + expect(enabledUpgradeStatus.deprecations).toHaveLength(2); + expect(enabledUpgradeStatus.totalCriticalDeprecations).toBe(1); + + const disabledUpgradeStatus = await getESUpgradeStatus(esClient, { + ...featureSet, + mlSnapshots: false, }); - const upgradeStatus = await getESUpgradeStatus(esClient, { ...featureSet, mlSnapshots: false }); + expect(disabledUpgradeStatus.deprecations).toHaveLength(0); + expect(disabledUpgradeStatus.totalCriticalDeprecations).toBe(0); + }); - expect(upgradeStatus.deprecations).toHaveLength(0); - expect(upgradeStatus.totalCriticalDeprecations).toBe(0); + it('filters out data_streams if featureSet.migrateDataStreams is set to false', async () => { + const mockResponse = { + ...esMigrationsMock.getMockEsDeprecations(), + ...esMigrationsMock.getMockDataStreamDeprecations(), + }; + esClient.asCurrentUser.migration.deprecations.mockResponse(mockResponse); + + const enabledUpgradeStatus = await getESUpgradeStatus(esClient, { ...featureSet }); + expect(enabledUpgradeStatus.deprecations).toHaveLength(1); + expect(enabledUpgradeStatus.totalCriticalDeprecations).toBe(1); + + const disabledUpgradeStatus = await getESUpgradeStatus(esClient, { + ...featureSet, + migrateDataStreams: false, + }); + + expect(disabledUpgradeStatus.deprecations).toHaveLength(0); + expect(disabledUpgradeStatus.totalCriticalDeprecations).toBe(0); }); it('filters out reindex corrective actions if featureSet.reindexCorrectiveActions is set to false', async () => { @@ -306,6 +195,7 @@ describe('getESUpgradeStatus', () => { ], ml_settings: [], index_settings: {}, + data_streams: {}, }); const upgradeStatus = await getESUpgradeStatus(esClient, { @@ -332,6 +222,7 @@ describe('getESUpgradeStatus', () => { ], ml_settings: [], index_settings: {}, + data_streams: {}, }); esClient.asCurrentUser.healthReport.mockResponse({ @@ -380,6 +271,7 @@ describe('getESUpgradeStatus', () => { "type": "reindex", }, "details": "This index was created using version: 6.8.13", + "index": undefined, "isCritical": true, "message": "Index created before 7.0", "resolveDuringUpgrade": false, diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/index.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/index.ts new file mode 100644 index 0000000000000..062c62660a538 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/index.ts @@ -0,0 +1,69 @@ +/* + * 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 { IScopedClusterClient } from '@kbn/core/server'; +import { EnrichedDeprecationInfo, ESUpgradeStatus, FeatureSet } from '../../../common/types'; +import { getEnrichedDeprecations } from './migrations'; +import { getHealthIndicators } from './health_indicators'; + +export async function getESUpgradeStatus( + dataClient: IScopedClusterClient, + featureSet: FeatureSet +): Promise { + const getCombinedDeprecations = async () => { + const healthIndicators = await getHealthIndicators(dataClient); + const enrichedDeprecations = await getEnrichedDeprecations(dataClient); + + const toggledMigrationsDeprecations = enrichedDeprecations.filter( + ({ type, correctiveAction }) => { + /** + * This disables showing the ML deprecations in the UA if `featureSet.mlSnapshots` + * is set to `false`. + * + * This config should be set to true only on the `x.last` versions, or when + * the constant `MachineLearningField.MIN_CHECKED_SUPPORTED_SNAPSHOT_VERSION` + * is incremented to something higher than 7.0.0 in the Elasticsearch code. + */ + if (type === 'ml_settings' || correctiveAction?.type === 'mlSnapshot') { + return featureSet.mlSnapshots; + } + + /** + * This disables showing the Data streams deprecations in the UA if + * `featureSet.migrateDataStreams` is set to `false`. + */ + if (type === 'data_streams') { + return !!featureSet.migrateDataStreams; + } + + /** + * This disables showing the reindexing deprecations in the UA if + * `featureSet.reindexCorrectiveActions` is set to `false`. + */ + if (correctiveAction?.type === 'reindex') { + return !!featureSet.reindexCorrectiveActions; + } + + return true; + } + ); + + const enrichedHealthIndicators = healthIndicators.filter(({ status }) => { + return status !== 'green'; + }) as EnrichedDeprecationInfo[]; + + return [...enrichedHealthIndicators, ...toggledMigrationsDeprecations]; + }; + + const combinedDeprecations = await getCombinedDeprecations(); + const criticalWarnings = combinedDeprecations.filter(({ isCritical }) => isCritical === true); + + return { + totalCriticalDeprecations: criticalWarnings.length, + deprecations: combinedDeprecations, + }; +} diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/migrations.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/migrations.ts new file mode 100644 index 0000000000000..39d9984a64aa1 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecations_status/migrations.ts @@ -0,0 +1,157 @@ +/* + * 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 { + MigrationDeprecationsResponse, + MigrationDeprecationsDeprecation, +} from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import _ from 'lodash'; +import { EnrichedDeprecationInfo } from '../../../common/types'; +import { + convertFeaturesToIndicesArray, + getESSystemIndicesMigrationStatus, +} from '../es_system_indices_migration'; +import { getCorrectiveAction } from './get_corrective_actions'; +import { esIndicesStateCheck } from '../es_indices_state_check'; + +/** + * Remove once the data_streams type is added to the `MigrationDeprecationsResponse` type + */ +interface EsDeprecations extends MigrationDeprecationsResponse { + data_streams: Record; +} + +const createBaseMigrationDeprecation = ( + migrationDeprecation: MigrationDeprecationsDeprecation, + { deprecationType, indexName }: { deprecationType: keyof EsDeprecations; indexName?: string } +) => { + const { + details, + message, + url, + level, + // @ts-expect-error @elastic/elasticsearch _meta not available yet in MigrationDeprecationInfoResponse + _meta: metadata, + // @ts-expect-error @elastic/elasticsearch resolve_during_rolling_upgrade not available yet in MigrationDeprecationInfoResponse + resolve_during_rolling_upgrade: resolveDuringUpgrade, + } = migrationDeprecation; + + return { + index: indexName, + type: deprecationType, + details, + message, + url, + isCritical: level === 'critical', + metadata, + resolveDuringUpgrade, + }; +}; + +const normalizeEsResponse = (migrationsResponse: EsDeprecations) => { + const indexSettingsMigrations = Object.entries(migrationsResponse.index_settings).flatMap( + ([indexName, migrationDeprecations]) => { + return migrationDeprecations.flatMap((migrationDeprecation) => + createBaseMigrationDeprecation(migrationDeprecation, { + indexName, + deprecationType: 'index_settings', + }) + ); + } + ); + + const dataStreamsMigrations = Object.entries(migrationsResponse.data_streams).flatMap( + ([indexName, dataStreamDeprecations]) => { + return dataStreamDeprecations.flatMap((depractionData) => + createBaseMigrationDeprecation(depractionData, { + indexName, + deprecationType: 'data_streams', + }) + ); + } + ); + + const mlSettingsMigrations = migrationsResponse.ml_settings.map((depractionData) => + createBaseMigrationDeprecation(depractionData, { deprecationType: 'ml_settings' }) + ); + const nodeSettingsMigrations = migrationsResponse.node_settings.map((depractionData) => + createBaseMigrationDeprecation(depractionData, { deprecationType: 'node_settings' }) + ); + + const clusterSettingsMigrations = migrationsResponse.cluster_settings.map((depractionData) => + createBaseMigrationDeprecation(depractionData, { deprecationType: 'cluster_settings' }) + ); + + return [ + ...clusterSettingsMigrations, + ...mlSettingsMigrations, + ...nodeSettingsMigrations, + ...indexSettingsMigrations, + ...dataStreamsMigrations, + ].flat(); +}; + +export const getEnrichedDeprecations = async ( + dataClient: IScopedClusterClient +): Promise => { + const deprecations = (await dataClient.asCurrentUser.migration.deprecations()) as EsDeprecations; + const systemIndices = await getESSystemIndicesMigrationStatus(dataClient.asCurrentUser); + + const systemIndicesList = convertFeaturesToIndicesArray(systemIndices.features); + + const indexSettingsIndexNames = Object.keys(deprecations.index_settings).map( + (indexName) => indexName! + ); + const indexSettingsIndexStates = indexSettingsIndexNames.length + ? await esIndicesStateCheck(dataClient.asCurrentUser, indexSettingsIndexNames) + : {}; + + return normalizeEsResponse(deprecations) + .filter((deprecation) => { + switch (deprecation.type) { + case 'index_settings': { + if (!deprecation.index) { + return false; + } + // filter out system indices + return !systemIndicesList.includes(deprecation.index); + } + case 'cluster_settings': + case 'ml_settings': + case 'node_settings': + case 'data_streams': { + return true; + } + default: { + // Throwing here to avoid allowing upgrades while we have unhandled deprecation types from ES + // That might cause the stack to fail to start after upgrade. + throw new Error(`Unknown ES deprecation type "${deprecation.type}"`); + } + } + }) + .map((deprecation) => { + const correctiveAction = getCorrectiveAction( + deprecation.message, + deprecation.metadata, + deprecation.index! + ); + + // If we have found deprecation information for index/indices + // check whether the index is open or closed. + if (deprecation.type === 'index_settings' && correctiveAction?.type === 'reindex') { + correctiveAction.blockerForReindexing = + indexSettingsIndexStates[deprecation.index!] === 'closed' ? 'index-closed' : undefined; + } + + const enrichedDeprecation = _.omit(deprecation, 'metadata'); + return { + ...enrichedDeprecation, + correctiveAction, + }; + }); +}; diff --git a/x-pack/plugins/upgrade_assistant/tsconfig.json b/x-pack/plugins/upgrade_assistant/tsconfig.json index 85fb886ca2af3..d78cafa21551b 100644 --- a/x-pack/plugins/upgrade_assistant/tsconfig.json +++ b/x-pack/plugins/upgrade_assistant/tsconfig.json @@ -42,7 +42,8 @@ "@kbn/deeplinks-observability", "@kbn/core-logging-server-mocks", "@kbn/core-http-server-mocks", - "@kbn/core-http-server-utils" + "@kbn/core-http-server-utils", + "@kbn/core-elasticsearch-server" ], "exclude": [ "target/**/*", diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/config.ts b/x-pack/test/api_integration/apis/upgrade_assistant/config.ts index 5f335f116fefe..762cb8baa1ef2 100644 --- a/x-pack/test/api_integration/apis/upgrade_assistant/config.ts +++ b/x-pack/test/api_integration/apis/upgrade_assistant/config.ts @@ -13,5 +13,15 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...baseIntegrationTestsConfig.getAll(), testFiles: [require.resolve('.')], + kbnTestServer: { + ...baseIntegrationTestsConfig.get('kbnTestServer'), + serverArgs: [ + ...baseIntegrationTestsConfig.get('kbnTestServer.serverArgs'), + '--xpack.upgrade_assistant.featureSet.mlSnapshots=true', + '--xpack.upgrade_assistant.featureSet.migrateSystemIndices=true', + '--xpack.upgrade_assistant.featureSet.migrateDataStreams=true', + '--xpack.upgrade_assistant.featureSet.reindexCorrectiveActions=true', + ], + }, }; } diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts b/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts index 7d7e035741bbb..f03cb6fb80e37 100644 --- a/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts +++ b/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts @@ -14,8 +14,7 @@ export default function ({ getService }: FtrProviderContext) { const es = getService('es'); const supertest = getService('supertest'); - // Failing: See https://github.com/elastic/kibana/issues/199719 - describe.skip('Upgrade Assistant', function () { + describe('Upgrade Assistant', function () { describe('Reindex operation saved object', () => { const dotKibanaIndex = '.kibana'; const fakeSavedObjectId = 'fakeSavedObjectId';