From 45634ed2dabdd54cc162a4ea5b10ba6be5e37d92 Mon Sep 17 00:00:00 2001 From: "Christiane (Tina) Heiligers" Date: Mon, 27 Jan 2025 12:11:47 -0700 Subject: [PATCH] [UA] Allows upgrades on cloud for minor versions (#208309) fix https://github.com/elastic/kibana/issues/206468 ## Summary Upgrade Assistant treats upgrading to a minor or patch in the same way as for a major and blocks the upgrade when there are critical deprecations. Critical deprecations only have to be resolved before upgrading to the next major version and should not prevent upgrading to a minor or patch. This PR refactors the blocking behavior and allows non-major upgrades for healthy clusters. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) ### Identify risks Does this PR introduce any risks? For example, consider risks like hard to test bugs, performance regression, potential of data loss. Describe the risk, its severity, and mitigation for each identified risk. Invite stakeholders and evaluate how to proceed before merging. - [ ] Cloud UI does not adapt the API to handle a query. Without query support, calls to the API may not work as intended, or fail. Reverting this PR will block upgrades to non major versions (next minor, next patch) if there are critical deprecations that have not been resolved. --------- Co-authored-by: Elastic Machine --- docs/api/upgrade-assistant/status.asciidoc | 15 +- ...luster_settings_deprecation_flyout.test.ts | 2 +- .../default_deprecation_flyout.test.ts | 2 +- .../es_deprecations/deprecations_list.test.ts | 38 +-- .../index_settings_deprecation_flyout.test.ts | 2 +- .../ml_snapshots_deprecation_flyout.test.ts | 2 +- .../es_deprecations/mocked_responses.ts | 10 +- .../reindex_deprecation_flyout.test.ts | 2 +- .../fix_issues_step/mock_es_issues.ts | 12 +- .../private/upgrade_assistant/common/types.ts | 4 +- .../es_deprecations/es_deprecations.tsx | 11 +- .../es_deprecation_issues_panel.tsx | 8 +- .../__snapshots__/index.test.ts.snap | 4 +- .../lib/es_deprecations_status/index.test.ts | 45 ++- .../lib/es_deprecations_status/index.ts | 22 +- .../server/lib/upgrade_type.test.ts | 43 +++ .../server/lib/upgrade_type.ts | 26 ++ .../upgrade_assistant/server/lib/version.ts | 10 +- .../upgrade_assistant/server/plugin.ts | 7 +- .../server/routes/es_deprecations.test.ts | 6 +- .../server/routes/es_deprecations.ts | 2 +- .../server/routes/status.test.ts | 264 +++++++++++++++--- .../upgrade_assistant/server/routes/status.ts | 53 +++- .../private/upgrade_assistant/server/types.ts | 3 + .../upgrade_assistant/upgrade_assistant.ts | 55 ++++ 25 files changed, 532 insertions(+), 116 deletions(-) create mode 100644 x-pack/platform/plugins/private/upgrade_assistant/server/lib/upgrade_type.test.ts create mode 100644 x-pack/platform/plugins/private/upgrade_assistant/server/lib/upgrade_type.ts diff --git a/docs/api/upgrade-assistant/status.asciidoc b/docs/api/upgrade-assistant/status.asciidoc index aec280e8d16f9..7a91ac76cdfcc 100644 --- a/docs/api/upgrade-assistant/status.asciidoc +++ b/docs/api/upgrade-assistant/status.asciidoc @@ -11,13 +11,19 @@ Check the status of your cluster. [[upgrade-assistant-api-status-request]] ==== Request -`GET :/api/upgrade_assistant/status` +`GET :/api/upgrade_assistant/status?targetVersion=9.0.0` + +`targetVersion`:: +(optional, string): Version to upgrade to. [[upgrade-assistant-api-status-response-codes]] ==== Response codes `200`:: Indicates a successful call. + +`403`:: + Indicates a forbidden request when the upgrade path is not supported (e.g. upgrading more than 1 major or downgrading) [[upgrade-assistant-api-status-example]] ==== Example @@ -28,11 +34,6 @@ The API returns the following: -------------------------------------------------- { "readyForUpgrade": false, - "cluster": [ - { - "message": "Cluster deprecated issue", - "details":"You have 2 system indices that must be migrated and 5 Elasticsearch deprecation issues and 0 Kibana deprecation issues that must be resolved before upgrading." - } - ] + "details":"The following issues must be resolved before upgrading: 1 Elasticsearch deprecation issue." } -------------------------------------------------- diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/cluster_settings_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/cluster_settings_deprecation_flyout.test.ts index e312c35813365..fe98c9bb7ad61 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/cluster_settings_deprecation_flyout.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/cluster_settings_deprecation_flyout.test.ts @@ -16,7 +16,7 @@ describe('Cluster settings deprecation flyout', () => { let testBed: ElasticsearchTestBed; let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; let httpSetup: ReturnType['httpSetup']; - const clusterSettingDeprecation = esDeprecationsMockResponse.deprecations[4]; + const clusterSettingDeprecation = esDeprecationsMockResponse.migrationsDeprecations[4]; beforeEach(async () => { const mockEnvironment = setupEnvironment(); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/default_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/default_deprecation_flyout.test.ts index 6c716dd5ad12e..34504f4a234dc 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/default_deprecation_flyout.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/default_deprecation_flyout.test.ts @@ -46,7 +46,7 @@ describe('Default deprecation flyout', () => { }); test('renders a flyout with deprecation details', async () => { - const multiFieldsDeprecation = esDeprecationsMockResponse.deprecations[2]; + const multiFieldsDeprecation = esDeprecationsMockResponse.migrationsDeprecations[2]; const { actions, find, exists } = testBed; await actions.table.clickDeprecationRowAt('default', 0); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts index f73eb7fbfabbe..2cd44d63dec9e 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/deprecations_list.test.ts @@ -60,7 +60,7 @@ describe('ES deprecations table', () => { // Verify all deprecations appear in the table expect(find('deprecationTableRow').length).toEqual( - esDeprecationsMockResponse.deprecations.length + esDeprecationsMockResponse.migrationsDeprecations.length ); }); @@ -69,8 +69,8 @@ describe('ES deprecations table', () => { await actions.table.clickRefreshButton(); - const mlDeprecation = esDeprecationsMockResponse.deprecations[0]; - const reindexDeprecation = esDeprecationsMockResponse.deprecations[3]; + const mlDeprecation = esDeprecationsMockResponse.migrationsDeprecations[0]; + const reindexDeprecation = esDeprecationsMockResponse.migrationsDeprecations[3]; // Since upgradeStatusMockResponse includes ML and reindex actions (which require fetching status), there will be 4 requests made expect(httpSetup.get).toHaveBeenCalledWith( @@ -96,10 +96,10 @@ describe('ES deprecations table', () => { it('shows critical and warning deprecations count', () => { const { find } = testBed; - const criticalDeprecations = esDeprecationsMockResponse.deprecations.filter( + const criticalDeprecations = esDeprecationsMockResponse.migrationsDeprecations.filter( (deprecation) => deprecation.isCritical ); - const warningDeprecations = esDeprecationsMockResponse.deprecations.filter( + const warningDeprecations = esDeprecationsMockResponse.migrationsDeprecations.filter( (deprecation) => deprecation.isCritical === false ); @@ -133,7 +133,7 @@ describe('ES deprecations table', () => { await actions.searchBar.clickCriticalFilterButton(); - const criticalDeprecations = esDeprecationsMockResponse.deprecations.filter( + const criticalDeprecations = esDeprecationsMockResponse.migrationsDeprecations.filter( (deprecation) => deprecation.isCritical ); @@ -142,7 +142,7 @@ describe('ES deprecations table', () => { await actions.searchBar.clickCriticalFilterButton(); expect(find('deprecationTableRow').length).toEqual( - esDeprecationsMockResponse.deprecations.length + esDeprecationsMockResponse.migrationsDeprecations.length ); }); @@ -165,7 +165,7 @@ describe('ES deprecations table', () => { component.update(); - const clusterDeprecations = esDeprecationsMockResponse.deprecations.filter( + const clusterDeprecations = esDeprecationsMockResponse.migrationsDeprecations.filter( (deprecation) => deprecation.type === 'cluster_settings' ); @@ -174,7 +174,7 @@ describe('ES deprecations table', () => { it('filters results by query string', async () => { const { find, actions } = testBed; - const multiFieldsDeprecation = esDeprecationsMockResponse.deprecations[2]; + const multiFieldsDeprecation = esDeprecationsMockResponse.migrationsDeprecations[2]; await actions.searchBar.setSearchInputValue(multiFieldsDeprecation.message); @@ -205,7 +205,7 @@ describe('ES deprecations table', () => { describe('pagination', () => { const esDeprecationsMockResponseWithManyDeprecations = createEsDeprecationsMockResponse(20); - const { deprecations } = esDeprecationsMockResponseWithManyDeprecations; + const { migrationsDeprecations } = esDeprecationsMockResponseWithManyDeprecations; beforeEach(async () => { httpRequestsMockHelpers.setLoadEsDeprecationsResponse( @@ -229,7 +229,7 @@ describe('ES deprecations table', () => { const { find, actions } = testBed; expect(find('esDeprecationsPagination').find('.euiPagination__item').length).toEqual( - Math.round(deprecations.length / 50) // Default rows per page is 50 + Math.round(migrationsDeprecations.length / 50) // Default rows per page is 50 ); expect(find('deprecationTableRow').length).toEqual(50); @@ -237,7 +237,7 @@ describe('ES deprecations table', () => { await actions.pagination.clickPaginationAt(1); // On the second (last) page, we expect to see the remaining deprecations - expect(find('deprecationTableRow').length).toEqual(deprecations.length - 50); + expect(find('deprecationTableRow').length).toEqual(migrationsDeprecations.length - 50); }); it('allows the number of viewable rows to change', async () => { @@ -260,15 +260,17 @@ describe('ES deprecations table', () => { component.update(); expect(find('esDeprecationsPagination').find('.euiPagination__item').length).toEqual( - Math.round(deprecations.length / 100) // Rows per page is now 100 + Math.round(migrationsDeprecations.length / 100) // Rows per page is now 100 ); - expect(find('deprecationTableRow').length).toEqual(deprecations.length); + expect(find('deprecationTableRow').length).toEqual(migrationsDeprecations.length); }); it('updates pagination when filters change', async () => { const { actions, find } = testBed; - const criticalDeprecations = deprecations.filter((deprecation) => deprecation.isCritical); + const criticalDeprecations = migrationsDeprecations.filter( + (deprecation) => deprecation.isCritical + ); await actions.searchBar.clickCriticalFilterButton(); @@ -279,7 +281,7 @@ describe('ES deprecations table', () => { it('updates pagination on search', async () => { const { actions, find } = testBed; - const reindexDeprecations = deprecations.filter( + const reindexDeprecations = migrationsDeprecations.filter( (deprecation) => deprecation.correctiveAction?.type === 'reindex' ); @@ -295,7 +297,9 @@ describe('ES deprecations table', () => { beforeEach(async () => { const noDeprecationsResponse = { totalCriticalDeprecations: 0, - deprecations: [], + migrationsDeprecations: [], + totalCriticalHealthIssues: 0, + enrichedHealthIndicators: [], }; httpRequestsMockHelpers.setLoadEsDeprecationsResponse(noDeprecationsResponse); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/index_settings_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/index_settings_deprecation_flyout.test.ts index 10d7481bc232d..de96a180183ba 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/index_settings_deprecation_flyout.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/index_settings_deprecation_flyout.test.ts @@ -20,7 +20,7 @@ describe('Index settings deprecation flyout', () => { let testBed: ElasticsearchTestBed; let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; let httpSetup: ReturnType['httpSetup']; - const indexSettingDeprecation = esDeprecationsMockResponse.deprecations[1]; + const indexSettingDeprecation = esDeprecationsMockResponse.migrationsDeprecations[1]; beforeEach(async () => { const mockEnvironment = setupEnvironment(); httpRequestsMockHelpers = mockEnvironment.httpRequestsMockHelpers; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/ml_snapshots_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/ml_snapshots_deprecation_flyout.test.ts index 3f50690dbdeb7..aa5f80b48c447 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/ml_snapshots_deprecation_flyout.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/ml_snapshots_deprecation_flyout.test.ts @@ -14,7 +14,7 @@ import { esDeprecationsMockResponse, MOCK_SNAPSHOT_ID, MOCK_JOB_ID } from './moc describe('Machine learning deprecation flyout', () => { let testBed: ElasticsearchTestBed; - const mlDeprecation = esDeprecationsMockResponse.deprecations[0]; + const mlDeprecation = esDeprecationsMockResponse.migrationsDeprecations[0]; let httpRequestsMockHelpers: ReturnType['httpRequestsMockHelpers']; let httpSetup: ReturnType['httpSetup']; beforeEach(async () => { diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/mocked_responses.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/mocked_responses.ts index 2f3bece2043a8..2dc33c29de9ec 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/mocked_responses.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/mocked_responses.ts @@ -77,13 +77,15 @@ const MOCK_DEFAULT_DEPRECATION: EnrichedDeprecationInfo = { export const esDeprecationsMockResponse: ESUpgradeStatus = { totalCriticalDeprecations: 2, - deprecations: [ + migrationsDeprecations: [ MOCK_ML_DEPRECATION, MOCK_INDEX_SETTING_DEPRECATION, MOCK_DEFAULT_DEPRECATION, MOCK_REINDEX_DEPRECATION, MOCK_CLUSTER_SETTING_DEPRECATION, ], + totalCriticalHealthIssues: 0, + enrichedHealthIndicators: [], }; // Useful for testing pagination where a large number of deprecations are needed @@ -118,7 +120,7 @@ export const createEsDeprecationsMockResponse = ( () => MOCK_DEFAULT_DEPRECATION ); - const deprecations: EnrichedDeprecationInfo[] = [ + const migrationsDeprecations: EnrichedDeprecationInfo[] = [ ...defaultDeprecations, ...reindexDeprecations, ...indexSettingsDeprecations, @@ -127,6 +129,8 @@ export const createEsDeprecationsMockResponse = ( return { totalCriticalDeprecations: mlDeprecations.length + reindexDeprecations.length, - deprecations, + migrationsDeprecations, + totalCriticalHealthIssues: esDeprecationsMockResponse.totalCriticalHealthIssues, + enrichedHealthIndicators: esDeprecationsMockResponse.enrichedHealthIndicators, }; }; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts index 29e6a80fc41bd..ad5d5d74f8059 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/es_deprecations/reindex_deprecation_flyout.test.ts @@ -68,7 +68,7 @@ describe('Reindex deprecation flyout', () => { }); it('renders a flyout with reindexing details', async () => { - const reindexDeprecation = esDeprecationsMockResponse.deprecations[3]; + const reindexDeprecation = esDeprecationsMockResponse.migrationsDeprecations[3]; const { actions, find, exists } = testBed; await actions.table.clickDeprecationRowAt('reindex', 0); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/mock_es_issues.ts b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/mock_es_issues.ts index 99c0f4892a4ef..4fd2f767bc707 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/mock_es_issues.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/__jest__/client_integration/overview/fix_issues_step/mock_es_issues.ts @@ -9,7 +9,7 @@ import { ESUpgradeStatus } from '../../../../common/types'; export const esCriticalAndWarningDeprecations: ESUpgradeStatus = { totalCriticalDeprecations: 1, - deprecations: [ + migrationsDeprecations: [ { isCritical: true, type: 'cluster_settings', @@ -34,11 +34,13 @@ export const esCriticalAndWarningDeprecations: ESUpgradeStatus = { }, }, ], + totalCriticalHealthIssues: 0, + enrichedHealthIndicators: [], }; export const esCriticalOnlyDeprecations: ESUpgradeStatus = { totalCriticalDeprecations: 1, - deprecations: [ + migrationsDeprecations: [ { isCritical: true, type: 'cluster_settings', @@ -49,9 +51,13 @@ export const esCriticalOnlyDeprecations: ESUpgradeStatus = { 'The Index Lifecycle Management poll interval setting [indices.lifecycle.poll_interval] is currently set to [500ms], but must be 1s or greater', }, ], + totalCriticalHealthIssues: 0, + enrichedHealthIndicators: [], }; export const esNoDeprecations: ESUpgradeStatus = { totalCriticalDeprecations: 0, - deprecations: [], + migrationsDeprecations: [], + totalCriticalHealthIssues: 0, + enrichedHealthIndicators: [], }; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/common/types.ts b/x-pack/platform/plugins/private/upgrade_assistant/common/types.ts index 8e484a047c7b1..bff91b90698a6 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/common/types.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/common/types.ts @@ -244,7 +244,9 @@ export interface CloudBackupStatus { export interface ESUpgradeStatus { totalCriticalDeprecations: number; - deprecations: EnrichedDeprecationInfo[]; + migrationsDeprecations: EnrichedDeprecationInfo[]; + totalCriticalHealthIssues: number; + enrichedHealthIndicators: EnrichedDeprecationInfo[]; } export interface ResolveIndexResponseFromES { diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx index adb6feeba1cbf..cec257f195892 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/es_deprecations/es_deprecations.tsx @@ -127,8 +127,8 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => { warningDeprecations: number; criticalDeprecations: number; } = useMemo( - () => getDeprecationCountByLevel(esDeprecations?.deprecations || []), - [esDeprecations?.deprecations] + () => getDeprecationCountByLevel(esDeprecations?.migrationsDeprecations || []), + [esDeprecations?.migrationsDeprecations] ); useEffect(() => { @@ -152,7 +152,7 @@ export const EsDeprecations = withRouter(({ history }: RouteComponentProps) => { return {i18nTexts.isLoading}; } - if (esDeprecations?.deprecations?.length === 0) { + if (esDeprecations?.migrationsDeprecations?.length === 0) { return ( { - + ); }); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/overview/fix_issues_step/components/es_deprecation_issues_panel.tsx b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/overview/fix_issues_step/components/es_deprecation_issues_panel.tsx index b4258ababc92e..2ae7399c38ad1 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/overview/fix_issues_step/components/es_deprecation_issues_panel.tsx +++ b/x-pack/platform/plugins/private/upgrade_assistant/public/application/components/overview/fix_issues_step/components/es_deprecation_issues_panel.tsx @@ -23,11 +23,13 @@ export const EsDeprecationIssuesPanel: FunctionComponent = ({ setIsFixed const { data: esDeprecations, isLoading, error } = api.useLoadEsDeprecations(); const criticalDeprecationsCount = - esDeprecations?.deprecations?.filter((deprecation) => deprecation.isCritical)?.length ?? 0; + esDeprecations?.migrationsDeprecations?.filter((deprecation) => deprecation.isCritical) + ?.length ?? 0; const warningDeprecationsCount = - esDeprecations?.deprecations?.filter((deprecation) => deprecation.isCritical === false) - ?.length ?? 0; + esDeprecations?.migrationsDeprecations?.filter( + (deprecation) => deprecation.isCritical === false + )?.length ?? 0; const errorMessage = error && getEsDeprecationError(error).message; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/__snapshots__/index.test.ts.snap b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/__snapshots__/index.test.ts.snap index 7f6b7826da8a8..a0e659be79a7a 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/__snapshots__/index.test.ts.snap +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/__snapshots__/index.test.ts.snap @@ -2,7 +2,8 @@ exports[`getESUpgradeStatus returns the correct shape of data 1`] = ` Object { - "deprecations": Array [ + "enrichedHealthIndicators": Array [], + "migrationsDeprecations": Array [ 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", @@ -183,5 +184,6 @@ Object { }, ], "totalCriticalDeprecations": 6, + "totalCriticalHealthIssues": 0, } `; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/index.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/index.test.ts index dc1e4f48bc3ef..d2cc3b9ac67f5 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/index.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/index.test.ts @@ -126,9 +126,15 @@ describe('getESUpgradeStatus', () => { }); const upgradeStatus = await getESUpgradeStatus(esClient, featureSet); - - expect(upgradeStatus.deprecations).toHaveLength(0); - expect(upgradeStatus.totalCriticalDeprecations).toBe(0); + const { + totalCriticalDeprecations, + migrationsDeprecations, + totalCriticalHealthIssues, + enrichedHealthIndicators, + } = upgradeStatus; + expect([...migrationsDeprecations, ...enrichedHealthIndicators]).toHaveLength(0); + expect(totalCriticalDeprecations).toBe(0); + expect(totalCriticalHealthIssues).toBe(0); }); it('filters out ml_settings if featureSet.mlSnapshots is set to false', async () => { @@ -140,7 +146,10 @@ describe('getESUpgradeStatus', () => { esClient.asCurrentUser.migration.deprecations.mockResponse(mockResponse); const enabledUpgradeStatus = await getESUpgradeStatus(esClient, { ...featureSet }); - expect(enabledUpgradeStatus.deprecations).toHaveLength(2); + expect([ + ...enabledUpgradeStatus.migrationsDeprecations, + ...enabledUpgradeStatus.enrichedHealthIndicators, + ]).toHaveLength(2); expect(enabledUpgradeStatus.totalCriticalDeprecations).toBe(1); const disabledUpgradeStatus = await getESUpgradeStatus(esClient, { @@ -148,7 +157,10 @@ describe('getESUpgradeStatus', () => { mlSnapshots: false, }); - expect(disabledUpgradeStatus.deprecations).toHaveLength(0); + expect([ + ...disabledUpgradeStatus.migrationsDeprecations, + ...disabledUpgradeStatus.enrichedHealthIndicators, + ]).toHaveLength(0); expect(disabledUpgradeStatus.totalCriticalDeprecations).toBe(0); }); @@ -160,7 +172,10 @@ describe('getESUpgradeStatus', () => { esClient.asCurrentUser.migration.deprecations.mockResponse(mockResponse); const enabledUpgradeStatus = await getESUpgradeStatus(esClient, { ...featureSet }); - expect(enabledUpgradeStatus.deprecations).toHaveLength(1); + expect([ + ...enabledUpgradeStatus.migrationsDeprecations, + ...enabledUpgradeStatus.enrichedHealthIndicators, + ]).toHaveLength(1); expect(enabledUpgradeStatus.totalCriticalDeprecations).toBe(1); const disabledUpgradeStatus = await getESUpgradeStatus(esClient, { @@ -168,7 +183,10 @@ describe('getESUpgradeStatus', () => { migrateDataStreams: false, }); - expect(disabledUpgradeStatus.deprecations).toHaveLength(0); + expect([ + ...disabledUpgradeStatus.migrationsDeprecations, + ...disabledUpgradeStatus.enrichedHealthIndicators, + ]).toHaveLength(0); expect(disabledUpgradeStatus.totalCriticalDeprecations).toBe(0); }); @@ -203,7 +221,10 @@ describe('getESUpgradeStatus', () => { reindexCorrectiveActions: false, }); - expect(upgradeStatus.deprecations).toHaveLength(0); + expect([ + ...upgradeStatus.migrationsDeprecations, + ...upgradeStatus.enrichedHealthIndicators, + ]).toHaveLength(0); expect(upgradeStatus.totalCriticalDeprecations).toBe(0); }); @@ -235,9 +256,11 @@ describe('getESUpgradeStatus', () => { }); const upgradeStatus = await getESUpgradeStatus(esClient, featureSet); - - expect(upgradeStatus.totalCriticalDeprecations).toBe(2); - expect(upgradeStatus.deprecations).toMatchInlineSnapshot(` + expect(upgradeStatus.totalCriticalHealthIssues + upgradeStatus.totalCriticalDeprecations).toBe( + 2 + ); + expect([...upgradeStatus.enrichedHealthIndicators, ...upgradeStatus.migrationsDeprecations]) + .toMatchInlineSnapshot(` Array [ Object { "correctiveAction": Object { diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/index.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/index.ts index 062c62660a538..ac052e92d1275 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/index.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/es_deprecations_status/index.ts @@ -56,14 +56,22 @@ export async function getESUpgradeStatus( return status !== 'green'; }) as EnrichedDeprecationInfo[]; - return [...enrichedHealthIndicators, ...toggledMigrationsDeprecations]; + return { + enrichedHealthIndicators, + migrationsDeprecations: toggledMigrationsDeprecations, + }; }; + const { enrichedHealthIndicators, migrationsDeprecations } = await getCombinedDeprecations(); - const combinedDeprecations = await getCombinedDeprecations(); - const criticalWarnings = combinedDeprecations.filter(({ isCritical }) => isCritical === true); - - return { - totalCriticalDeprecations: criticalWarnings.length, - deprecations: combinedDeprecations, + const result = { + totalCriticalDeprecations: migrationsDeprecations.filter( + ({ isCritical }) => isCritical === true + ).length, + migrationsDeprecations, + totalCriticalHealthIssues: enrichedHealthIndicators.filter( + ({ isCritical }) => isCritical === true + ).length, + enrichedHealthIndicators, }; + return result; } diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/upgrade_type.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/upgrade_type.test.ts new file mode 100644 index 0000000000000..e3a0fd2a80f79 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/upgrade_type.test.ts @@ -0,0 +1,43 @@ +/* + * 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 { getUpgradeType } from './upgrade_type'; +import { versionService } from './version'; +import semver, { SemVer } from 'semver'; + +describe('getUpgradeType', () => { + let current: SemVer; + beforeEach(() => { + versionService.setup('8.0.0'); + current = versionService.getCurrentVersion(); + }); + it('returns null if the upgrade target version is the same as the current version', () => { + const target = current.raw.toString(); + const result = getUpgradeType({ current, target }); + expect(result).toBeNull(); + }); + it("returns 'major' if the upgrade target version is the next major", () => { + const target = semver.inc(current, 'major')?.toString()!; + const result = getUpgradeType({ current, target }); + expect(result).toBe('major'); + }); + it("returns 'minor' if the upgrade target version is the next minor", () => { + const target = semver.inc(current, 'minor')?.toString()!; + const result = getUpgradeType({ current, target }); + expect(result).toBe('minor'); + }); + it('returns undefined if the upgrade target version is more than 1 major', () => { + const target = new SemVer('10.0.0').raw.toString(); + const result = getUpgradeType({ current, target }); + expect(result).toBeUndefined(); + }); + it('returns undefined if the upgrade target version is less than the current version', () => { + const target = new SemVer('7.0.0').raw.toString(); + const result = getUpgradeType({ current, target }); + expect(result).toBeUndefined(); + }); +}); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/upgrade_type.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/upgrade_type.ts new file mode 100644 index 0000000000000..2ac5e33d8dbc1 --- /dev/null +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/upgrade_type.ts @@ -0,0 +1,26 @@ +/* + * 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 semver, { SemVer } from 'semver'; + +export interface UpgradeTypeParams { + current: SemVer; + target: string; +} + +/** + * @param {SemVer} current kibana version + * @param {string} target version to upgrade to, defaults to next major + * @returns {semver.ReleaseType | null | undefined} null if same version, undefined if target version is out of bounds + */ +export const getUpgradeType = ({ current, target }: UpgradeTypeParams) => { + const targetVersion = semver.coerce(target)!; + const versionDiff = targetVersion.major - current.major; + if (versionDiff > 1 || versionDiff < 0) { + return; + } + return semver.diff(current, semver.coerce(target)!); +}; diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/version.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/version.ts index 49ec4c223fdf2..b67468ec2a572 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/lib/version.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/lib/version.ts @@ -7,7 +7,15 @@ import SemVer from 'semver/classes/semver'; -export class Version { +export interface IVersion { + setup(version: string): void; + getCurrentVersion(): SemVer; + getMajorVersion(): number; + getNextMajorVersion(): number; + getPrevMajorVersion(): number; +} + +export class Version implements IVersion { private version!: SemVer; public setup(version: string) { diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/plugin.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/plugin.ts index 3df9f7deced9d..9641fd52ed324 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/plugin.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/plugin.ts @@ -119,6 +119,8 @@ export class UpgradeAssistantServerPlugin implements Plugin { }); const router = http.createRouter(); + // Initialize version service with current kibana version + versionService.setup(this.kibanaVersion); const dependencies: RouteDependencies = { router, @@ -139,11 +141,10 @@ export class UpgradeAssistantServerPlugin implements Plugin { featureSet: this.initialFeatureSet, isSecurityEnabled: () => security !== undefined && security.license.isEnabled(), }, + current: versionService.getCurrentVersion(), + defaultTarget: versionService.getNextMajorVersion(), }; - // Initialize version service with current kibana version - versionService.setup(this.kibanaVersion); - registerRoutes(dependencies, this.getWorker.bind(this)); if (usageCollection) { diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/routes/es_deprecations.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/routes/es_deprecations.test.ts index f4792d3a24250..344e0ed76df25 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/routes/es_deprecations.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/routes/es_deprecations.test.ts @@ -54,8 +54,10 @@ describe('ES deprecations API', () => { describe('GET /api/upgrade_assistant/es_deprecations', () => { it('returns state', async () => { ESUpgradeStatusApis.getESUpgradeStatus.mockResolvedValue({ - deprecations: [], + migrationsDeprecations: [], + enrichedHealthIndicators: [], totalCriticalDeprecations: 0, + totalCriticalHealthIssues: 0, }); const resp = await routeDependencies.router.getHandler({ method: 'get', @@ -64,7 +66,7 @@ describe('ES deprecations API', () => { expect(resp.status).toEqual(200); expect(JSON.stringify(resp.payload)).toMatchInlineSnapshot( - `"{\\"deprecations\\":[],\\"totalCriticalDeprecations\\":0}"` + `"{\\"migrationsDeprecations\\":[],\\"enrichedHealthIndicators\\":[],\\"totalCriticalDeprecations\\":0,\\"totalCriticalHealthIssues\\":0}"` ); }); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/routes/es_deprecations.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/routes/es_deprecations.ts index 3b3d2387c196a..aa015c0523a18 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/routes/es_deprecations.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/routes/es_deprecations.ts @@ -41,7 +41,7 @@ export function registerESDeprecationRoutes({ const asCurrentUser = client.asCurrentUser; const reindexActions = reindexActionsFactory(savedObjectsClient, asCurrentUser); const reindexService = reindexServiceFactory(asCurrentUser, reindexActions, log, licensing); - const indexNames = status.deprecations + const indexNames = [...status.migrationsDeprecations, ...status.enrichedHealthIndicators] .filter(({ index }) => typeof index !== 'undefined') .map(({ index }) => index as string); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/routes/status.test.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/routes/status.test.ts index 21daa3e69697e..ada9c4a16fdfa 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/routes/status.test.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/routes/status.test.ts @@ -15,7 +15,10 @@ import { getESUpgradeStatus } from '../lib/es_deprecations_status'; import { getKibanaUpgradeStatus } from '../lib/kibana_status'; import { getESSystemIndicesMigrationStatus } from '../lib/es_system_indices_migration'; import type { FeatureSet } from '../../common/types'; +import { versionService } from '../lib/version'; +import { getMockVersionInfo } from '../lib/__fixtures__/version'; +const { currentVersion, nextMajor } = getMockVersionInfo(); jest.mock('../lib/es_version_precheck', () => ({ versionCheckHandlerWrapper: (a: any) => a, })); @@ -36,28 +39,36 @@ jest.mock('../lib/es_system_indices_migration', () => ({ const getESSystemIndicesMigrationStatusMock = getESSystemIndicesMigrationStatus as jest.Mock; const esDeprecationsResponse = { - cluster: [ + totalCriticalDeprecations: 1, + migrationsDeprecations: [ { - level: 'critical', - message: - 'Model snapshot [1] for job [deprecation_check_job] has an obsolete minimum version [6.3.0].', - details: 'Delete model snapshot [1] or update it to 7.0.0 or greater.', - url: 'doc_url', - correctiveAction: { - type: 'mlSnapshot', - snapshotId: '1', - jobId: 'deprecation_check_job', - }, + // This is a critical migration deprecation object, but it's not used in the tests }, ], - indices: [], + totalCriticalHealthIssues: 0, + enrichedHealthIndicators: [], +}; + +const esHealthResponse = { totalCriticalDeprecations: 1, + migrationsDeprecations: [ + { + // This is a critical migration deprecation object, but it's not used in the tests + }, + ], + totalCriticalHealthIssues: 1, + enrichedHealthIndicators: [ + { + status: 'red', // this is a critical health issue + }, + ], }; const esNoDeprecationsResponse = { - cluster: [], - indices: [], totalCriticalDeprecations: 0, + migrationsDeprecations: [], + totalCriticalHealthIssues: 0, + enrichedHealthIndicators: [], }; const systemIndicesMigrationResponse = { @@ -87,27 +98,32 @@ const systemIndicesNoMigrationResponse = { }; describe('Status API', () => { - const registerRoutes = (featureSetOverrides: Partial = {}) => { - const mockRouter = createMockRouter(); - const routeDependencies: any = { - config: { - featureSet: { - mlSnapshots: true, - migrateSystemIndices: true, - reindexCorrectiveActions: true, - ...featureSetOverrides, - }, - }, - router: mockRouter, - lib: { handleEsError }, - }; + beforeAll(() => { + versionService.setup('8.17.0'); + }); - registerUpgradeStatusRoute(routeDependencies); + describe('GET /api/upgrade_assistant/status for major upgrade', () => { + const registerRoutes = (featureSetOverrides: Partial = {}) => { + const mockRouter = createMockRouter(); + const routeDependencies: any = { + config: { + featureSet: { + mlSnapshots: true, + migrateSystemIndices: true, + reindexCorrectiveActions: true, + ...featureSetOverrides, + }, + }, + router: mockRouter, + lib: { handleEsError }, + current: currentVersion, + defaultTarget: nextMajor, + }; - return { mockRouter, routeDependencies }; - }; + registerUpgradeStatusRoute(routeDependencies); - describe('GET /api/upgrade_assistant/status', () => { + return { mockRouter, routeDependencies }; + }; afterEach(() => { jest.resetAllMocks(); }); @@ -128,6 +144,7 @@ describe('Status API', () => { })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); expect(getESSystemIndicesMigrationStatusMock).toBeCalledTimes(1); + expect(getKibanaUpgradeStatusMock).toBeCalledTimes(1); expect(resp.status).toEqual(200); expect(resp.payload).toEqual({ readyForUpgrade: false, @@ -244,4 +261,187 @@ describe('Status API', () => { ).rejects.toThrow('test error'); }); }); + + describe('GET /api/upgrade_assistant/status for non-major upgrade', () => { + const registerRoutes = (featureSetOverrides: Partial = {}) => { + const mockRouter = createMockRouter(); + const routeDependencies: any = { + config: { + featureSet: { + mlSnapshots: true, + migrateSystemIndices: true, + reindexCorrectiveActions: true, + ...featureSetOverrides, + }, + }, + router: mockRouter, + lib: { handleEsError }, + current: currentVersion, + defaultTarget: nextMajor, + }; + + registerUpgradeStatusRoute(routeDependencies); + + return { mockRouter, routeDependencies }; + }; + const testQuery = { query: { targetVersion: '8.18.0' } }; + afterEach(() => { + jest.resetAllMocks(); + }); + + it('returns readyForUpgrade === false if ES contains critical health issues, ignoring deprecations', async () => { + const { routeDependencies } = registerRoutes(); + getESUpgradeStatusMock.mockResolvedValue(esHealthResponse); + + getKibanaUpgradeStatusMock.mockResolvedValue({ + totalCriticalDeprecations: 1, + }); + + getESSystemIndicesMigrationStatusMock.mockResolvedValue(systemIndicesNoMigrationResponse); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(testQuery), kibanaResponseFactory); + + expect(getESSystemIndicesMigrationStatusMock).toBeCalledTimes(1); + expect(getKibanaUpgradeStatusMock).toBeCalledTimes(1); + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + readyForUpgrade: false, + details: + 'The following issues must be resolved before upgrading: 1 Elasticsearch deprecation issue.', + }); + }); + + it('returns readyForUpgrade === true if Kibana or ES contain critical deprecations and no system indices need migration', async () => { + const { routeDependencies } = registerRoutes(); + getESUpgradeStatusMock.mockResolvedValue(esDeprecationsResponse); + + getKibanaUpgradeStatusMock.mockResolvedValue({ + totalCriticalDeprecations: 1, + }); + + getESSystemIndicesMigrationStatusMock.mockResolvedValue(systemIndicesNoMigrationResponse); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(testQuery), kibanaResponseFactory); + + expect(getESSystemIndicesMigrationStatusMock).toBeCalledTimes(1); + expect(getKibanaUpgradeStatusMock).toBeCalledTimes(1); + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + readyForUpgrade: true, + details: 'All deprecation warnings have been resolved.', + }); + }); + + it('returns readyForUpgrade === true if Kibana or ES contain critical deprecations and system indices need migration', async () => { + const { routeDependencies } = registerRoutes(); + getESUpgradeStatusMock.mockResolvedValue(esDeprecationsResponse); + + getKibanaUpgradeStatusMock.mockResolvedValue({ + totalCriticalDeprecations: 1, + }); + + getESSystemIndicesMigrationStatusMock.mockResolvedValue(systemIndicesMigrationResponse); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(testQuery), kibanaResponseFactory); + + expect(getESSystemIndicesMigrationStatusMock).toBeCalledTimes(1); + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + readyForUpgrade: true, + details: 'All deprecation warnings have been resolved.', + }); + }); + + it('returns readyForUpgrade === true if no critical Kibana or ES deprecations but system indices need migration', async () => { + const { routeDependencies } = registerRoutes(); + getESUpgradeStatusMock.mockResolvedValue(esNoDeprecationsResponse); + + getKibanaUpgradeStatusMock.mockResolvedValue({ + totalCriticalDeprecations: 0, + }); + + getESSystemIndicesMigrationStatusMock.mockResolvedValue(systemIndicesMigrationResponse); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(testQuery), kibanaResponseFactory); + + expect(getESSystemIndicesMigrationStatusMock).toBeCalledTimes(1); + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + readyForUpgrade: true, + details: 'All deprecation warnings have been resolved.', + }); + }); + + it('returns readyForUpgrade === true if there are no critical deprecations and no system indices need migration', async () => { + const { routeDependencies } = registerRoutes(); + getESUpgradeStatusMock.mockResolvedValue(esNoDeprecationsResponse); + + getKibanaUpgradeStatusMock.mockResolvedValue({ + totalCriticalDeprecations: 0, + }); + + getESSystemIndicesMigrationStatusMock.mockResolvedValue(systemIndicesNoMigrationResponse); + + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(testQuery), kibanaResponseFactory); + + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + readyForUpgrade: true, + details: 'All deprecation warnings have been resolved.', + }); + }); + + it('skips ES system indices migration check when featureSet.migrateSystemIndices is set to false', async () => { + const { routeDependencies } = registerRoutes({ migrateSystemIndices: false }); + getESUpgradeStatusMock.mockResolvedValue(esNoDeprecationsResponse); + + getKibanaUpgradeStatusMock.mockResolvedValue({ + totalCriticalDeprecations: 0, + }); + + getESSystemIndicesMigrationStatusMock.mockResolvedValue(systemIndicesMigrationResponse); + const resp = await routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(testQuery), kibanaResponseFactory); + + expect(getESSystemIndicesMigrationStatusMock).toBeCalledTimes(0); + expect(resp.status).toEqual(200); + expect(resp.payload).toEqual({ + readyForUpgrade: true, + details: 'All deprecation warnings have been resolved.', + }); + }); + + it('returns an error if it throws', async () => { + const { routeDependencies } = registerRoutes(); + getESUpgradeStatusMock.mockRejectedValue(new Error('test error')); + + getKibanaUpgradeStatusMock.mockResolvedValue({ + totalCriticalDeprecations: 0, + }); + + await expect( + routeDependencies.router.getHandler({ + method: 'get', + pathPattern: '/api/upgrade_assistant/status', + })(routeHandlerContextMock, createRequestMock(testQuery), kibanaResponseFactory) + ).rejects.toThrow('test error'); + }); + }); }); diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/routes/status.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/routes/status.ts index 19f3a5c07a944..54d00e0e8f132 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/routes/status.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/routes/status.ts @@ -4,14 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - import { i18n } from '@kbn/i18n'; +import { schema } from '@kbn/config-schema'; import { API_BASE_PATH } from '../../common/constants'; import { getESUpgradeStatus } from '../lib/es_deprecations_status'; import { versionCheckHandlerWrapper } from '../lib/es_version_precheck'; import { getKibanaUpgradeStatus } from '../lib/kibana_status'; import { getESSystemIndicesMigrationStatus } from '../lib/es_system_indices_migration'; import { RouteDependencies } from '../types'; +import { getUpgradeType } from '../lib/upgrade_type'; /** * Note that this route is primarily intended for consumption by Cloud. @@ -20,6 +21,8 @@ export function registerUpgradeStatusRoute({ config: { featureSet }, router, lib: { handleEsError }, + current, + defaultTarget, }: RouteDependencies) { router.get( { @@ -34,25 +37,33 @@ export function registerUpgradeStatusRoute({ access: 'public', summary: `Get upgrade readiness status`, }, - validate: false, + validate: { + query: schema.object({ + targetVersion: schema.maybe(schema.string()), + }), + }, }, versionCheckHandlerWrapper(async ({ core }, request, response) => { + const targetVersion = request.query?.targetVersion || `${defaultTarget}`; + const upgradeType = getUpgradeType({ current, target: targetVersion }); + if (!upgradeType) return response.forbidden(); + try { const { elasticsearch: { client: esClient }, deprecations: { client: deprecationsClient }, } = await core; // Fetch ES upgrade status - const { totalCriticalDeprecations: esTotalCriticalDeps } = await getESUpgradeStatus( - esClient, - featureSet - ); + const { + totalCriticalDeprecations, // critical deprecations + totalCriticalHealthIssues, // critical health issues + } = await getESUpgradeStatus(esClient, featureSet); const getSystemIndicesMigrationStatus = async () => { /** * Skip system indices migration status check if `featureSet.migrateSystemIndices` * is set to `false`. This flag is enabled from configs for major version stack ugprades. - * returns `migration_status: 'NO_MIGRATION_NEEDED'` to indicate no migation needed. + * returns `migration_status: 'NO_MIGRATION_NEEDED'` to indicate no migration needed. */ if (!featureSet.migrateSystemIndices) { return { @@ -76,10 +87,19 @@ export function registerUpgradeStatusRoute({ const { totalCriticalDeprecations: kibanaTotalCriticalDeps } = await getKibanaUpgradeStatus( deprecationsClient ); - const readyForUpgrade = - esTotalCriticalDeps === 0 && - kibanaTotalCriticalDeps === 0 && - systemIndicesMigrationStatus === 'NO_MIGRATION_NEEDED'; + // non-major upgrades blocked only for health issues (status !== green) + let upgradeTypeBasedReadyForUpgrade: boolean; + if (upgradeType === 'major') { + upgradeTypeBasedReadyForUpgrade = + totalCriticalHealthIssues === 0 && + totalCriticalDeprecations === 0 && + kibanaTotalCriticalDeps === 0 && + systemIndicesMigrationStatus === 'NO_MIGRATION_NEEDED'; + } else { + upgradeTypeBasedReadyForUpgrade = totalCriticalHealthIssues === 0; + } + + const readyForUpgrade = upgradeType && upgradeTypeBasedReadyForUpgrade; const getStatusMessage = () => { if (readyForUpgrade) { @@ -89,8 +109,12 @@ export function registerUpgradeStatusRoute({ } const upgradeIssues: string[] = []; + let esTotalCriticalDeps = totalCriticalHealthIssues; + if (upgradeType === 'major') { + esTotalCriticalDeps += totalCriticalDeprecations; + } - if (notMigratedSystemIndices) { + if (upgradeType === 'major' && notMigratedSystemIndices) { upgradeIssues.push( i18n.translate('xpack.upgradeAssistant.status.systemIndicesMessage', { defaultMessage: @@ -99,7 +123,7 @@ export function registerUpgradeStatusRoute({ }) ); } - + // can be improved by showing health indicator issues separately if (esTotalCriticalDeps) { upgradeIssues.push( i18n.translate('xpack.upgradeAssistant.status.esTotalCriticalDepsMessage', { @@ -110,7 +134,7 @@ export function registerUpgradeStatusRoute({ ); } - if (kibanaTotalCriticalDeps) { + if (upgradeType === 'major' && kibanaTotalCriticalDeps) { upgradeIssues.push( i18n.translate('xpack.upgradeAssistant.status.kibanaTotalCriticalDepsMessage', { defaultMessage: @@ -119,7 +143,6 @@ export function registerUpgradeStatusRoute({ }) ); } - return i18n.translate('xpack.upgradeAssistant.status.deprecationsUnresolvedMessage', { defaultMessage: 'The following issues must be resolved before upgrading: {upgradeIssues}.', diff --git a/x-pack/platform/plugins/private/upgrade_assistant/server/types.ts b/x-pack/platform/plugins/private/upgrade_assistant/server/types.ts index 487577b648a52..a5b1049bd6cb4 100644 --- a/x-pack/platform/plugins/private/upgrade_assistant/server/types.ts +++ b/x-pack/platform/plugins/private/upgrade_assistant/server/types.ts @@ -8,6 +8,7 @@ import { IRouter, Logger, SavedObjectsServiceStart } from '@kbn/core/server'; import { LicensingPluginSetup } from '@kbn/licensing-plugin/server'; import { SecurityPluginStart } from '@kbn/security-plugin/server'; +import SemVer from 'semver/classes/semver'; import { CredentialStore } from './lib/reindexing/credential_store'; import { handleEsError } from './shared_imports'; import type { FeatureSet } from '../common/types'; @@ -26,4 +27,6 @@ export interface RouteDependencies { featureSet: FeatureSet; isSecurityEnabled: () => boolean; }; + current: SemVer; + defaultTarget: number; } 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 d7a96333f49e8..f0b8259fb7363 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 @@ -128,13 +128,68 @@ export default function ({ getService }: FtrProviderContext) { .expect(200); const expectedResponseKeys = ['readyForUpgrade', 'details']; + // We're not able to easily test different upgrade status scenarios (there are tests with mocked data to handle this) + // so, for now, we simply verify the response returns the expected format + expectedResponseKeys.forEach((key) => { + expect(body[key]).to.not.equal(undefined); + }); + }); + + it('returns a successful response when upgrading to the next minor', async () => { + const { body } = await supertest + .get('/api/upgrade_assistant/status') + .query({ + targetVersion: '9.1.0', + }) + .set('kbn-xsrf', 'xxx') + .expect(200); + const expectedResponseKeys = ['readyForUpgrade', 'details']; // We're not able to easily test different upgrade status scenarios (there are tests with mocked data to handle this) // so, for now, we simply verify the response returns the expected format expectedResponseKeys.forEach((key) => { expect(body[key]).to.not.equal(undefined); }); }); + + it('returns a successful response when upgrading to the next major', async () => { + const { body } = await supertest + .get('/api/upgrade_assistant/status') + .query({ + targetVersion: '10.0.0', + }) + .set('kbn-xsrf', 'xxx') + .expect(200); + + const expectedResponseKeys = ['readyForUpgrade', 'details']; + // We're not able to easily test different upgrade status scenarios (there are tests with mocked data to handle this) + // so, for now, we simply verify the response returns the expected format + expectedResponseKeys.forEach((key) => { + expect(body[key]).to.not.equal(undefined); + }); + }); + + it('returns 403 forbidden error when upgrading more than 1 major', async () => { + const { body } = await supertest + .get('/api/upgrade_assistant/status') + .query({ + targetVersion: '11.0.0', + }) + .set('kbn-xsrf', 'xxx') + .expect(403); + expect(body.message).to.be('Forbidden'); + }); + + it('returns 403 forbidden error when attempting to downgrade', async () => { + const { body } = await supertest + .get('/api/upgrade_assistant/status') + .query({ + targetVersion: '8.0.0', + }) + .set('kbn-xsrf', 'xxx') + .expect(403); + expect(body.message).to.be('Forbidden'); + }); }); }); }